From 8d9f5777e0c52579bfa3c1a093896cee0ed577f1 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sun, 2 Aug 2026 10:37:19 +0800 Subject: [PATCH] feat(web-admin): add personnel photo on driver/escort pages Co-authored-by: Cursor --- web-admin/src/views/pl/personnel/index.vue | 94 +++++++++++++++++++++- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/web-admin/src/views/pl/personnel/index.vue b/web-admin/src/views/pl/personnel/index.vue index 419af4af4..e49ae3682 100644 --- a/web-admin/src/views/pl/personnel/index.vue +++ b/web-admin/src/views/pl/personnel/index.vue @@ -105,6 +105,23 @@ header-align="center" :index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1" /> + + + + + + + + + @@ -341,6 +378,16 @@ 删除 + @@ -362,6 +409,8 @@ import PlCertFormList from '@/components/business/pl-cert-form-list/index.vue' import PlPasteRecognizeDialog from '@/components/business/pl-paste-recognize-dialog/index.vue' import PlCertViewList from '@/components/business/pl-cert-view-list/index.vue' import PlPersonnelCertBadges from '@/components/business/pl-personnel-cert-badges/index.vue' +import PlCertListAttachmentCell from '@/components/business/pl-cert-list-attachment-cell/index.vue' +import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue' import SystemViewDialog, { type ViewSection } from '@/components/business/system-view-dialog/index.vue' @@ -372,6 +421,7 @@ import { useDbInstantSearch } from '@/composables/useDbInstantSearch' import { pickBxPageResult } from '@/utils/http/bx' import { DEFAULT_LIST_PAGE_SIZE } from '@/utils/table/tableConfig' import { formatDateTime } from '@/utils/format' +import { firstPhotoUrl, resolvePhotoDisplayUrl } from '@/utils/pl-photo' import { canDeleteByCreator, filterDeletableByCreator, allRowsDeletableByCreator, CREATOR_DELETE_TIP } from '@/utils/creator-delete' import { collectRequiredCertSlotErrors, @@ -401,6 +451,8 @@ const isCarrierPersonnel = computed(() => affiliationMode.value === 'carrier') const loading = ref(false) const listColumnDefs = [ + { prop: 'photoUrls', label: '照片' }, + { prop: 'certAttachments', label: '证书附件' }, { prop: 'personnelAffiliation', label: '人员归属' }, { prop: 'personnelType', label: '人员类型' }, { prop: 'phoneNo', label: '手机号' }, @@ -417,7 +469,7 @@ const { isIndeterminate, isColumnVisible, resetToDefault -} = usePersistedListColumns('pl-personnel-list-columns', listColumnDefs) +} = usePersistedListColumns('pl-personnel-list-columns-v3', listColumnDefs) const submitLoading = ref(false) const personnelRows = ref([]) @@ -429,6 +481,7 @@ const formRef = ref() const isEdit = ref(false) const certList = ref([]) const certFormListRef = ref() +const photoUploadRef = ref>() const viewVisible = ref(false) const viewLoading = ref(false) const viewDetail = ref(null) @@ -463,7 +516,8 @@ const formData = reactive({ personnelType: '', personnelAffiliation: 'self', status: '1', - remark: '' + remark: '', + photoUrls: '' }) const requiredPersonnelCertNames = computed(() => @@ -559,6 +613,11 @@ const viewSections = computed(() => { fieldLayout: 'table', tableMode: 'triple' }, + { + title: '人员照片', + fields: [], + slot: 'photo' + }, { title: `人员证书${ viewDetail.value && !isCarrierPersonnel.value @@ -697,7 +756,8 @@ function handleAdd() { personnelType: '', personnelAffiliation: affiliationMode.value, status: '1', - remark: '' + remark: '', + photoUrls: '' }) resetCertList() dialogVisible.value = true @@ -740,7 +800,8 @@ async function handleEdit(row: Personnel) { personnelType: detail.personnelType || 'both', personnelAffiliation: affiliationMode.value, status: detail.status, - remark: detail.remark || '' + remark: detail.remark || '', + photoUrls: detail.photoUrls || '' }) certList.value = mergePersonnelCertSlots( @@ -767,6 +828,10 @@ async function handleSubmit() { ElMessage.warning('证书附件上传中,请稍候') return } + if (photoUploadRef.value?.hasUploading?.()) { + ElMessage.warning('附件上传中,请稍候') + return + } submitLoading.value = true try { @@ -779,6 +844,7 @@ async function handleSubmit() { personnelAffiliation: formData.personnelAffiliation, status: formData.status, remark: formData.remark || undefined, + photoUrls: formData.photoUrls || undefined, certList: certList.value.map(c => ({ certId: c.certId, certName: c.certName.trim(), @@ -866,4 +932,24 @@ onMounted(() => { flex-wrap: wrap; gap: 6px; } + +.pl-photo-thumb { + width: 48px; + height: 48px; + border-radius: 8px; + object-fit: cover; + border: 1px solid var(--el-border-color, #d9e8e2); + display: inline-block; + vertical-align: middle; + background: #f8fafc; +} + +.pl-photo-thumb--empty { + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 11px; + color: #94a3b8; + box-sizing: border-box; +}