人员库

This commit is contained in:
cwchen 2025-10-24 14:32:56 +08:00
parent e93b815634
commit b0e69564a7
7 changed files with 317 additions and 32 deletions

View File

@ -83,7 +83,7 @@ export default {
const item = foundItem ? { const item = foundItem ? {
fileUploadType: foundItem.value, fileUploadType: foundItem.value,
fields_json: foundItem.raw.remark, fields_json: foundItem.raw.remark,
suffix: 'mainDatabase' suffix: 'main_database'
} : null; } : null;
this.fileUploadList.push(item) this.fileUploadList.push(item)

View File

@ -132,7 +132,7 @@ export default {
const item = foundItem ? { const item = foundItem ? {
fileUploadType: foundItem.value, fileUploadType: foundItem.value,
fields_json: foundItem.raw.remark, fields_json: foundItem.raw.remark,
suffix: 'mainDatabase' suffix: 'main_database'
} : null; } : null;
this.fileUploadList.push(item) this.fileUploadList.push(item)

View File

@ -106,7 +106,7 @@ export default {
const item = foundItem ? { const item = foundItem ? {
fileUploadType: foundItem.value, fileUploadType: foundItem.value,
fields_json: foundItem.raw.remark, fields_json: foundItem.raw.remark,
suffix: 'mainDatabase' suffix: 'main_database'
} : null; } : null;
this.fileUploadList.push(item) this.fileUploadList.push(item)

View File

@ -221,7 +221,6 @@ export default {
this.$router.push({ this.$router.push({
name: 'EnterpriseKnowledge', name: 'EnterpriseKnowledge',
query: { query: {
type: encryptWithSM4('knowledge'),
enterpriseId: encryptWithSM4(enterprise.enterpriseId + '' ?? '0'), enterpriseId: encryptWithSM4(enterprise.enterpriseId + '' ?? '0'),
} }
}) })

View File

@ -1,6 +1,25 @@
<!-- 新增人员表单 --> <!-- 新增人员表单 -->
<template> <template>
<div class="app-container"> <div class="app-container" :class="{ 'no-pointer-events': showUploadAnimation || showSaveAnimation }">
<!-- 全局上传动画 -->
<div v-if="showUploadAnimation" class="global-upload-animation">
<div class="animation-mask"></div>
<div class="animation-content">
<div class="spinner"></div>
<div class="animation-text">{{ animationText }}</div>
<div class="animation-subtext">正在处理文件请稍候...</div>
</div>
</div>
<!-- 保存动画 -->
<div v-if="showSaveAnimation" class="global-upload-animation">
<div class="animation-mask"></div>
<div class="animation-content">
<div class="spinner"></div>
<div class="animation-text">数据上传中</div>
<div class="animation-subtext">请稍后正在保存数据...</div>
</div>
</div>
<div class="content-header"> <div class="content-header">
<el-button class="reset-btn" @click="handleClose()">返回</el-button> <el-button class="reset-btn" @click="handleClose()">返回</el-button>
<el-button class="search-btn" @click="handleSave()">保存</el-button> <el-button class="search-btn" @click="handleSave()">保存</el-button>
@ -24,7 +43,7 @@
</div> </div>
</template> </template>
<script> <script>
import { decryptWithSM4 } from '@/utils/sm' import { encryptWithSM4,decryptWithSM4 } from '@/utils/sm'
import BasicInfoPersonnel from './child/BasicInfo.vue' import BasicInfoPersonnel from './child/BasicInfo.vue'
import QualificationInfoPersonnel from './child/QualificationInfo.vue' import QualificationInfoPersonnel from './child/QualificationInfo.vue'
import OtherInfoPersonnel from './child/OtherInfo.vue' import OtherInfoPersonnel from './child/OtherInfo.vue'
@ -37,20 +56,31 @@ export default {
}, },
data() { data() {
return { return {
id: decryptWithSM4(this.$route.query.id), enterpriseId: decryptWithSM4(this.$route.query.enterpriseId),
personnelId: decryptWithSM4(this.$route.query.personnelId),
type: decryptWithSM4(this.$route.query.type), type: decryptWithSM4(this.$route.query.type),
// //
personnelPosition: { personnelPosition: {
label: '项目经理', label: '项目经理',
value: 'project_manager', value: 'project_manager',
qualification:'建造师证书、安全考核B证' qualification:'建造师证书、安全考核B证'
}, },
showUploadAnimation: false,
showSaveAnimation: false, //
uploadQueue: 0, //
animationText: '识别中',
isSaving: false, // loading
detailData: {} //
} }
}, },
methods: { methods: {
// //
handleClose() { handleClose() {
const obj = { path: "/enterpriseLibrary/enterprise" } const obj = { path: "/personnel/index",
query: {
enterpriseId: encryptWithSM4(this.enterpriseId || '0'),
}
}
this.$tab.closeOpenPage(obj) this.$tab.closeOpenPage(obj)
}, },
// //
@ -84,8 +114,44 @@ export default {
// console.error(':', error) // console.error(':', error)
this.$message.error(error.message || '请完善表单信息') this.$message.error(error.message || '请完善表单信息')
} }
},
//
handleStartUpload(data) {
this.animationText = data
this.uploadQueue++
this.showUploadAnimation = true
},
//
handleEndUpload(data) {
if (this.uploadQueue > 0) {
this.uploadQueue--
}
//
if (this.uploadQueue === 0) {
this.showUploadAnimation = false
}
} }
}, },
mounted() {
//
this.$bus.$on('startUpload', this.handleStartUpload)
//
this.$bus.$on('endUpload', this.handleEndUpload)
},
beforeDestroy() {
//
this.$bus.$off('startUpload', this.handleStartUpload)
this.$bus.$off('endUpload', this.handleEndUpload)
//
this.showUploadAnimation = false
this.showSaveAnimation = false
this.uploadQueue = 0
this.animationText = '识别中'
this.isSaving = false
},
computed: { computed: {
// //
isProjectChiefEngineer() { isProjectChiefEngineer() {
@ -102,6 +168,104 @@ export default {
background: linear-gradient(180deg, #F1F6FF 20%, #E5EFFF 100%); background: linear-gradient(180deg, #F1F6FF 20%, #E5EFFF 100%);
min-height: 100vh; min-height: 100vh;
overflow-y: auto; overflow-y: auto;
position: relative;
//
&.no-pointer-events {
pointer-events: none;
//
* {
pointer-events: none;
}
}
}
//
.global-upload-animation {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
//
pointer-events: auto;
.animation-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.6); //
backdrop-filter: blur(4px);
//
pointer-events: auto;
}
.animation-content {
position: relative;
z-index: 10000;
text-align: center;
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
padding: 40px 50px;
border-radius: 20px;
box-shadow:
0 10px 40px rgba(31, 114, 234, 0.15),
0 0 0 1px rgba(31, 114, 234, 0.1);
min-width: 280px;
animation: slideInUp 0.3s ease-out;
//
pointer-events: auto;
.spinner {
width: 60px;
height: 60px;
border: 4px solid #f3f7ff;
border-top: 4px solid #1F72EA;
border-radius: 50%;
animation: spin 1.2s linear infinite;
margin: 0 auto 20px;
position: relative;
&::after {
content: '';
position: absolute;
top: -4px;
left: -4px;
right: -4px;
bottom: -4px;
border: 4px solid transparent;
border-top: 4px solid #4A8BFF;
border-radius: 50%;
animation: spin 0.8s linear infinite reverse;
}
}
.animation-text {
font-size: 20px;
font-weight: 600;
color: #1F72EA;
margin-bottom: 8px;
background: linear-gradient(135deg, #1F72EA 0%, #4A8BFF 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.animation-subtext {
font-size: 14px;
color: #666;
opacity: 0.8;
}
}
} }
.content-body { .content-body {
@ -122,14 +286,12 @@ export default {
border-radius: 16px 16px 16px 16px; border-radius: 16px 16px 16px 16px;
min-height: 600px; min-height: 600px;
box-shadow: 0px 4px 20px 0px rgba(31, 35, 55, 0.1); box-shadow: 0px 4px 20px 0px rgba(31, 35, 55, 0.1);
// border: 1px solid #e8f4ff;
padding: 0; padding: 0;
margin-bottom: 20px; margin-bottom: 20px;
flex: 1; flex: 1;
min-width: 0; min-width: 0;
} }
.content-header { .content-header {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
@ -153,6 +315,12 @@ export default {
background: #4A8BFF; background: #4A8BFF;
box-shadow: 0px 6px 12px 0px rgba(51, 135, 255, 0.6); box-shadow: 0px 6px 12px 0px rgba(51, 135, 255, 0.6);
} }
// loading
&.is-loading {
opacity: 0.7;
pointer-events: none;
}
} }
.reset-btn { .reset-btn {
@ -172,4 +340,27 @@ export default {
box-shadow: 0px 6px 12px 0px rgba(76, 76, 76, 0.3); box-shadow: 0px 6px 12px 0px rgba(76, 76, 76, 0.3);
} }
} }
//
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px) scale(0.9);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
</style> </style>

View File

@ -6,50 +6,63 @@
</div> </div>
<el-form :model="form" :rules="rules" ref="basicInfoForm" label-width="110px" label-position="top"> <el-form :model="form" :rules="rules" ref="basicInfoForm" label-width="110px" label-position="top">
<el-form-item label="人员职位" prop="personnelPosition"> <el-form-item label="人员职位" prop="personnelPosition">
<el-select class="form-item" v-model="form.personnelPosition" placeholder="请选择人员职位" @change="handlePersonnelPositionChange"> <el-select class="form-item" v-model="form.personnelPosition" placeholder="请选择人员职位"
<el-option v-for="item in dict.type.personnel_position" :key="item.value" :label="item.label" :value="item.value"></el-option> @change="handlePersonnelPositionChange">
<el-option v-for="item in dict.type.personnel_position" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- 身份证人面像 --> <!-- 身份证人面像 -->
<el-form-item label="身份证人面像" prop="fileList"> <el-form-item label="身份证人面像" prop="fileList">
<UploadFile :fileList="form.fileList" /> <UploadFile :fileList="form.fileList" :fileUploadRule="fileUploadRule" @del-file="handleDelFile"
@file-change="handleFileChange" type="face_id_card_portrait" />
</el-form-item> </el-form-item>
<!-- 身份证国徽面 --> <!-- 身份证国徽面 -->
<el-form-item label="身份证国徽面" prop="fileList2"> <el-form-item label="身份证国徽面" prop="fileList2">
<UploadFile :fileList="form.fileList2" /> <UploadFile :fileList="form.fileList2" />
</el-form-item> </el-form-item>
<el-form-item label="人员姓名" prop="personnelName"> <el-form-item label="人员姓名" prop="personnelName">
<el-input v-model="form.personnelName" placeholder="自动提取"></el-input> <el-input v-model.trim="form.personnelName" placeholder="自动提取" clearable show-word-limit
maxlength="32"></el-input>
</el-form-item>
<el-form-item label="身份证号码" prop="personnelIdCard">
<el-input v-model.trim="form.personnelIdCard" placeholder="自动提取" clearable show-word-limit
maxlength="18"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="入职时间" prop="employmentDate"> <el-form-item label="入职时间" prop="employmentDate">
<el-date-picker class="form-item" v-model="form.employmentDate" placeholder="请选择入职时间" <el-date-picker class="form-item" v-model="form.employmentDate" placeholder="请选择入职时间"
value-format="yyyy-MM-dd" type="date"></el-date-picker> value-format="yyyy-MM-dd" type="date"></el-date-picker>
</el-form-item> </el-form-item>
<el-form-item label="从业年限" prop="employmentYears"> <el-form-item label="从业年限" prop="employmentYears">
<el-input min="0" max="60" type="number" v-model="form.employmentYears" placeholder="请输入从业年限"></el-input> <el-input min="0" max="60" type="number" v-model="form.employmentYears"
placeholder="请输入从业年限"></el-input>
</el-form-item> </el-form-item>
<!-- 学历证书 --> <!-- 学历证书 -->
<el-form-item label="学历证书" prop="fileList3"> <el-form-item label="学历证书" prop="fileList3">
<UploadFile :fileList="form.fileList3" uploadType="png、jpg、jpeg、pdf" /> <UploadFile :fileList="form.fileList3" uploadType="png、jpg、jpeg、pdf" />
</el-form-item> </el-form-item>
<el-form-item label="毕业院校" prop="graduateSchool"> <el-form-item label="毕业院校" prop="graduateSchool">
<el-input v-model="form.graduateSchool" placeholder="自动提取"></el-input> <el-input v-model.trim="form.graduateSchool" placeholder="自动提取" clearable show-word-limit
maxlength="64"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="毕业专业" prop="graduationMajor"> <el-form-item label="毕业专业" prop="graduationMajor">
<el-input v-model="form.graduationMajor" placeholder="自动提取"></el-input> <el-input v-model.trim="form.graduationMajor" placeholder="自动提取" clearable show-word-limit
maxlength="64"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="学历" prop="qualification"> <el-form-item label="学历" prop="qualification">
<el-input v-model="form.qualification" placeholder="自动提取"></el-input> <el-input v-model.trim="form.qualification" placeholder="自动提取" clearable show-word-limit
maxlength="32"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="毕业时间" prop="graduationDate"> <el-form-item label="毕业时间" prop="graduationDate">
<el-date-picker class="form-item" v-model="form.graduationDate" placeholder="自动提取" <el-date-picker class="form-item" v-model="form.graduationDate" placeholder="自动提取"
value-format="yyyy-MM-dd" type="date"></el-date-picker> value-format="yyyy-MM-dd" type="date"></el-date-picker>
</el-form-item> </el-form-item>
<el-form-item label="联系方式" prop="personnelPhone"> <el-form-item label="联系方式" prop="personnelPhone">
<el-input v-model="form.personnelPhone" placeholder="请输入联系方式"></el-input> <el-input v-model.trim="form.personnelPhone" placeholder="请输入联系方式" clearable show-word-limit
maxlength="11"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="劳动合同" prop="fileList4"> <el-form-item label="劳动合同" prop="fileList4">
<UploadFile :fileList="form.fileList4" uploadType="pdf、doc、docx" maxFileTips="100MB"/> <UploadFile :fileList="form.fileList4" uploadType="pdf、doc、docx" maxFileTips="100MB" />
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@ -59,27 +72,42 @@
import UploadFile from '@/views/common/UploadFile.vue' import UploadFile from '@/views/common/UploadFile.vue'
export default { export default {
name: 'BasicInfoPersonnel', name: 'BasicInfoPersonnel',
dicts: ['personnel_position'], dicts: ['personnel_position', 'identification_tag'],
components: { components: {
UploadFile UploadFile
}, },
props: {
detailData: {
type: Object,
default: () => { }
}
},
data() { data() {
return { return {
form: { form: {
personnelPosition: '', personnelPosition: '',
personnelName: '', personnelName: '',
employmentDate: '', employmentDate: '',
employmentYears:'', employmentYears: '',
graduateSchool: '', graduateSchool: '',
graduationMajor: '', graduationMajor: '',
qualification:'', qualification: '',
graduationDate:'', graduationDate: '',
personnelPhone:'', personnelPhone: '',
personnelIdCard: '',
fileList: [], fileList: [],
fileList2: [], fileList2: [],
fileList3: [], fileList3: [],
fileList4: [] fileList4: []
}, },
// OCR
ocrRuleList: ['face_id_card_portrait'],
fileUploadList: [],
// chat_res
ocrResultParams: {
"姓名": "personnelName",
"公民身份号码": "personnelIdCard",
},
rules: { rules: {
personnelPosition: [ personnelPosition: [
{ required: true, message: '请选择人员职位', trigger: 'blur' } { required: true, message: '请选择人员职位', trigger: 'blur' }
@ -87,6 +115,9 @@ export default {
personnelName: [ personnelName: [
{ required: true, message: '请输入人员姓名', trigger: 'blur' } { required: true, message: '请输入人员姓名', trigger: 'blur' }
], ],
personnelIdCard: [
{ required: true, message: '请输入身份证号码', trigger: 'blur' }
],
employmentDate: [ employmentDate: [
{ required: true, message: '请选择入职时间', trigger: 'change' } { required: true, message: '请选择入职时间', trigger: 'change' }
], ],
@ -138,15 +169,73 @@ export default {
}) })
}, },
// //
handlePersonnelPositionChange(){ handlePersonnelPositionChange() {
const data = this.dict.type.personnel_position.find(item => item.value === this.form.personnelPosition); const data = this.dict.type.personnel_position.find(item => item.value === this.form.personnelPosition);
const obj = { const obj = {
label: data.label, label: data.label,
value: data.value, value: data.value,
qualification:data.raw.remark qualification: data.raw.remark
} }
this.$emit('handlePersonnelPosition', obj); this.$emit('handlePersonnelPosition', obj);
} },
// ocr
ocrRule(type) {
const foundItem = this.dict.type.identification_tag.find(item => item.value === type);
const item = foundItem ? {
fileUploadType: foundItem.value,
fields_json: foundItem.raw.remark,
suffix: 'personnel_database'
} : null;
this.fileUploadList.push(item)
},
// ocr
addOcrRule() {
this.ocrRuleList.forEach(item => {
this.ocrRule(item)
})
},
//
handleFileChange(file, type) {
if (file instanceof Array && file.length > 0 && file[0].response) {
const response = file[0].response;
if (response.ocrResult && response.ocrResult.status_code === 200) {
// ocr
const chat_res = response.ocrResult.data?.chat_res;
if (chat_res && typeof chat_res === 'object') {
// chat_res
Object.keys(chat_res).forEach(key => {
const formField = this.ocrResultParams[key];
if (formField && chat_res[key]) {
this.form[formField] = chat_res[key];
}
});
}
}
if (type == 'face_id_card_portrait') {
this.form.fileList = file;
this.$refs.basicInfoForm.validateField(['personnelName', 'personnelIdCard']);
}
}
},
//
handleDelFile(file) {
console.log(file);
this.form.delFileList.push(file.response.fileRes.uploadPath || file.filePath);
},
},
watch: {
//
'dict.type.identification_tag': {
handler(newVal) {
if (newVal && newVal.length > 0) {
this.addOcrRule();
}
},
immediate: true //
},
}, },
} }
</script> </script>
@ -162,6 +251,7 @@ export default {
font-size: 20px; font-size: 20px;
} }
} }
.form-item { .form-item {
width: 100%; width: 100%;
} }

View File

@ -130,7 +130,12 @@ export default {
// //
handleBack() { handleBack() {
const obj = { path: "/enterpriseKnowledge/index" } const obj = {
path: "/enterpriseKnowledge/index",
query: {
enterpriseId: encryptWithSM4(this.enterpriseId || '0'),
}
}
this.$tab.closeOpenPage(obj) this.$tab.closeOpenPage(obj)
}, },