421 lines
12 KiB
Vue
421 lines
12 KiB
Vue
<template>
|
||
<div class="qualification-form">
|
||
<el-form
|
||
:model="formData"
|
||
:rules="rules"
|
||
ref="qualificationForm"
|
||
label-width="110px"
|
||
label-position="top"
|
||
class="form-wrapper"
|
||
>
|
||
<!-- 资质证书上传 -->
|
||
<el-form-item label="资质证书附件" prop="files">
|
||
<UploadFile
|
||
ref="uploadFileComp"
|
||
:fileList="formData.files"
|
||
:fileUploadRule="qualificationUploadRule"
|
||
@file-change="handleFileChange"
|
||
@del-file="handleDelFile"
|
||
:uploadType="uploadType"
|
||
:maxFileTips="maxFileTips"
|
||
type="qualification_certificate"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<!-- 证书名称 -->
|
||
<el-form-item label="证书名称" prop="certificateName">
|
||
<el-input
|
||
v-model="formData.certificateName"
|
||
placeholder="文件上传后自动提取"
|
||
class="form-control"
|
||
maxlength="50"
|
||
></el-input>
|
||
</el-form-item>
|
||
|
||
<!-- 证书编号 -->
|
||
<el-form-item label="证书编号" prop="certificateCode">
|
||
<el-input
|
||
v-model="formData.certificateCode"
|
||
placeholder="文件上传后自动提取"
|
||
class="form-control"
|
||
maxlength="32"
|
||
></el-input>
|
||
</el-form-item>
|
||
|
||
<!-- 发证日期 -->
|
||
<el-form-item label="发证日期" prop="certificateDate">
|
||
<el-date-picker
|
||
v-model="formData.certificateDate"
|
||
type="date"
|
||
placeholder="文件上传后自动提取"
|
||
value-format="yyyy-MM-dd"
|
||
class="form-control"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<!-- 证书有效截止日期 -->
|
||
<el-form-item label="证书有效截止日期" prop="certificateEndDate">
|
||
<el-date-picker
|
||
v-model="formData.certificateEndDate"
|
||
type="date"
|
||
placeholder="文件上传后自动提取"
|
||
value-format="yyyy-MM-dd"
|
||
class="form-control"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<!-- 发证机构 -->
|
||
<el-form-item label="发证机构" prop="certificateInstitution">
|
||
<el-input
|
||
v-model="formData.certificateInstitution"
|
||
placeholder="文件上传后自动提取"
|
||
class="form-control"
|
||
maxlength="64"
|
||
></el-input>
|
||
</el-form-item>
|
||
|
||
<!-- 资质类型 -->
|
||
<el-form-item label="资质类型" prop="qualificationType">
|
||
<el-select
|
||
v-model="formData.qualificationType"
|
||
placeholder="请选择资质类型"
|
||
clearable
|
||
class="form-control"
|
||
>
|
||
<el-option
|
||
v-for="item in qualificationTypeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import UploadFile from '@/views/common/UploadFile.vue'
|
||
|
||
// 工具函数:安全获取数组
|
||
const safeGetArray = (value) => Array.isArray(value) ? value : []
|
||
|
||
export default {
|
||
name: 'QualificationFormChild',
|
||
components: { UploadFile },
|
||
dicts: ['identification_tag'],
|
||
data() {
|
||
return {
|
||
uploadType: 'jpg、png、pdf',
|
||
maxFileTips: '100MB',
|
||
ocrRuleList: ['qualification_certificate'], // 资质证书OCR识别规则
|
||
fileUploadList: [],
|
||
// OCR识别结果映射关系
|
||
ocrResultParams: {
|
||
"证书名称": "certificateName",
|
||
"证书编号": "certificateCode",
|
||
"发证日期": "certificateDate",
|
||
"证书有效截止日期": "certificateEndDate",
|
||
"发证机构": "certificateInstitution"
|
||
},
|
||
formData: {
|
||
files: [], // 证书文件列表
|
||
certificateName: '',
|
||
certificateCode: '',
|
||
certificateDate: '',
|
||
certificateEndDate: '',
|
||
certificateInstitution: '',
|
||
qualificationType: '',
|
||
delFileList: [] // 被删除的文件路径列表(关键:记录替换/删除的文件)
|
||
},
|
||
qualificationTypeOptions: [],
|
||
rules: {
|
||
files: [
|
||
{ required: true, message: '请上传资质证书附件', trigger: 'change' }
|
||
],
|
||
certificateName: [
|
||
{ required: true, message: '请输入证书名称', trigger: 'blur' }
|
||
],
|
||
certificateCode: [
|
||
{ required: true, message: '请输入证书编号', trigger: 'blur' }
|
||
],
|
||
certificateDate: [
|
||
{ required: true, message: '请选择发证日期', trigger: 'change' }
|
||
],
|
||
certificateEndDate: [
|
||
{ required: true, message: '请选择有效截止日期', trigger: 'change' }
|
||
],
|
||
qualificationType: [
|
||
{ required: true, message: '请选择资质类型', trigger: 'change' }
|
||
]
|
||
}
|
||
};
|
||
},
|
||
computed: {
|
||
qualificationUploadRule() {
|
||
return this.fileUploadList[0] || {};
|
||
}
|
||
},
|
||
watch: {
|
||
'dict.type.identification_tag': {
|
||
handler(newVal) {
|
||
if (newVal && newVal.length > 0) {
|
||
this.addOcrRule();
|
||
}
|
||
},
|
||
immediate: true
|
||
}
|
||
},
|
||
methods: {
|
||
// 加载资质类型字典
|
||
loadQualificationTypeDict() {
|
||
this.getDicts('qualification_type').then(response => {
|
||
this.qualificationTypeOptions = response.data.map(item => ({
|
||
label: item.dictLabel,
|
||
value: item.dictValue
|
||
}));
|
||
}).catch(error => {
|
||
console.error('加载资质类型字典失败:', error);
|
||
});
|
||
},
|
||
|
||
// 接收父组件数据并回显(优化:确保文件路径完整)
|
||
setFormData(data) {
|
||
// 1. 过滤“资质证书类型”的文件
|
||
const qualificationFiles = safeGetArray(data.fileList)
|
||
.filter(file => file && file.businessType === 'qualification_certificate')
|
||
.map(file => ({
|
||
...file,
|
||
uid: file.uid || Date.now() + Math.random().toString(36).substr(2, 9), // 唯一ID
|
||
name: file.fileName || file.name || '未命名文件',
|
||
lsFilePath: file.lsFilePath || file.fileUrl || '', // 预览路径
|
||
filePath: file.filePath || file.uploadPath || '', // 存储路径(用于删除)
|
||
status: 'success', // 标记为已上传
|
||
// 补充上传组件所需的响应结构(删除时需要)
|
||
response: {
|
||
fileRes: {
|
||
uploadPath: file.filePath || file.uploadPath || '',
|
||
filePath: file.filePath || file.uploadPath || ''
|
||
}
|
||
}
|
||
}));
|
||
|
||
// 2. 赋值表单数据
|
||
this.formData = {
|
||
...data,
|
||
files: qualificationFiles,
|
||
delFileList: [] // 重置删除列表
|
||
};
|
||
|
||
// 3. 触发上传组件刷新
|
||
this.$nextTick(() => {
|
||
if (this.$refs.uploadFileComp?.updatePreview) {
|
||
this.$refs.uploadFileComp.updatePreview(qualificationFiles);
|
||
}
|
||
});
|
||
},
|
||
|
||
// 处理文件变更(新增/替换文件时触发)
|
||
handleFileChange(files) {
|
||
// 标记文件业务类型
|
||
const markedFiles = files.map(file => ({
|
||
...file,
|
||
businessType: 'qualification_certificate'
|
||
}));
|
||
this.formData.files = markedFiles;
|
||
|
||
// 处理替换场景:当上传新文件时,旧文件会被自动删除,需记录旧文件路径
|
||
this.handleReplaceFile(markedFiles);
|
||
|
||
// 触发OCR识别
|
||
this.handleOcrResult(markedFiles);
|
||
},
|
||
|
||
// 关键:处理文件替换场景(新文件上传时,旧文件需加入删除列表)
|
||
handleReplaceFile(newFiles) {
|
||
// 新文件的唯一标识(uid)集合
|
||
const newUids = newFiles.map(file => file.uid);
|
||
// 旧文件中不在新文件列表的,即为被替换的文件
|
||
const replacedFiles = this.formData.files.filter(oldFile =>
|
||
!newUids.includes(oldFile.uid)
|
||
);
|
||
|
||
// 将被替换的文件路径加入删除列表(去重)
|
||
replacedFiles.forEach(file => {
|
||
const filePath = file.response?.fileRes?.uploadPath || file.filePath;
|
||
if (filePath && !this.formData.delFileList.includes(filePath)) {
|
||
this.formData.delFileList.push(filePath);
|
||
}
|
||
});
|
||
},
|
||
|
||
// 处理文件删除(用户主动删除时触发)
|
||
handleDelFile(file) {
|
||
const filePath = file.response?.fileRes?.uploadPath || file.filePath;
|
||
if (filePath && !this.formData.delFileList.includes(filePath)) {
|
||
this.formData.delFileList.push(filePath);
|
||
}
|
||
// 删除文件后清空关联字段
|
||
if (this.formData.files.length === 0) {
|
||
this.formData.certificateName = '';
|
||
this.formData.certificateCode = '';
|
||
this.formData.certificateDate = '';
|
||
this.formData.certificateEndDate = '';
|
||
this.formData.certificateInstitution = '';
|
||
}
|
||
},
|
||
|
||
// 表单验证
|
||
validate() {
|
||
return new Promise((resolve, reject) => {
|
||
if (!this.$refs.qualificationForm) {
|
||
reject(new Error('表单实例未加载完成'));
|
||
return;
|
||
}
|
||
|
||
this.$refs.qualificationForm.validate((valid) => {
|
||
if (valid) {
|
||
resolve(this.formData); // 返回包含delFileList的完整数据
|
||
} else {
|
||
reject(new Error('资质证书信息填写不完整'));
|
||
}
|
||
});
|
||
});
|
||
},
|
||
|
||
// 重置表单(优化:确保删除列表被清空)
|
||
resetForm() {
|
||
this.$nextTick(async () => {
|
||
// 清空上传组件的文件
|
||
if (this.$refs.uploadFileComp) {
|
||
this.$refs.uploadFileComp.clearFiles();
|
||
}
|
||
|
||
// 重置表单字段
|
||
if (this.$refs.qualificationForm) {
|
||
this.$refs.qualificationForm.resetFields();
|
||
}
|
||
|
||
// 清空删除列表和文件列表
|
||
this.formData = {
|
||
...this.formData,
|
||
files: [],
|
||
delFileList: [],
|
||
certificateName: '',
|
||
certificateCode: '',
|
||
certificateDate: '',
|
||
certificateEndDate: '',
|
||
certificateInstitution: '',
|
||
qualificationType: ''
|
||
};
|
||
});
|
||
},
|
||
|
||
// 初始化OCR识别规则
|
||
addOcrRule() {
|
||
this.ocrRuleList.forEach(item => this.ocrRule(item));
|
||
},
|
||
|
||
// 获取OCR识别规则
|
||
ocrRule(type) {
|
||
const foundItem = this.dict.type.identification_tag?.find(item => item.value === type);
|
||
if (!foundItem) {
|
||
this.$message.warning(`未找到${type}的识别规则配置`);
|
||
return;
|
||
}
|
||
|
||
this.fileUploadList.push({
|
||
fileUploadType: foundItem.value,
|
||
fields_json: foundItem.raw?.remark,
|
||
suffix: 'mainDatabase'
|
||
});
|
||
},
|
||
|
||
// 处理OCR识别结果
|
||
handleOcrResult(files) {
|
||
if (!files || files.length === 0) return;
|
||
|
||
this.$bus.$emit('startUpload', '正在识别证书信息');
|
||
|
||
try {
|
||
const firstFile = files[0];
|
||
if (firstFile.response?.ocrResult) {
|
||
const ocrResult = firstFile.response.ocrResult;
|
||
if (ocrResult.status_code === 200) {
|
||
const chat_res = ocrResult.data?.chat_res;
|
||
if (chat_res && typeof chat_res === 'object') {
|
||
// 映射识别结果到表单字段
|
||
Object.keys(chat_res).forEach(key => {
|
||
const formField = this.ocrResultParams[key];
|
||
if (formField && chat_res[key]) {
|
||
this.formData[formField] = chat_res[key];
|
||
}
|
||
});
|
||
this.$message.success('OCR识别成功,已自动填充证书信息');
|
||
// 触发表单验证
|
||
Object.keys(this.ocrResultParams).forEach(key => {
|
||
const formField = this.ocrResultParams[key];
|
||
this.$refs.qualificationForm.validateField(formField);
|
||
});
|
||
}
|
||
} else {
|
||
this.$message.error(`识别失败: ${ocrResult.status_msg || '未知错误'}`);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
this.$message.error(`处理识别结果失败: ${error.message}`);
|
||
} finally {
|
||
this.$bus.$emit('endUpload');
|
||
}
|
||
}
|
||
},
|
||
created() {
|
||
this.loadQualificationTypeDict();
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.basic-info-title {
|
||
display: flex;
|
||
align-items: center;
|
||
margin: 0 0 20px 0;
|
||
span {
|
||
margin: 0 5px;
|
||
font-size: 20px;
|
||
color: #333;
|
||
}
|
||
img {
|
||
width: 24px;
|
||
height: 24px;
|
||
object-fit: contain;
|
||
}
|
||
}
|
||
|
||
.qualification-form {
|
||
padding: 0;
|
||
}
|
||
|
||
.el-form {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.form-control {
|
||
width: 100%;
|
||
}
|
||
|
||
.el-form-item {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
::v-deep .el-form-item__label {
|
||
color: #4e5969;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-wrapper {
|
||
padding: 24px;
|
||
background: #fff;
|
||
}
|
||
</style>
|