353 lines
9.9 KiB
Vue
353 lines
9.9 KiB
Vue
<template>
|
||
<el-dialog
|
||
:title="isEditing ? '编辑租户' : '添加租户'"
|
||
v-model="visible"
|
||
width="500px"
|
||
:close-on-click-modal="false"
|
||
@close="handleClose"
|
||
>
|
||
<el-form
|
||
:model="tenantForm"
|
||
:rules="formRules"
|
||
ref="tenantFormRef"
|
||
label-width="90px"
|
||
>
|
||
<el-form-item label="租户名称" prop="name">
|
||
<el-input v-model="tenantForm.name" placeholder="请输入租户名称" />
|
||
</el-form-item>
|
||
<el-form-item label="负责人" prop="owner">
|
||
<el-input v-model="tenantForm.owner" placeholder="请输入负责人" />
|
||
</el-form-item>
|
||
<el-form-item label="联系电话" prop="phone">
|
||
<el-input v-model="tenantForm.phone" placeholder="请输入联系电话" />
|
||
</el-form-item>
|
||
<el-form-item label="邮箱" prop="email">
|
||
<el-input v-model="tenantForm.email" placeholder="请输入邮箱" />
|
||
</el-form-item>
|
||
<el-form-item label="状态" prop="status">
|
||
<el-select v-model="tenantForm.status" placeholder="请选择状态" style="width: 100%">
|
||
<el-option
|
||
v-for="item in tenantStatusDict"
|
||
:key="item.dict_value"
|
||
:label="item.dict_label"
|
||
:value="item.dict_value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="存储容量" prop="capacity">
|
||
<el-input-number
|
||
v-model="tenantForm.capacity"
|
||
:min="0"
|
||
:precision="2"
|
||
:step="100"
|
||
style="width: 100%"
|
||
placeholder="请输入存储容量(MB)"
|
||
/>
|
||
<div class="form-tip">单位:MB,1GB = 1024MB</div>
|
||
</el-form-item>
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input
|
||
v-model="tenantForm.remark"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入备注"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="附件">
|
||
<el-upload
|
||
:limit="1"
|
||
list-type="picture-card"
|
||
:file-list="uploadFileList"
|
||
:before-upload="beforeUpload"
|
||
:http-request="handleCustomUpload"
|
||
:on-success="handleUploadSuccess"
|
||
:on-remove="handleRemove"
|
||
:on-preview="handlePreview"
|
||
accept="image/*"
|
||
>
|
||
<el-icon><Plus /></el-icon>
|
||
</el-upload>
|
||
<el-dialog v-model="previewVisible" width="500px">
|
||
<img :src="previewUrl" style="width: 100%" />
|
||
</el-dialog>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="handleClose">取消</el-button>
|
||
<el-button type="primary" @click="handleSubmit" :loading="submitting">
|
||
保存
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, watch, computed, onMounted, type Ref } from 'vue';
|
||
import {
|
||
ElMessage,
|
||
ElMessageBox,
|
||
type FormInstance,
|
||
type FormRules,
|
||
type UploadFile,
|
||
type UploadUserFile,
|
||
type UploadRequestOptions,
|
||
} from 'element-plus';
|
||
import { Plus } from '@element-plus/icons-vue';
|
||
import { createTenant, updateTenant } from '@/api/tenant';
|
||
import { uploadFile } from '@/api/file';
|
||
import request from '@/utils/request';
|
||
import { useDictStore } from '@/stores/dict';
|
||
|
||
interface Props {
|
||
modelValue: boolean;
|
||
tenant?: any;
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
modelValue: false,
|
||
tenant: null,
|
||
});
|
||
|
||
const emit = defineEmits<{
|
||
'update:modelValue': [value: boolean];
|
||
success: [];
|
||
}>();
|
||
|
||
const visible = computed({
|
||
get: () => props.modelValue,
|
||
set: (val) => emit('update:modelValue', val),
|
||
});
|
||
|
||
const submitting = ref(false);
|
||
const tenantFormRef = ref<FormInstance>();
|
||
const uploadFileList = ref<UploadUserFile[]>([]);
|
||
const previewVisible = ref(false);
|
||
const previewUrl = ref('');
|
||
|
||
// 字典数据
|
||
const dictStore = useDictStore();
|
||
const tenantStatusDict = ref<any[]>([]);
|
||
|
||
// 获取租户状态字典数据
|
||
async function fetchTenantStatusDict() {
|
||
try {
|
||
tenantStatusDict.value = await dictStore.getDictItems('tenants_status');
|
||
} catch (error) {
|
||
console.error('获取租户状态字典数据失败:', error);
|
||
}
|
||
}
|
||
|
||
// 判断是否为编辑模式
|
||
const isEditing = computed(() => {
|
||
return !!(props.tenant && props.tenant.id);
|
||
});
|
||
|
||
// 表单数据
|
||
const tenantForm = reactive({
|
||
id: null as number | null,
|
||
name: '',
|
||
owner: '',
|
||
phone: '',
|
||
email: '',
|
||
status: '1', // 改为字符串类型,与字典数据中的dict_value保持一致
|
||
capacity: 0,
|
||
remark: '',
|
||
attachment_url: '' as string,
|
||
});
|
||
|
||
const formRules: FormRules = {
|
||
name: [{ required: true, message: '请输入租户名称', trigger: 'blur' }],
|
||
owner: [{ required: true, message: '请输入负责人', trigger: 'blur' }],
|
||
phone: [
|
||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' },
|
||
],
|
||
email: [{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }],
|
||
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
|
||
capacity: [
|
||
{ required: true, message: '请输入存储容量', trigger: 'blur' },
|
||
{ type: 'number', min: 0, message: '存储容量不能小于0', trigger: 'blur' },
|
||
],
|
||
};
|
||
|
||
function resolveFileUrl(path: string): string {
|
||
if (!path) return '';
|
||
if (/^https?:\/\//i.test(path)) return path;
|
||
const base = (request as any)?.defaults?.baseURL || '';
|
||
try {
|
||
const origin = new URL(base, window.location.origin).origin;
|
||
return origin.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '');
|
||
} catch {
|
||
return path;
|
||
}
|
||
}
|
||
|
||
// 重置表单
|
||
function resetForm() {
|
||
tenantForm.id = null;
|
||
tenantForm.name = '';
|
||
tenantForm.owner = '';
|
||
tenantForm.phone = '';
|
||
tenantForm.email = '';
|
||
tenantForm.status = '1'; // 改为字符串类型,与字典数据中的dict_value保持一致
|
||
tenantForm.capacity = 0;
|
||
tenantForm.remark = '';
|
||
tenantForm.attachment_url = '';
|
||
uploadFileList.value = [];
|
||
tenantFormRef.value?.resetFields();
|
||
}
|
||
|
||
// 初始化表单数据
|
||
function initFormData() {
|
||
if (props.tenant && props.tenant.id) {
|
||
tenantForm.id = props.tenant.id;
|
||
tenantForm.name = props.tenant.name || '';
|
||
tenantForm.owner = props.tenant.owner || '';
|
||
tenantForm.phone = props.tenant.phone || '';
|
||
tenantForm.email = props.tenant.email || '';
|
||
// 确保status是字符串类型,与字典数据中的dict_value保持一致
|
||
tenantForm.status = props.tenant.status != null ? String(props.tenant.status) : '1';
|
||
// capacity 后端返回的是 MB,直接使用
|
||
tenantForm.capacity = props.tenant.capacity != null ? Number(props.tenant.capacity) : 0;
|
||
tenantForm.remark = props.tenant.remark || '';
|
||
tenantForm.attachment_url = props.tenant.attachment_url || '';
|
||
uploadFileList.value = tenantForm.attachment_url
|
||
? [{ name: '附件', url: resolveFileUrl(tenantForm.attachment_url) }]
|
||
: [];
|
||
} else {
|
||
resetForm();
|
||
}
|
||
}
|
||
|
||
// 提交表单
|
||
async function handleSubmit() {
|
||
if (!tenantFormRef.value) return;
|
||
|
||
await tenantFormRef.value.validate();
|
||
submitting.value = true;
|
||
try {
|
||
const tenantData = {
|
||
name: tenantForm.name,
|
||
owner: tenantForm.owner,
|
||
phone: tenantForm.phone || '',
|
||
email: tenantForm.email || '',
|
||
status: Number(tenantForm.status), // 提交时转换为数字类型
|
||
// capacity 前后端都使用 MB
|
||
capacity: tenantForm.capacity || 0,
|
||
remark: tenantForm.remark || '',
|
||
attachment_url: tenantForm.attachment_url || '',
|
||
};
|
||
|
||
let res;
|
||
if (isEditing.value && tenantForm.id) {
|
||
res = await updateTenant(tenantForm.id, tenantData);
|
||
} else {
|
||
res = await createTenant(tenantData);
|
||
}
|
||
|
||
if (res.success) {
|
||
if (isEditing.value) {
|
||
ElMessage.success('租户信息已更新');
|
||
} else {
|
||
ElMessage.success('租户添加成功');
|
||
}
|
||
handleClose();
|
||
emit('success');
|
||
} else {
|
||
ElMessage.error(res.message || '操作失败,请重试');
|
||
}
|
||
} catch (err: any) {
|
||
ElMessage.error(err.message || '操作失败,请重试');
|
||
} finally {
|
||
submitting.value = false;
|
||
}
|
||
}
|
||
|
||
const handleClose = () => {
|
||
visible.value = false;
|
||
resetForm();
|
||
};
|
||
|
||
// 监听 tenant 变化,初始化表单
|
||
watch(
|
||
() => props.modelValue,
|
||
(newVal) => {
|
||
if (newVal) {
|
||
initFormData();
|
||
}
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
|
||
// 在组件挂载时获取字典数据
|
||
onMounted(() => {
|
||
fetchTenantStatusDict();
|
||
});
|
||
|
||
watch(
|
||
() => props.tenant,
|
||
() => {
|
||
if (visible.value) {
|
||
initFormData();
|
||
}
|
||
},
|
||
{ deep: true }
|
||
);
|
||
|
||
const beforeUpload = (file: File) => {
|
||
const isImage = file.type.startsWith('image/');
|
||
const isLt5M = file.size / 1024 / 1024 < 5;
|
||
if (!isImage) {
|
||
ElMessage.error('仅支持图片类型');
|
||
return false;
|
||
}
|
||
if (!isLt5M) {
|
||
ElMessage.error('图片大小不能超过 5MB');
|
||
return false;
|
||
}
|
||
return true;
|
||
};
|
||
|
||
const handleUploadSuccess = (response: any, file: UploadFile) => {
|
||
const raw = response?.data?.file_url || response?.data?.url || response?.url || '';
|
||
if (raw) {
|
||
tenantForm.attachment_url = raw;
|
||
const abs = resolveFileUrl(raw);
|
||
uploadFileList.value = [{ name: file.name, url: abs }];
|
||
ElMessage.success('上传成功');
|
||
} else {
|
||
ElMessage.error('上传成功但未返回URL');
|
||
}
|
||
};
|
||
|
||
const handleRemove = () => {
|
||
tenantForm.attachment_url = '';
|
||
uploadFileList.value = [];
|
||
};
|
||
|
||
const handlePreview = (file: UploadFile) => {
|
||
previewUrl.value = (file.url as string) || tenantForm.attachment_url || '';
|
||
if (previewUrl.value) previewVisible.value = true;
|
||
};
|
||
|
||
const handleCustomUpload = async (options: UploadRequestOptions) => {
|
||
const { file, onError, onSuccess } = options as any;
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append('file', file as File);
|
||
const res = await uploadFile(formData, { category: '图片' });
|
||
onSuccess && onSuccess(res);
|
||
} catch (err) {
|
||
ElMessage.error('上传失败');
|
||
onError && onError(err);
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.form-tip {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-placeholder);
|
||
margin-top: 4px;
|
||
}
|
||
</style>
|