487 lines
16 KiB
Vue
487 lines
16 KiB
Vue
<!-- 新增人员表单 -->
|
|
<template>
|
|
<div class="app-container" :class="{ 'no-pointer-events': showUploadAnimation || showSaveAnimation }">
|
|
<!-- 全局上传动画 -->
|
|
<GlobalUploadAnimation v-if="showUploadAnimation" :text="animationText" subtext="正在处理文件,请稍候..." />
|
|
|
|
<!-- 保存动画 -->
|
|
<GlobalUploadAnimation v-if="showSaveAnimation" text="数据上传中" subtext="请稍后,正在保存数据..." />
|
|
|
|
<div class="content-header">
|
|
<el-button class="reset-btn" @click="handleClose">返回</el-button>
|
|
<el-button class="search-btn" :loading="isSaving" @click="handleSave">
|
|
保存
|
|
</el-button>
|
|
</div>
|
|
|
|
<div class="content-body">
|
|
<el-row :gutter="24" class="content-row">
|
|
<!-- 基本信息 -->
|
|
<el-col :span="6" class="pane-left">
|
|
<BasicInfoPersonnel ref="basicInfoPersonnel" @handlePersonnelPosition="handlePersonnelPosition"
|
|
:detailData="detailData" />
|
|
</el-col>
|
|
|
|
<!-- 资质信息 -->
|
|
<el-col :span="6" class="pane-center" v-show="!isProjectChiefEngineer">
|
|
<QualificationInfoPersonnel ref="qualificationInfoPersonnel" :personnelPosition="personnelPosition"
|
|
:detailData="detailData" />
|
|
</el-col>
|
|
|
|
<!-- 其他信息 -->
|
|
<el-col :span="6" class="pane-right">
|
|
<OtherInfoPersonnel ref="otherInfoPersonnel" :detailData="detailData"
|
|
:personnelPosition="personnelPosition" />
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { encryptWithSM4, decryptWithSM4 } from '@/utils/sm'
|
|
import BasicInfoPersonnel from './child/BasicInfo.vue'
|
|
import QualificationInfoPersonnel from './child/QualificationInfo.vue'
|
|
import OtherInfoPersonnel from './child/OtherInfo.vue'
|
|
import GlobalUploadAnimation from '@/views/common/GlobalUploadAnimation.vue'
|
|
import { addDataAPI, editDataAPI, getDetailDataAPI } from '@/api/enterpriseLibrary/personnel/personnel'
|
|
|
|
// 常量定义
|
|
const PERSONNEL_POSITION_DEFAULT = {
|
|
label: '项目经理',
|
|
value: 'project_manager',
|
|
qualification: '建造师证书、安全考核B证'
|
|
}
|
|
|
|
const CERTIFICATE_TYPES = {
|
|
CONSTRUCTOR: 'constructor_certificate',
|
|
TITLE: 'professional_title_certificate'
|
|
}
|
|
|
|
// 空对象常量,避免重复创建
|
|
const EMPTY_OBJECT = Object.freeze({})
|
|
|
|
export default {
|
|
name: 'PersonnelForm',
|
|
components: {
|
|
BasicInfoPersonnel,
|
|
QualificationInfoPersonnel,
|
|
OtherInfoPersonnel,
|
|
GlobalUploadAnimation
|
|
},
|
|
dicts: ['personnel_position'],
|
|
data() {
|
|
return {
|
|
enterpriseId: decryptWithSM4(this.$route.query.enterpriseId),
|
|
personnelId: decryptWithSM4(this.$route.query.personnelId),
|
|
type: decryptWithSM4(this.$route.query.type),
|
|
personnelPosition: { ...PERSONNEL_POSITION_DEFAULT },
|
|
showUploadAnimation: false,
|
|
showSaveAnimation: false,
|
|
uploadQueue: 0,
|
|
animationText: '识别中',
|
|
isSaving: false,
|
|
detailData: {}
|
|
}
|
|
},
|
|
computed: {
|
|
isProjectChiefEngineer() {
|
|
return this.personnelPosition.value === 'project_chief_engineer'
|
|
},
|
|
isEditMode() {
|
|
return this.type === 'edit'
|
|
},
|
|
// 计算属性安全获取ref
|
|
qualificationInfoRef() {
|
|
return this.$refs.qualificationInfoPersonnel
|
|
}
|
|
},
|
|
watch: {
|
|
'dict.type.personnel_position': {
|
|
handler(newVal) {
|
|
if (newVal && newVal.length > 0) {
|
|
this.getDetail();
|
|
}
|
|
},
|
|
}
|
|
},
|
|
created() {
|
|
|
|
},
|
|
mounted() {
|
|
this.$bus.$on('startUpload', this.handleStartUpload)
|
|
this.$bus.$on('endUpload', this.handleEndUpload)
|
|
},
|
|
beforeDestroy() {
|
|
this.cleanup()
|
|
},
|
|
methods: {
|
|
// 清理资源
|
|
cleanup() {
|
|
this.$bus.$off('startUpload', this.handleStartUpload)
|
|
this.$bus.$off('endUpload', this.handleEndUpload)
|
|
this.resetAnimationState()
|
|
},
|
|
|
|
// 重置动画状态
|
|
resetAnimationState() {
|
|
this.showUploadAnimation = false
|
|
this.showSaveAnimation = false
|
|
this.uploadQueue = 0
|
|
this.animationText = '识别中'
|
|
this.isSaving = false
|
|
},
|
|
|
|
// 返回
|
|
handleClose() {
|
|
const obj = {
|
|
path: "/personnel/index",
|
|
query: {
|
|
enterpriseId: encryptWithSM4(this.enterpriseId || '0'),
|
|
}
|
|
}
|
|
this.$tab.closeOpenPage(obj)
|
|
},
|
|
|
|
// 获取详情
|
|
async getDetail() {
|
|
if (!this.isEditMode) return
|
|
try {
|
|
const res = await getDetailDataAPI({
|
|
personnelId: this.personnelId,
|
|
enterpriseId: this.enterpriseId
|
|
})
|
|
console.log('res', res);
|
|
|
|
this.detailData = res.data
|
|
this.getPersonnelPosition();
|
|
} catch (error) {
|
|
console.error('获取详情失败:', error)
|
|
// this.$message.error('获取详情失败')
|
|
}
|
|
},
|
|
// 获取人员职位-回显
|
|
getPersonnelPosition() {
|
|
const obj = this.dict.type.personnel_position.find(item => item.value === this.detailData.enterprisePersonnel.personnelPosition)
|
|
this.personnelPosition = {
|
|
label: obj.label,
|
|
value: obj.value,
|
|
qualification: obj.raw.remark
|
|
}
|
|
},
|
|
|
|
// 人员职位
|
|
handlePersonnelPosition(data) {
|
|
this.personnelPosition = data
|
|
},
|
|
|
|
// 获取资源文件
|
|
getResourceFilePo(fileList) {
|
|
if (!fileList || !Array.isArray(fileList)) return null
|
|
|
|
const resourceFiles = fileList
|
|
.map(file => file?.response?.fileRes ? { ...file.response.fileRes } : null)
|
|
.filter(Boolean)
|
|
|
|
return resourceFiles.length > 0 ? resourceFiles[0] : null
|
|
},
|
|
|
|
// 获取建造师证书数据
|
|
getBuilderCertificateData(raw) {
|
|
if (!raw) return null
|
|
|
|
return {
|
|
personnelCertificateId: this.isEditMode && this.qualificationInfoRef ?
|
|
this.qualificationInfoRef.personnelCertificateId?.[0] : null,
|
|
professionalType: raw.professionalType,
|
|
certificateCode: raw.certificateCode,
|
|
certificateLevel: raw.certificateLevel,
|
|
certificateValidityPeriod: this.formatDateRange(raw.certificateValidityPeriod),
|
|
useValidityPeriod: this.formatDateRange(raw.useValidityPeriod),
|
|
certificateType: CERTIFICATE_TYPES.CONSTRUCTOR
|
|
}
|
|
},
|
|
|
|
// 获取证书类型
|
|
getCertificateType(fileList) {
|
|
if (!fileList || !Array.isArray(fileList) || fileList.length === 0) return ''
|
|
|
|
const firstFile = fileList[0]
|
|
console.log(firstFile);
|
|
|
|
return firstFile?.response?.fileRes?.businessType || firstFile?.businessType || ''
|
|
},
|
|
|
|
// 获取 B证、C证、其他证书数据
|
|
getOtherCertificateData(raw) {
|
|
if (!raw) return null
|
|
|
|
return {
|
|
personnelCertificateId: this.isEditMode && this.qualificationInfoRef ?
|
|
this.qualificationInfoRef.personnelCertificateId?.[1] : null,
|
|
certificateCode: raw.certificateCode2,
|
|
certificateValidityPeriod: this.formatDateRange(raw.certificateValidityPeriod2),
|
|
registerProfessional: raw.registerProfessional,
|
|
certificateType: this.getCertificateType(raw.fileList2)
|
|
}
|
|
},
|
|
|
|
// 获取职称证书数据
|
|
getTitleData(raw) {
|
|
if (!raw) return null
|
|
|
|
return {
|
|
personnelCertificateId: this.isEditMode ? this.$refs.otherInfoPersonnel.personnelCertificateId?.[0] : null,
|
|
titleName: raw.titleName,
|
|
professionalName: raw.professionalName,
|
|
certificateCode: raw.certificateCode,
|
|
certificateType: CERTIFICATE_TYPES.TITLE
|
|
}
|
|
},
|
|
|
|
// 格式化日期范围
|
|
formatDateRange(dateArray) {
|
|
return dateArray && Array.isArray(dateArray) && dateArray.length === 2 ? dateArray.join(' - ') : ''
|
|
},
|
|
|
|
// 安全获取数组
|
|
safeGetArray(value) {
|
|
return Array.isArray(value) ? value : []
|
|
},
|
|
|
|
// 处理证书文件数据
|
|
processCertificateFiles(qualificationData = EMPTY_OBJECT, otherData = EMPTY_OBJECT) {
|
|
const personnelCertificateFiles = []
|
|
|
|
// 建造师证书 - 安全访问qualificationData属性
|
|
const qualificationFileList = this.safeGetArray(qualificationData.fileList)
|
|
if (qualificationFileList.length > 0) {
|
|
const builderCert = {
|
|
personnelCertificate: this.getBuilderCertificateData(qualificationData),
|
|
resourceFilePo: this.getResourceFilePo(qualificationFileList),
|
|
}
|
|
// 确保数据有效才添加
|
|
if (builderCert.personnelCertificate) {
|
|
personnelCertificateFiles.push(builderCert)
|
|
}
|
|
}
|
|
|
|
// B证、C证、其他证书 - 安全访问qualificationData属性
|
|
const qualificationFileList2 = this.safeGetArray(qualificationData.fileList2)
|
|
if (qualificationFileList2.length > 0) {
|
|
const otherCert = {
|
|
personnelCertificate: this.getOtherCertificateData(qualificationData),
|
|
resourceFilePo: this.getResourceFilePo(qualificationFileList2),
|
|
}
|
|
if (otherCert.personnelCertificate) {
|
|
personnelCertificateFiles.push(otherCert)
|
|
}
|
|
}
|
|
|
|
// 职称证书 - 安全访问otherData属性
|
|
const otherFileList = this.safeGetArray(otherData.fileList)
|
|
if (otherFileList.length > 0) {
|
|
const titleCert = {
|
|
personnelCertificate: this.getTitleData(otherData),
|
|
resourceFilePo: this.getResourceFilePo(otherFileList),
|
|
}
|
|
if (titleCert.personnelCertificate) {
|
|
personnelCertificateFiles.push(titleCert)
|
|
}
|
|
}
|
|
|
|
return personnelCertificateFiles
|
|
},
|
|
|
|
// 组装表单数据
|
|
assembleFormData(basicInfoData, qualificationData = EMPTY_OBJECT, otherData = EMPTY_OBJECT) {
|
|
console.log(qualificationData);
|
|
console.log(otherData);
|
|
|
|
// 安全合并所有文件列表
|
|
const allFiles = [
|
|
...this.safeGetArray(basicInfoData.fileList),
|
|
...this.safeGetArray(basicInfoData.fileList2),
|
|
...this.safeGetArray(basicInfoData.fileList3),
|
|
...this.safeGetArray(basicInfoData.fileList4),
|
|
].map(file => JSON.parse(JSON.stringify(file)))
|
|
|
|
const formData = {
|
|
...basicInfoData,
|
|
files: allFiles.map(file =>
|
|
file?.response?.fileRes ? { ...file.response.fileRes } : null
|
|
).filter(Boolean),
|
|
delFiles: this.safeGetArray(basicInfoData.delFileList),
|
|
delCertificateFiles: [
|
|
...this.safeGetArray(qualificationData.delFileList),
|
|
...this.safeGetArray(otherData.delFileList)
|
|
],
|
|
enterpriseId: this.enterpriseId,
|
|
personnelCertificateFiles: this.processCertificateFiles(qualificationData, otherData),
|
|
personnelIntroduction: otherData.personnelIntroduction || ''
|
|
}
|
|
|
|
// 删除临时属性
|
|
const tempProps = ['fileList', 'fileList2', 'fileList3', 'fileList4', 'delFileList', 'allFiles']
|
|
tempProps.forEach(prop => delete formData[prop])
|
|
|
|
return formData
|
|
},
|
|
|
|
// 保存
|
|
async handleSave() {
|
|
if (this.isSaving) return
|
|
|
|
this.isSaving = true
|
|
this.showSaveAnimation = true
|
|
|
|
try {
|
|
// 并行校验所有表单,安全处理可能不存在的组件
|
|
const [basicInfoData, qualificationData, otherData] = await Promise.all([
|
|
this.$refs.basicInfoPersonnel.validate(),
|
|
this.isProjectChiefEngineer ? Promise.resolve(EMPTY_OBJECT) : this.qualificationInfoRef?.validate?.() || Promise.resolve(EMPTY_OBJECT),
|
|
this.$refs.otherInfoPersonnel.validate()
|
|
])
|
|
|
|
// 组装完整数据
|
|
const formData = this.assembleFormData(basicInfoData, qualificationData, otherData)
|
|
|
|
console.log('所有表单校验通过,完整数据:', formData)
|
|
|
|
// 保存请求
|
|
const res = await this.savePersonnel(formData)
|
|
if (res.code === 200) {
|
|
this.$message.success('保存成功')
|
|
this.handleClose()
|
|
}
|
|
} catch (error) {
|
|
if(error instanceof Error && error.message.includes('未填写完整')){
|
|
this.$message.error(error.message)
|
|
}
|
|
} finally {
|
|
this.isSaving = false
|
|
this.showSaveAnimation = false
|
|
}
|
|
},
|
|
|
|
// 保存接口
|
|
async savePersonnel(formData) {
|
|
const apiParams = this.isEditMode
|
|
? { ...formData, personnelId: this.personnelId }
|
|
: formData
|
|
|
|
const apiMethod = this.isEditMode ? editDataAPI : addDataAPI
|
|
|
|
return await apiMethod(apiParams)
|
|
},
|
|
|
|
// 开始上传
|
|
handleStartUpload(text) {
|
|
this.animationText = text
|
|
this.uploadQueue++
|
|
this.showUploadAnimation = true
|
|
},
|
|
|
|
// 结束上传
|
|
handleEndUpload() {
|
|
if (this.uploadQueue > 0) {
|
|
this.uploadQueue--
|
|
}
|
|
|
|
if (this.uploadQueue === 0) {
|
|
this.showUploadAnimation = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.app-container {
|
|
padding: 24px;
|
|
//background: linear-gradient(180deg, #F1F6FF 20%, #E5EFFF 100%);
|
|
min-height: 100vh;
|
|
overflow-y: auto;
|
|
position: relative;
|
|
|
|
&.no-pointer-events {
|
|
pointer-events: none;
|
|
|
|
* {
|
|
pointer-events: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
.content-body {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.content-row {
|
|
margin: 0;
|
|
display: flex;
|
|
flex-wrap: nowrap;
|
|
gap: 16px;
|
|
}
|
|
|
|
.pane-left,
|
|
.pane-center,
|
|
.pane-right {
|
|
background: #fff;
|
|
border-radius: 16px;
|
|
min-height: 600px;
|
|
box-shadow: 0px 4px 20px 0px rgba(31, 35, 55, 0.1);
|
|
padding: 0;
|
|
margin-bottom: 20px;
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.content-header {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
gap: 12px;
|
|
}
|
|
|
|
.search-btn {
|
|
width: 98px;
|
|
height: 36px;
|
|
background: #1F72EA;
|
|
box-shadow: 0px 4px 8px 0px rgba(51, 135, 255, 0.5);
|
|
border-radius: 4px;
|
|
border: none;
|
|
color: #fff;
|
|
font-size: 14px;
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
background: #4A8BFF;
|
|
box-shadow: 0px 6px 12px 0px rgba(51, 135, 255, 0.6);
|
|
}
|
|
|
|
&.is-loading {
|
|
opacity: 0.7;
|
|
pointer-events: none;
|
|
}
|
|
}
|
|
|
|
.reset-btn {
|
|
width: 98px;
|
|
height: 36px;
|
|
background: #FFFFFF;
|
|
box-shadow: 0px 4px 8px 0px rgba(76, 76, 76, 0.2);
|
|
border-radius: 4px;
|
|
border: none;
|
|
color: #666;
|
|
font-size: 14px;
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
background: #f5f5f5;
|
|
color: #409EFF;
|
|
box-shadow: 0px 6px 12px 0px rgba(76, 76, 76, 0.3);
|
|
}
|
|
}
|
|
</style> |