| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905 |
- <template>
- <div class="p-2 personnel-page">
- <!-- 页面标题 -->
- <div class="page-header">
- <h2 class="page-title">人员信息管理</h2>
- <p class="page-desc">管理企业所有工作人员基本信息和岗位证书</p>
- </div>
- <!-- 筛选区域 -->
- <el-card shadow="hover" class="filter-card">
- <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="80">
- <el-form-item label="姓名" prop="personnelName">
- <el-input
- v-model="queryParams.personnelName"
- placeholder="请输入姓名搜索"
- style="width: 220px"
- clearable
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="人员类型" prop="jobType">
- <el-select v-model="queryParams.jobType" placeholder="全部类型" style="width: 200px" @change="handleQuery">
- <el-option label="全部类型" value="" />
- <el-option
- v-for="d in personTypeOptions"
- :key="d.value"
- :label="d.label"
- :value="d.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="工作门店" prop="storeId">
- <el-select
- v-model="queryParams.storeId"
- placeholder="请选择工作门店"
- clearable
- filterable
- style="width: 220px"
- @change="handleQuery"
- >
- <el-option
- v-for="d in storeOptions"
- :key="d.value"
- :label="d.label"
- :value="d.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleQuery">搜索</el-button>
- <el-button @click="resetQuery">重置</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- <!-- 列表 -->
- <el-card shadow="hover" class="list-card">
- <div class="list-toolbar">
- <span class="list-total">共 {{ total }} 条记录</span>
- <el-button type="primary" :icon="Plus" @click="handleAdd">新增人员</el-button>
- </div>
- <el-table v-loading="loading" :data="personnelList" border stripe>
- <el-table-column type="index" label="序号" align="center" width="60" />
- <el-table-column label="类型" align="center" prop="jobType" width="100">
- <template #default="scope">
- <el-tag :type="getJobTypeTag(scope.row.jobType)">
- {{ getDictLabel(personTypeOptions, scope.row.jobType) || scope.row.jobType || '-' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="姓名" align="center" prop="personnelName" width="100" />
- <el-table-column label="年龄" align="center" prop="age" width="70" />
- <el-table-column label="身份证号" align="center" prop="idCard" width="200">
- <template #default="scope">
- <span>{{ maskIdCard(scope.row.idCard) }}</span>
- </template>
- </el-table-column>
- <el-table-column label="岗位证书" align="center" min-width="200">
- <template #default="scope">
- <el-tag
- v-for="(cert, idx) in getPersonnelCerts(scope.row.id)"
- :key="idx"
- type="success"
- size="small"
- style="margin-right: 4px; margin-bottom: 4px"
- >
- {{ cert.certName || cert.certType }}
- </el-tag>
- <span v-if="getPersonnelCerts(scope.row.id).length === 0" class="text-placeholder">暂无证书</span>
- </template>
- </el-table-column>
- <el-table-column label="工作门店" align="center" prop="storeName" :show-overflow-tooltip="true" width="160" />
- <el-table-column label="保险状态" align="center" width="110">
- <template #default="scope">
- <el-tag :type="getInsuranceTag(scope.row)">{{ getInsuranceLabel(scope.row) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" align="center" width="220" fixed="right">
- <template #default="scope">
- <el-button link type="primary" @click="handleViewCert(scope.row)">查看证书</el-button>
- <el-button link type="primary" @click="handleUpdate(scope.row)">编辑</el-button>
- <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <pagination
- v-show="total > 0"
- v-model:page="queryParams.pageNum"
- v-model:limit="queryParams.pageSize"
- :total="total"
- @pagination="getList"
- />
- </el-card>
- <!-- 新增/编辑弹窗 -->
- <el-dialog
- v-model="dialog.visible"
- :title="dialog.title"
- width="820px"
- append-to-body
- :close-on-click-modal="false"
- @close="closeDialog"
- >
- <el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="人员类型" prop="jobType">
- <el-select v-model="form.jobType" placeholder="请选择人员类型" style="width: 100%">
- <el-option
- v-for="d in personTypeOptions"
- :key="d.value"
- :label="d.label"
- :value="d.value"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="姓名" prop="personnelName">
- <el-input v-model="form.personnelName" placeholder="请输入姓名" maxlength="50" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="身份证号" prop="idCard">
- <el-input
- v-model="form.idCard"
- placeholder="请输入18位身份证号"
- maxlength="18"
- @blur="handleIdCardBlur"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="年龄" prop="age">
- <el-input-number
- v-model="form.age"
- :min="0"
- :max="150"
- placeholder="根据身份证自动计算"
- disabled
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="工作门店" prop="storeId">
- <el-select
- v-model="form.storeId"
- placeholder="请选择工作门店"
- filterable
- style="width: 100%"
- @change="handleStoreChange"
- >
- <el-option
- v-for="d in storeOptions"
- :key="d.value"
- :label="d.label"
- :value="d.value"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="保险状态" prop="insuranceStatus">
- <el-select
- v-model="form.insuranceStatus"
- placeholder="请选择保险状态"
- style="width: 100%"
- @change="handleInsuranceStatusChange"
- >
- <el-option
- v-for="d in insuranceStatusOptions"
- :key="d.value"
- :label="d.label"
- :value="d.value"
- />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <!-- 保险有效期:仅已投保时必填 -->
- <el-row v-if="form.insuranceStatus === '已投保'" :gutter="20">
- <el-col :span="12">
- <el-form-item label="保险有效期开始" prop="insuranceStartDate">
- <el-date-picker
- v-model="form.insuranceStartDate"
- type="datetime"
- placeholder="请选择保险有效期开始"
- value-format="YYYY-MM-DD HH:mm:ss"
- format="YYYY-MM-DD HH:mm"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="保险有效期结束" prop="insuranceExpireDate">
- <el-date-picker
- v-model="form.insuranceExpireDate"
- type="datetime"
- placeholder="请选择保险有效期结束"
- value-format="YYYY-MM-DD HH:mm:ss"
- format="YYYY-MM-DD HH:mm"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <!-- 岗位证书:动态项 -->
- <el-form-item label="岗位证书">
- <div class="cert-container">
- <div
- v-for="(cert, index) in form.certList"
- :key="cert.uid || index"
- class="cert-item"
- >
- <el-select
- v-model="cert.certType"
- placeholder="请选择证书类型"
- style="width: 220px"
- >
- <el-option
- v-for="d in certTypeOptions"
- :key="d.value"
- :label="d.label"
- :value="d.value"
- />
- </el-select>
- <el-upload
- v-model:file-list="cert.fileList"
- :http-request="customCertUpload"
- :limit="1"
- :accept="'.pdf,.jpg,.png,.jpeg'"
- :before-upload="beforeCertUpload"
- :on-success="(response, file) => handleCertUploadSuccess(response, file, index)"
- :on-error="handleUploadError"
- :on-exceed="handleCertExceed"
- :on-remove="() => handleCertRemove(index)"
- >
- <template #trigger>
- <div class="cert-upload-trigger">
- <el-button type="primary" plain>选择文件</el-button>
- <span class="cert-upload-text">{{ cert.fileList.length > 0 ? '已选择 1 个文件' : '未选择任何文件' }}</span>
- </div>
- </template>
- </el-upload>
- <el-button link type="danger" @click="handleRemoveCert(index)">删除</el-button>
- </div>
- <el-button type="primary" plain :icon="Plus" @click="handleAddCert" style="margin-top: 4px">
- 添加证书
- </el-button>
- </div>
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="cancel">取消</el-button>
- <el-button type="primary" @click="submitForm">保存</el-button>
- </div>
- </template>
- </el-dialog>
- <!-- 查看证书弹窗 -->
- <el-dialog v-model="certDialog.visible" title="岗位证书" width="640px" append-to-body>
- <el-descriptions v-if="currentCerts.length > 0" :column="1" border>
- <el-descriptions-item
- v-for="(cert, idx) in currentCerts"
- :key="idx"
- :label="cert.certName || cert.certType"
- >
- <div v-if="cert.certFile" class="cert-view-box">
- <el-icon :size="22" class="cert-view-icon"><Document /></el-icon>
- <el-link :href="cert.certFile" target="_blank" type="primary" style="margin-left: 6px">
- 查看证书文件
- </el-link>
- </div>
- <span v-else class="text-placeholder">未上传证书文件</span>
- </el-descriptions-item>
- </el-descriptions>
- <div v-else class="empty-tip">该人员暂无岗位证书</div>
- <template #footer>
- <el-button @click="certDialog.visible = false">关闭</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup name="Personnel" lang="ts">
- import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue';
- import {
- listPersonnel,
- getPersonnel,
- savePersonnelWithCerts,
- delPersonnel
- } from '@/api/risk/personnel';
- import { listPersonnelCertByPersonnel } from '@/api/risk/personnelCert';
- import { uploadFile } from '@/api/risk/file';
- import { getDicts } from '@/api/system/dict/data';
- import { listDept } from '@/api/system/dept';
- import type { DictDataVO } from '@/api/system/dict/data/types';
- import type { RiskPersonnelBo, RiskPersonnelVo, RiskPersonnelCertVo } from '@/api/risk/types';
- import { Plus, Document } from '@element-plus/icons-vue';
- import { ElMessage, FormInstance, UploadFile, UploadRawFile, UploadUserFile } from 'element-plus';
- import type { ComponentInternalInstance } from 'vue';
- const { proxy } = getCurrentInstance() as ComponentInternalInstance;
- interface CertItem {
- uid: number;
- id?: string | number;
- certType: string;
- certName?: string;
- certFile?: string;
- fileList: UploadUserFile[];
- }
- const personnelList = ref<RiskPersonnelVo[]>([]);
- const certMap = ref<Record<string | number, RiskPersonnelCertVo[]>>({});
- const loading = ref(true);
- const total = ref(0);
- const currentCerts = ref<RiskPersonnelCertVo[]>([]);
- // 字典
- const personTypeOptions = ref<Array<{ label: string; value: string }>>([]);
- const certTypeOptions = ref<Array<{ label: string; value: string }>>([]);
- const insuranceStatusOptions = ref<Array<{ label: string; value: string }>>([]);
- // 门店列表(从 sys_dept 拉 dept_category='门店' 的)
- const storeOptions = ref<Array<{ label: string; value: number }>>([]);
- const queryFormRef = ref<FormInstance>();
- const formRef = ref<FormInstance>();
- /** 加载字典 */
- const loadDicts = async () => {
- try {
- const [personType, certType, insuranceStatus] = await Promise.all([
- getDicts('person_type'),
- getDicts('certificate_type'),
- getDicts('insurance_status')
- ]);
- personTypeOptions.value = mapDictData(personType);
- certTypeOptions.value = mapDictData(certType);
- insuranceStatusOptions.value = mapDictData(insuranceStatus);
- } catch (e) {
- console.error('加载字典失败', e);
- }
- };
- const mapDictData = (resOrList: any) => {
- // 字典接口被 axios 拦截器自动解包,返回值为数组;兼容部分场景被再包一层 .data 的情况
- const list: any[] = Array.isArray(resOrList)
- ? resOrList
- : Array.isArray(resOrList?.data)
- ? resOrList.data
- : [];
- return list
- .filter((d) => d.status === '0' || d.status === undefined)
- .sort((a, b) => (a.dictSort || 0) - (b.dictSort || 0))
- .map((d) => ({ label: d.dictLabel, value: d.dictValue }));
- };
- /** 加载门店列表(从 sys_dept 过滤 dept_category='门店') */
- const loadStores = async () => {
- try {
- const res: any = await listDept({});
- const list: any[] = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : [];
- storeOptions.value = list
- .filter((d: any) => d.deptCategory === '门店' && (d.status === '0' || d.status === undefined))
- .map((d: any) => ({ label: d.deptName, value: d.deptId }));
- } catch (e) {
- console.error('加载门店列表失败', e);
- storeOptions.value = [];
- }
- };
- const validateIdCard = (_rule: any, value: any, callback: any) => {
- if (!value) {
- callback(new Error('请输入身份证号'));
- return;
- }
- const reg = /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
- if (!reg.test(value)) {
- callback(new Error('请输入正确的18位身份证号'));
- return;
- }
- const year = parseInt(value.substring(6, 10), 10);
- const month = parseInt(value.substring(10, 12), 10);
- const day = parseInt(value.substring(12, 14), 10);
- const birthDate = new Date(year, month - 1, day);
- if (
- birthDate.getFullYear() !== year ||
- birthDate.getMonth() !== month - 1 ||
- birthDate.getDate() !== day
- ) {
- callback(new Error('身份证号中的出生日期不合法'));
- return;
- }
- callback();
- };
- const data = reactive<any>({
- form: {
- id: undefined,
- personnelCode: '',
- personnelName: '',
- idCard: '',
- age: undefined,
- storeId: undefined,
- storeName: '',
- jobType: '',
- insuranceStatus: '未投保',
- insuranceStartDate: '',
- insuranceExpireDate: '',
- certList: [] as CertItem[]
- } as any,
- queryParams: {
- pageNum: 1,
- pageSize: 10,
- personnelName: '',
- jobType: '',
- storeId: undefined
- },
- rules: {
- jobType: [{ required: true, message: '请选择人员类型', trigger: 'change' }],
- personnelName: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
- idCard: [{ required: true, validator: validateIdCard, trigger: 'blur' }],
- storeId: [{ required: true, message: '请选择工作门店', trigger: 'change' }],
- insuranceStatus: [{ required: true, message: '请选择保险状态', trigger: 'change' }],
- insuranceStartDate: [
- {
- validator: (_rule: any, value: any, callback: any) => {
- if (data.form.insuranceStatus === '已投保' && !value) {
- callback(new Error('已投保时必须选择保险开始日期'));
- } else {
- callback();
- }
- },
- trigger: 'change'
- }
- ],
- insuranceExpireDate: [
- {
- validator: (_rule: any, value: any, callback: any) => {
- if (data.form.insuranceStatus === '已投保' && !value) {
- callback(new Error('已投保时必须选择保险结束日期'));
- } else {
- callback();
- }
- },
- trigger: 'change'
- }
- ]
- }
- });
- const { queryParams, form, rules } = toRefs(data);
- const dialog = reactive({
- visible: false,
- title: '新增人员信息'
- });
- const certDialog = reactive({
- visible: false
- });
- /** 身份证号计算年龄 */
- const calculateAge = (idCard: string): number => {
- if (!idCard || idCard.length !== 18) return 0;
- const birthYear = parseInt(idCard.substring(6, 10), 10);
- const birthMonth = parseInt(idCard.substring(10, 12), 10);
- const birthDay = parseInt(idCard.substring(14, 16), 10);
- const today = new Date();
- let age = today.getFullYear() - birthYear;
- if (
- today.getMonth() + 1 < birthMonth ||
- (today.getMonth() + 1 === birthMonth && today.getDate() < birthDay)
- ) {
- age--;
- }
- return age;
- };
- const maskIdCard = (idCard: string): string => {
- if (!idCard) return '';
- if (idCard.length < 8) return idCard;
- return idCard.substring(0, 6) + '********' + idCard.substring(14);
- };
- const handleIdCardBlur = () => {
- if (form.value.idCard && form.value.idCard.length === 18) {
- form.value.age = calculateAge(form.value.idCard);
- }
- };
- /** 保险状态切换:切换为未投保时清空有效期 */
- const handleInsuranceStatusChange = (val: string) => {
- if (val !== '已投保') {
- form.value.insuranceStartDate = '';
- form.value.insuranceExpireDate = '';
- }
- };
- /** 工作门店选择变化:自动回填 storeName */
- const handleStoreChange = (deptId: number | string) => {
- const found = storeOptions.value.find((s) => s.value === deptId);
- form.value.storeName = found?.label || '';
- };
- /** 列表中保险状态标签 */
- const getInsuranceLabel = (row: any) => {
- if (row.insuranceStatus === '未投保') return '未投保';
- if (row.insuranceStatus === '已投保') {
- if (!row.insuranceExpireDate) return '已投保';
- const expire = new Date(row.insuranceExpireDate);
- const today = new Date();
- const diffDays = Math.floor((expire.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
- if (diffDays < 0) return '未投保';
- if (diffDays <= 30) return '即将到期';
- return '已投保';
- }
- return row.insuranceStatus || '未投保';
- };
- const getInsuranceTag = (row: any) => {
- const label = getInsuranceLabel(row);
- const map: Record<string, string> = {
- 已投保: 'success',
- 未投保: 'danger',
- 即将到期: 'warning'
- };
- return map[label] || 'info';
- };
- // 人员类型 tag 颜色(按字典 value 映射,与设计稿一致)
- const getJobTypeTag = (type: string) => {
- const map: Record<string, string> = {
- 防损人员: 'primary', // 蓝色
- 施工人员: 'warning', // 橙色
- 特岗人员: 'danger', // 红色
- 食品相关岗位人员: 'success', // 绿色
- 检测员: 'info', // 灰色
- 兼职品控人员: 'primary' // 蓝色
- };
- return map[type] || 'info';
- };
- // 字典值 -> 中文 label(用于列表渲染)
- const getDictLabel = (
- opts: Array<{ label: string; value: string }>,
- val: string
- ) => {
- if (!val) return ''
- return opts.find((o) => o.value === val)?.label || val
- }
- const getList = async () => {
- loading.value = true;
- const res = await listPersonnel(queryParams.value as RiskPersonnelBo);
- loading.value = false;
- personnelList.value = res.rows || [];
- total.value = res.total || 0;
- if (personnelList.value.length > 0) {
- loadPersonnelCerts(personnelList.value);
- }
- };
- const loadPersonnelCerts = async (list: RiskPersonnelVo[]) => {
- const map: Record<string | number, RiskPersonnelCertVo[]> = {};
- await Promise.all(
- list.map(async (p) => {
- try {
- const res = await listPersonnelCertByPersonnel(p.id);
- map[p.id] = res.data || [];
- } catch (e) {
- map[p.id] = [];
- }
- })
- );
- certMap.value = map;
- };
- const getPersonnelCerts = (id: string | number): RiskPersonnelCertVo[] => {
- return certMap.value[id] || [];
- };
- const handleQuery = () => {
- queryParams.value.pageNum = 1;
- getList();
- };
- const resetQuery = () => {
- queryFormRef.value?.resetFields();
- queryParams.value.pageNum = 1;
- queryParams.value.personnelName = '';
- queryParams.value.jobType = '';
- queryParams.value.storeId = undefined;
- handleQuery();
- };
- const handleAdd = () => {
- reset();
- dialog.visible = true;
- dialog.title = '新增人员信息';
- };
- const handleUpdate = async (row: RiskPersonnelVo) => {
- reset();
- const res = await getPersonnel(row.id);
- Object.assign(form.value, res.data);
- if (form.value.idCard) {
- form.value.age = calculateAge(form.value.idCard);
- }
- try {
- const certRes = await listPersonnelCertByPersonnel(row.id);
- const certs = certRes.data || [];
- form.value.certList = certs.map((c) => ({
- uid: -Math.floor(Math.random() * 100000),
- id: c.id,
- certType: c.certType,
- certName: c.certName,
- certFile: c.certFile,
- fileList: c.certFile
- ? [
- {
- uid: -Math.floor(Math.random() * 100000),
- name: c.certName || c.certType || '证书',
- status: 'success',
- url: c.certFile
- }
- ]
- : []
- }));
- } catch (e) {
- form.value.certList = [];
- }
- dialog.visible = true;
- dialog.title = '编辑人员信息';
- };
- const handleViewCert = async (row: RiskPersonnelVo) => {
- try {
- const res = await listPersonnelCertByPersonnel(row.id);
- currentCerts.value = res.data || [];
- } catch (e) {
- currentCerts.value = [];
- }
- certDialog.visible = true;
- };
- const handleDelete = async (row: RiskPersonnelVo) => {
- try {
- await proxy?.$modal.confirm(`是否确认删除姓名为 "${row.personnelName}" 的人员信息?`);
- await delPersonnel(row.id);
- proxy?.$modal.msgSuccess('删除成功');
- await getList();
- } catch (e) {
- // 取消
- }
- };
- const handleAddCert = () => {
- if (!form.value.certList) {
- form.value.certList = [];
- }
- form.value.certList.push({
- uid: Date.now() + Math.floor(Math.random() * 1000),
- certType: '',
- certFile: '',
- fileList: []
- });
- };
- const handleRemoveCert = (index: number) => {
- form.value.certList?.splice(index, 1);
- };
- const handleCertUploadSuccess = (response: any, file: UploadFile, index: number) => {
- const url = response.data?.url || response.url;
- file.url = url;
- if (form.value.certList && form.value.certList[index]) {
- form.value.certList[index].certFile = url;
- }
- ElMessage.success('证书上传成功');
- };
- const handleCertRemove = (index: number) => {
- if (form.value.certList && form.value.certList[index]) {
- form.value.certList[index].certFile = '';
- form.value.certList[index].fileList = [];
- }
- };
- const handleUploadError = () => {
- ElMessage.error('上传失败,请重试');
- };
- const customCertUpload = async (options: any) => {
- try {
- const res: any = await uploadFile(options.file);
- if (res && res.code === 200 && res.data) {
- options.onSuccess(res, options.file);
- } else {
- options.onError(new Error(res?.msg || '上传失败'));
- }
- } catch (err: any) {
- options.onError(err);
- }
- };
- const beforeCertUpload = (file: UploadRawFile) => {
- const validTypes = ['image/jpeg', 'image/png', 'image/jpg', 'application/pdf'];
- if (!validTypes.includes(file.type)) {
- ElMessage.error('只能上传 PDF/JPG/PNG 格式的文件!');
- return false;
- }
- if (file.size / 1024 / 1024 >= 10) {
- ElMessage.error('文件大小不能超过 10MB!');
- return false;
- }
- return true;
- };
- const handleCertExceed = () => {
- ElMessage.warning('每个证书只能上传1个文件');
- };
- const submitForm = () => {
- formRef.value?.validate(async (valid: boolean) => {
- if (valid) {
- const certBos = (form.value.certList || [])
- .filter((c: CertItem) => c.certType)
- .map((c: CertItem) => ({
- id: c.id,
- certType: c.certType,
- certName: c.certType,
- certFile: c.certFile || '',
- certStatus: '有效'
- }));
- const personnel = {
- id: form.value.id,
- personnelCode: form.value.personnelCode,
- personnelName: form.value.personnelName,
- idCard: form.value.idCard,
- age: form.value.age,
- storeId: form.value.storeId,
- storeName: form.value.storeName,
- jobType: form.value.jobType,
- insuranceStatus: form.value.insuranceStatus,
- insuranceStartDate: form.value.insuranceStatus === '已投保' ? form.value.insuranceStartDate : '',
- insuranceExpireDate: form.value.insuranceStatus === '已投保' ? form.value.insuranceExpireDate : ''
- };
- await savePersonnelWithCerts({ personnel, certList: certBos });
- proxy?.$modal.msgSuccess(form.value.id ? '修改成功' : '新增成功');
- dialog.visible = false;
- await getList();
- }
- });
- };
- const cancel = () => {
- dialog.visible = false;
- reset();
- };
- const reset = () => {
- form.value = {
- id: undefined,
- personnelCode: '',
- personnelName: '',
- idCard: '',
- age: undefined,
- storeId: undefined,
- storeName: '',
- jobType: '',
- insuranceStatus: '未投保',
- insuranceStartDate: '',
- insuranceExpireDate: '',
- certList: []
- } as any;
- formRef.value?.resetFields();
- };
- const closeDialog = () => {
- dialog.visible = false;
- reset();
- };
- onMounted(async () => {
- await loadDicts();
- await loadStores();
- await getList();
- });
- </script>
- <style scoped>
- .personnel-page {
- padding: 16px;
- height: 100%;
- overflow-y: auto;
- box-sizing: border-box;
- }
- .page-header {
- margin-bottom: 16px;
- }
- .page-title {
- font-size: 20px;
- font-weight: 600;
- margin: 0;
- color: #303133;
- }
- .page-desc {
- font-size: 13px;
- color: #909399;
- margin: 4px 0 0;
- }
- .filter-card {
- margin-bottom: 12px;
- }
- .list-card {
- margin-bottom: 12px;
- }
- .list-toolbar {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
- }
- .list-total {
- font-size: 13px;
- color: #606266;
- }
- .text-placeholder {
- color: #c0c4cc;
- font-size: 12px;
- }
- .empty-tip {
- text-align: center;
- color: #909399;
- padding: 40px 0;
- font-size: 14px;
- }
- .cert-container {
- width: 100%;
- }
- .cert-item {
- display: flex;
- align-items: center;
- gap: 12px;
- margin-bottom: 12px;
- }
- .cert-upload-trigger {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- }
- .cert-upload-text {
- font-size: 13px;
- color: #606266;
- }
- :deep(.el-upload) {
- display: inline-block;
- }
- .cert-view-box {
- display: inline-flex;
- align-items: center;
- }
- .cert-view-icon {
- color: #e6a23c;
- }
- </style>
|