增加单位功能

This commit is contained in:
hero920103 committed 2026-09-14 00:09:58 +08:00
1 parent a8c09c1db5
commit 6f106e43af
22 files changed
+1473 -128

No files matched your search

@@ -159,29 +159,18 @@
<!-- 证照附件 -->
<el-tab-pane :label="`证照附件(${certificateAttachments.length})`" name="attachment">
<div class="tab-toolbar">
<span class="upload-tip">支持图片 / PDF,单个不超过 10MB,可多选批量上传,数量不限</span>
<el-upload
:show-file-list="false"
:http-request="(opt) => handleCertUpload('education_photo', opt)"
:http-request="handleCertUpload"
:before-upload="beforeCertUpload"
multiple
accept="image/*,.pdf"
>
<el-button
type="primary"
plain
size="small"
:loading="certSaving === 'education_photo'"
>
{{ file.education_photo ? '替换学历照片' : '上传学历照片' }}
<el-button type="primary" size="small" :icon="Upload" :loading="certUploading">
批量上传证照
</el-button>
</el-upload>
<el-button
type="primary"
size="small"
:icon="Plus"
@click="openRecordDialog({ type: 4 })"
>
上传证照
</el-button>
</div>
<div v-if="certificateAttachments.length" class="attachment-grid">
<div v-for="item in certificateAttachments" :key="item.id" class="attachment-card">
@@ -238,8 +227,9 @@
<script setup>
import { computed, reactive, ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Document, Edit, Plus, Refresh } from '@element-plus/icons-vue';
import { Document, Edit, Plus, Refresh, Upload } from '@element-plus/icons-vue';
import {
createFileRecord,
deleteFileRecord,
getEmployeeFileDetail,
updateEmployeeFileCertificates,
@@ -263,6 +253,10 @@ const visible = ref(false);
const loading = ref(false);
const recordDialogRef = ref();
const certSaving = ref('');
/** 批量上传状态:pending 为并发中的文件数,全部结束后统一刷新 */
const certUploading = ref(false);
const certPending = ref(0);
const certDone = ref(0);
const certificateForm = reactive({
education_photo: '',
id_card_front: '',
@@ -299,30 +293,60 @@ const beforeCertUpload = (uploadedFile) => {
return isImageOrPdf && isLt10M;
};
/** 批量上传时用「文件名(去扩展名)」作为默认证照名称,便于后续辨认 */
const certTitleFromFile = (name) =>
String(name || '').replace(/\.[^.]+$/, '').trim() || '证照附件';
const saveCertificates = async () => {
await updateEmployeeFileCertificates(file.value.id, certificateForm);
await loadDetail();
emit('refresh');
};
const handleCertUpload = async (field, { file: uploadedFile }) => {
certSaving.value = field;
/**
* 批量上传证照:统一入口,不再区分学历照片 / 身份证等细分类别。
* 每个文件独立上传并生成一条「证照附件」记录,数量不限;
* el-upload 开启 multiple 后会对每个文件各调用一次本方法,全部结束后统一刷新列表。
*/
const handleCertUpload = async ({ file: uploadedFile }) => {
const employeeId = Number(file.value.employee_id) || 0;
if (!employeeId) {
ElMessage.error('档案缺少员工信息,无法上传证照');
return;
}
certPending.value += 1;
certUploading.value = true;
try {
const data = new FormData();
data.append('file', uploadedFile);
const res = await uploadFile(data);
const url = res?.url || res?.data?.url;
if (!url) {
ElMessage.error(res?.msg || '上传失败');
return;
}
certificateForm[field] = url;
await saveCertificates();
ElMessage.success('证照已保存');
if (!url) throw new Error(res?.msg || '上传失败');
await createFileRecord({
employee_id: employeeId,
type: 4,
title: certTitleFromFile(uploadedFile.name),
sub_title: '',
extra: '',
start_date: '',
end_date: '',
description: '',
attachment_url: url,
sort: 0,
});
certDone.value += 1;
} catch (error) {
ElMessage.error(error?.message || '证照保存失败');
ElMessage.error(`「${uploadedFile.name}」${error?.message || '上传失败'}`);
} finally {
certSaving.value = '';
certPending.value -= 1;
if (certPending.value <= 0) {
certUploading.value = false;
const done = certDone.value;
certDone.value = 0;
await loadDetail();
if (done) ElMessage.success(`已上传 ${done} 个证照附件`);
}
}
};
@@ -500,7 +524,7 @@ defineExpose({ open, reload: loadDetail });
gap: 12px;
padding: 14px 16px;
border-radius: 8px;
background: #f5f7fa;
background: var(--el-fill-color-light);
.name-line {
display: flex;
@@ -510,19 +534,19 @@ defineExpose({ open, reload: loadDetail });
.name {
font-size: 18px;
font-weight: 600;
color: #303133;
color: var(--el-text-color-primary);
}
.file-no {
font-size: 12px;
color: #909399;
color: var(--el-text-color-secondary);
}
}
.meta-line {
margin-top: 6px;
font-size: 13px;
color: #606266;
color: var(--el-text-color-regular);
}
}
@@ -536,7 +560,7 @@ defineExpose({ open, reload: loadDetail });
margin: 14px 0 10px;
font-size: 14px;
font-weight: 600;
color: #303133;
color: var(--el-text-color-primary);
&:first-child {
margin-top: 0;
@@ -551,6 +575,12 @@ defineExpose({ open, reload: loadDetail });
margin-bottom: 10px;
}
.upload-tip {
margin-right: auto;
font-size: 12px;
color: var(--el-text-color-secondary);
}
.attachment-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
@@ -558,7 +588,7 @@ defineExpose({ open, reload: loadDetail });
}
.attachment-card {
border: 1px solid #ebeef5;
border: 1px solid var(--el-border-color-lighter);
border-radius: 8px;
padding: 10px;
display: flex;
@@ -578,7 +608,7 @@ defineExpose({ open, reload: loadDetail });
justify-content: center;
border-radius: 6px;
overflow: hidden;
background: #f5f7fa;
background: var(--el-fill-color-light);
cursor: pointer;
.attachment-thumb {
@@ -592,7 +622,7 @@ defineExpose({ open, reload: loadDetail });
flex-direction: column;
align-items: center;
gap: 6px;
color: #909399;
color: var(--el-text-color-secondary);
font-size: 12px;
}
}
@@ -601,7 +631,7 @@ defineExpose({ open, reload: loadDetail });
margin-top: 8px;
width: 100%;
font-size: 13px;
color: #303133;
color: var(--el-text-color-primary);
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
@@ -611,7 +641,7 @@ defineExpose({ open, reload: loadDetail });
.attachment-sub {
width: 100%;
font-size: 12px;
color: #909399;
color: var(--el-text-color-secondary);
text-align: center;
overflow: hidden;
text-overflow: ellipsis;