generated from Leo_Ding/web-template
526 lines
16 KiB
Vue
526 lines
16 KiB
Vue
<template>
|
||
<a-modal
|
||
:width="800"
|
||
:open="visible"
|
||
:title="mode === 'create' ? '新增组织' : mode === 'edit' ? '编辑组织' : '组织详情'"
|
||
:confirm-loading="confirmLoading"
|
||
:after-close="onAfterClose"
|
||
:cancel-text="t('button.cancel')"
|
||
:ok-text="mode === 'view' ? t('button.close') : mode === 'edit' ? '保存' : '新增'"
|
||
@ok="handleOk"
|
||
@cancel="handleCancel"
|
||
:maskClosable="false"
|
||
>
|
||
<a-form
|
||
ref="formRef"
|
||
:model="formData"
|
||
:rules="isView ? {} : formRules"
|
||
layout="vertical"
|
||
autocomplete="off"
|
||
:disabled="isView"
|
||
>
|
||
<!-- 基础信息 -->
|
||
<a-card class="mb-4" title="基础信息">
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item :label="'组织名称'" name="name" :required="true">
|
||
<a-input v-model:value="formData.name" placeholder="请输入组织全称" allow-clear />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="12">
|
||
<a-form-item :label="'组织机构代码'" name="orgCode" :required="true">
|
||
<a-input v-model:value="formData.orgCode" placeholder="统一社会信用代码或组织机构代码" allow-clear />
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item :label="'组织等级'" name="orgLv" :required="true">
|
||
<a-select v-model:value="formData.orgLv" placeholder="请选择组织等级" allow-clear>
|
||
<a-select-option
|
||
v-for="item in dicsStore.dictOptions.Level"
|
||
:key="item.dval"
|
||
:value="item.dval"
|
||
>
|
||
{{ item.introduction }}
|
||
</a-select-option>
|
||
</a-select>
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
</a-card>
|
||
|
||
<!-- 联系人信息 -->
|
||
<a-card class="mb-4" title="联系人信息" style="margin-top: 20px">
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item :label="'负责人姓名'" name="concatName" :required="true">
|
||
<a-input v-model:value="formData.concatName" placeholder="请输入负责人姓名" allow-clear />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="12">
|
||
<a-form-item :label="'联系电话'" name="concatPhone" :required="true">
|
||
<a-input v-model:value="formData.concatPhone" placeholder="请输入手机号或固话" allow-clear />
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item :label="'法人姓名'" name="legalName">
|
||
<a-input v-model:value="formData.legalName" placeholder="非必填" allow-clear />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="12">
|
||
<a-form-item :label="'法人联系电话'" name="legalPhone">
|
||
<a-input v-model:value="formData.legalPhone" placeholder="非必填" allow-clear />
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
</a-card>
|
||
|
||
<!-- 资质信息 -->
|
||
<a-card class="mb-4" title="资质信息" style="margin-top: 20px">
|
||
<template #extra>
|
||
<a-tooltip title="营业执照、登记机关等用于资质审核">
|
||
<info-circle-outlined style="color: #8c8c8c" />
|
||
</a-tooltip>
|
||
</template>
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item :label="'营业执照名称'" name="businessLicenseName">
|
||
<a-input v-model:value="formData.businessLicenseName" placeholder="非必填" allow-clear />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="12">
|
||
<a-form-item :label="'登记机关'" name="registrationAuthorityName">
|
||
<a-input v-model:value="formData.registrationAuthorityName" placeholder="非必填" allow-clear />
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
<a-row :gutter="16">
|
||
<a-col :span="24">
|
||
<a-form-item :label="'登记证号'" name="registrationAuthorityNo">
|
||
<a-input v-model:value="formData.registrationAuthorityNo" placeholder="非必填" allow-clear />
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
<a-row :gutter="16">
|
||
<a-col :span="24">
|
||
<a-form-item :label="'中标组织'" name="winOrg">
|
||
<a-input v-model:value="formData.winOrg" placeholder="若为中标单位请填写,否则可留空" allow-clear />
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
</a-card>
|
||
|
||
<!-- 资质附件 -->
|
||
<a-card class="mb-4" title="资质附件" style="margin-top: 20px">
|
||
<!-- 营业执照 -->
|
||
<a-form-item :label="'营业执照图片'" name="businessLicenseFiles">
|
||
<a-upload
|
||
v-model:file-list="formData.businessLicenseFiles"
|
||
list-type="picture-card"
|
||
:before-upload="beforeUploadImage"
|
||
:custom-request="dummyRequest"
|
||
accept="image/*"
|
||
multiple
|
||
:disabled="isView"
|
||
>
|
||
<div v-if="formData.businessLicenseFiles.length < 5">
|
||
<plus-outlined />
|
||
<div class="ant-upload-text">上传</div>
|
||
</div>
|
||
</a-upload>
|
||
<div class="upload-tip">仅支持 JPG/PNG,最多5张,每张 ≤5MB</div>
|
||
</a-form-item>
|
||
|
||
<!-- 登记证 -->
|
||
<a-form-item :label="'登记证附件'" name="registrationCertificateFiles">
|
||
<a-upload
|
||
v-model:file-list="formData.registrationCertificateFiles"
|
||
list-type="picture-card"
|
||
:before-upload="beforeUploadImage"
|
||
:custom-request="dummyRequest"
|
||
accept="image/*"
|
||
multiple
|
||
:disabled="isView"
|
||
>
|
||
<div v-if="formData.registrationCertificateFiles.length < 5">
|
||
<plus-outlined />
|
||
<div class="ant-upload-text">上传</div>
|
||
</div>
|
||
</a-upload>
|
||
<div class="upload-tip">仅支持 JPG/PNG,最多5张,每张 ≤5MB</div>
|
||
</a-form-item>
|
||
|
||
<!-- 其他资质 -->
|
||
<a-form-item :label="'其他资质附件'" name="otherQualificationFiles">
|
||
<a-upload
|
||
v-model:file-list="formData.otherQualificationFiles"
|
||
:before-upload="beforeUploadFile"
|
||
:custom-request="dummyRequest"
|
||
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png"
|
||
multiple
|
||
:disabled="isView"
|
||
>
|
||
<a-button>点击上传资质文件</a-button>
|
||
</a-upload>
|
||
<div class="upload-tip">支持 PDF/Word/Excel/图片,单个 ≤10MB</div>
|
||
</a-form-item>
|
||
</a-card>
|
||
|
||
<!-- 地址信息 -->
|
||
<a-card class="mb-4" title="地址信息" style="margin-top: 20px">
|
||
<a-form-item :label="'所在地区'" name="areaCodes" :required="true">
|
||
<AreaCascader
|
||
v-model:value="formData.areaValue"
|
||
@change="onAreaChange"
|
||
:disabled="isView"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item :label="'详细地址'" name="address" :required="true">
|
||
<a-textarea
|
||
v-model:value="formData.address"
|
||
placeholder="请输入街道、门牌号等详细地址"
|
||
:rows="2"
|
||
show-count
|
||
:maxlength="256"
|
||
/>
|
||
</a-form-item>
|
||
</a-card>
|
||
|
||
<!-- 其他 -->
|
||
<a-card class="mb-4" title="其他" style="margin-top: 20px">
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item :label="'状态'" name="status" :required="true">
|
||
<a-select v-model:value="formData.status" style="width: 120px">
|
||
<a-select-option value="1">启用</a-select-option>
|
||
<a-select-option value="2">禁用</a-select-option>
|
||
</a-select>
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
<a-row :gutter="16">
|
||
<a-col :span="24">
|
||
<a-form-item :label="'备注'" name="remark">
|
||
<a-textarea
|
||
v-model:value="formData.remark"
|
||
placeholder="可填写特殊说明、内部备注等"
|
||
:rows="3"
|
||
show-count
|
||
:maxlength="500"
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
</a-card>
|
||
</a-form>
|
||
</a-modal>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, nextTick, computed } from 'vue';
|
||
import { message } from 'ant-design-vue';
|
||
import { InfoCircleOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||
import AreaCascader from '@/components/AreaCascader/index.vue';
|
||
import { useDicsStore } from '@/store';
|
||
import apis from '@/apis';
|
||
import { useI18n } from 'vue-i18n';
|
||
|
||
const { t } = useI18n();
|
||
const visible = ref(false);
|
||
const formRef = ref();
|
||
const confirmLoading = ref(false);
|
||
const dicsStore = useDicsStore();
|
||
const mode = ref('create');
|
||
const isView = computed(() => mode.value === 'view');
|
||
const isEditing = computed(() => mode.value === 'edit');
|
||
const emit = defineEmits(['success']);
|
||
const editingId = ref(null);
|
||
|
||
const formData = reactive({
|
||
name: '',
|
||
orgCode: '',
|
||
orgLv: '',
|
||
concatName: '',
|
||
concatPhone: '',
|
||
legalName: '',
|
||
legalPhone: '',
|
||
businessLicenseName: '',
|
||
registrationAuthorityName: '',
|
||
registrationAuthorityNo: '',
|
||
winOrg: '',
|
||
areaValue: [],
|
||
address: '',
|
||
remark: '',
|
||
status: '1',
|
||
businessLicenseFiles: [],
|
||
registrationCertificateFiles: [],
|
||
otherQualificationFiles: [],
|
||
});
|
||
|
||
const formRules = {
|
||
name: [{ required: true, message: '请输入组织名称' }],
|
||
orgCode: [{ required: true, message: '请输入组织机构代码' }],
|
||
orgLv: [{ required: true, message: '请选择组织等级' }],
|
||
concatName: [{ required: true, message: '请输入负责人姓名' }],
|
||
concatPhone: [{ required: true, message: '请输入联系电话' }],
|
||
status: [{ required: true, message: '请选择状态' }],
|
||
areaCodes: [
|
||
{
|
||
required: true,
|
||
validator: () => {
|
||
if (!Array.isArray(formData.areaValue) || formData.areaValue.length === 0) {
|
||
return Promise.reject(new Error('请选择服务组织地址'));
|
||
}
|
||
return Promise.resolve();
|
||
},
|
||
},
|
||
],
|
||
address: [{ required: true, message: '请输入详细地址' }],
|
||
};
|
||
|
||
const open = async ({ record = null, mode: _mode = 'create' }) => {
|
||
visible.value = true;
|
||
mode.value = _mode;
|
||
editingId.value = null;
|
||
|
||
await nextTick();
|
||
if (record && _mode !== 'create') {
|
||
editingId.value = record.id;
|
||
await getDetail(record.id);
|
||
} else {
|
||
resetForm();
|
||
}
|
||
|
||
if (formRef.value) formRef.value.clearValidate();
|
||
};
|
||
|
||
const getDetail = async (id) => {
|
||
const res = await apis.serviceMenu.getServiceOrgDetail(id);
|
||
if (res?.success) {
|
||
const data = res.data;
|
||
|
||
const areaLabels = data.areaLabels
|
||
? (Array.isArray(data.areaLabels) ? data.areaLabels : String(data.areaLabels).split('/').filter(Boolean))
|
||
: [];
|
||
|
||
const bizFiles = (data.businessLicenseUrls || []).map((url, i) => ({
|
||
uid: `biz-${i}`,
|
||
name: `营业执照-${i + 1}.jpg`,
|
||
status: 'done',
|
||
url,
|
||
}));
|
||
const regFiles = (data.registrationCertificateUrls || []).map((url, i) => ({
|
||
uid: `reg-${i}`,
|
||
name: `登记证-${i + 1}.jpg`,
|
||
status: 'done',
|
||
url,
|
||
}));
|
||
const qualFiles = (data.qualificationFileUrls || []).map((item, i) => ({
|
||
uid: `qual-${i}`,
|
||
name: item.name || `附件-${i + 1}`,
|
||
status: 'done',
|
||
url: item.url,
|
||
}));
|
||
|
||
Object.assign(formData, {
|
||
name: data.name || '',
|
||
orgCode: data.orgCode || '',
|
||
orgLv: data.orgLv || '',
|
||
concatName: data.concatName || '',
|
||
concatPhone: data.concatPhone || '',
|
||
legalName: data.legalName || '',
|
||
legalPhone: data.legalPhone || '',
|
||
businessLicenseName: data.businessLicenseName || '',
|
||
registrationAuthorityName: data.registrationAuthorityName || '',
|
||
registrationAuthorityNo: data.registrationAuthorityNo || '',
|
||
winOrg: data.winOrg || '',
|
||
areaValue: areaLabels,
|
||
address: data.address || '',
|
||
remark: data.remark || '',
|
||
status: String(data.status) || '1',
|
||
businessLicenseFiles: bizFiles,
|
||
registrationCertificateFiles: regFiles,
|
||
otherQualificationFiles: qualFiles,
|
||
});
|
||
} else {
|
||
message.error(res?.message || '获取详情失败');
|
||
}
|
||
};
|
||
|
||
const handleOk = () => {
|
||
if (mode.value === 'view') {
|
||
handleCancel();
|
||
return;
|
||
}
|
||
handleSubmit();
|
||
};
|
||
|
||
const handleSubmit = async () => {
|
||
try {
|
||
const values = await formRef.value.validateFields();
|
||
confirmLoading.value = true;
|
||
|
||
// 上传文件逻辑(略,与原逻辑一致)
|
||
const bizUrls = [];
|
||
for (const f of formData.businessLicenseFiles) {
|
||
if (f.originFileObj) bizUrls.push(await uploadFile(f.originFileObj));
|
||
else if (f.url) bizUrls.push(f.url);
|
||
}
|
||
|
||
const regUrls = [];
|
||
for (const f of formData.registrationCertificateFiles) {
|
||
if (f.originFileObj) regUrls.push(await uploadFile(f.originFileObj));
|
||
else if (f.url) regUrls.push(f.url);
|
||
}
|
||
|
||
const qualFiles = [];
|
||
for (const f of formData.otherQualificationFiles) {
|
||
if (f.originFileObj) {
|
||
const url = await uploadFile(f.originFileObj);
|
||
qualFiles.push({ name: f.name, url });
|
||
} else if (f.url) {
|
||
qualFiles.push({ name: f.name, url: f.url });
|
||
}
|
||
}
|
||
|
||
const submitData = {
|
||
...values,
|
||
areaCodes: formData.areaValue,
|
||
businessLicenseUrls: bizUrls,
|
||
registrationCertificateUrls: regUrls,
|
||
qualificationFileUrls: qualFiles,
|
||
};
|
||
|
||
let res;
|
||
if (isEditing.value) {
|
||
res = await apis.serviceMenu.updateServiceOrg(editingId.value, submitData);
|
||
} else {
|
||
res = await apis.serviceMenu.createServiceOrg(submitData);
|
||
}
|
||
|
||
confirmLoading.value = false;
|
||
if (res?.success) {
|
||
message.success(isEditing.value ? '更新成功' : '创建成功');
|
||
handleCancel();
|
||
emit('success');
|
||
} else {
|
||
message.error(res?.message || '操作失败');
|
||
}
|
||
} catch (err) {
|
||
console.error(err);
|
||
confirmLoading.value = false;
|
||
message.error('提交失败,请重试');
|
||
}
|
||
};
|
||
|
||
const handleCancel = () => {
|
||
visible.value = false;
|
||
};
|
||
|
||
const onAfterClose = () => {
|
||
resetForm();
|
||
confirmLoading.value = false;
|
||
if (formRef.value) formRef.value.clearValidate();
|
||
};
|
||
|
||
// === 上传相关 ===
|
||
const dummyRequest = ({ onSuccess }) => {
|
||
setTimeout(() => onSuccess('ok'), 0);
|
||
};
|
||
|
||
const beforeUploadImage = (file) => {
|
||
const isImage = file.type.startsWith('image/');
|
||
if (!isImage) {
|
||
message.error('只能上传图片!');
|
||
return false;
|
||
}
|
||
const isLt5M = file.size / 1024 / 1024 < 5;
|
||
if (!isLt5M) {
|
||
message.error('图片不能超过5MB!');
|
||
return false;
|
||
}
|
||
return true;
|
||
};
|
||
|
||
const beforeUploadFile = (file) => {
|
||
const allowedTypes = [
|
||
'application/pdf',
|
||
'application/msword',
|
||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
'application/vnd.ms-excel',
|
||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
'image/jpeg',
|
||
'image/png',
|
||
'image/jpg',
|
||
];
|
||
if (!allowedTypes.includes(file.type)) {
|
||
message.error('仅支持 PDF、Word、Excel、JPG、PNG 格式!');
|
||
return false;
|
||
}
|
||
if (file.size / 1024 / 1024 >= 10) {
|
||
message.error('文件不能超过10MB!');
|
||
return false;
|
||
}
|
||
return true;
|
||
};
|
||
|
||
const uploadFile = async (file) => {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
const res = await apis.file.upload(formData);
|
||
if (res?.success && res.data?.url) {
|
||
return res.data.url;
|
||
} else {
|
||
throw new Error(res?.message || '上传失败');
|
||
}
|
||
};
|
||
|
||
const resetForm = () => {
|
||
Object.assign(formData, {
|
||
name: '',
|
||
orgCode: '',
|
||
orgLv: '',
|
||
concatName: '',
|
||
concatPhone: '',
|
||
legalName: '',
|
||
legalPhone: '',
|
||
businessLicenseName: '',
|
||
registrationAuthorityName: '',
|
||
registrationAuthorityNo: '',
|
||
winOrg: '',
|
||
areaValue: [],
|
||
address: '',
|
||
remark: '',
|
||
status: '1',
|
||
businessLicenseFiles: [],
|
||
registrationCertificateFiles: [],
|
||
otherQualificationFiles: [],
|
||
});
|
||
};
|
||
|
||
const onAreaChange = (selectedCodes, selectedLabels) => {
|
||
formData.areaValue = [...selectedCodes];
|
||
formRef.value?.validateFields(['areaCodes']).catch(() => {});
|
||
};
|
||
|
||
defineExpose({ open, close: handleCancel });
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.upload-tip {
|
||
color: #8c8c8c;
|
||
font-size: 12px;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.mb-4 {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.ant-upload-text {
|
||
margin-top: 4px;
|
||
}
|
||
</style> |