This commit is contained in:
zlwu committed 2026-07-14 19:53:35 +08:00
1 parent 0e60b88cba
commit 87f7f6bf38
34 files changed
+18326 -6165

No files matched your search

+1
View File
@@ -419,6 +419,7 @@ export interface PlanQuery {
transportStatus?: string
archiveStatus?: string
customerName?: string
productName?: string
planDateFrom?: string
planDateTo?: string
page?: number
@@ -205,6 +205,8 @@ const props = withDefaults(
enforceSelfVehicleRequired?: boolean
/** 承运商车辆:按主车/挂车注入必备槽位(行驶证、营运证) */
enforceCarrierVehicleRequired?: boolean
/** 人员:切换类型时隐藏非当前类型的必备槽(如 driver 不展示押运员从业资格证) */
stripManagedCertNames?: readonly string[]
}>(),
{
title: '证书',
@@ -216,7 +218,8 @@ const props = withDefaults(
requiredCertNames: () => [],
requiredVehiclePart: PL_VEHICLE_CERT_PART_HEAD,
enforceSelfVehicleRequired: false,
enforceCarrierVehicleRequired: false
enforceCarrierVehicleRequired: false,
stripManagedCertNames: () => []
}
)
@@ -294,7 +297,8 @@ function syncRequiredSlots() {
}
if (props.requiredCertNames?.length) {
certList.value = mergeCertListWithRequiredSlots(certList.value, props.requiredCertNames, {
vehiclePart: props.requiredVehiclePart
vehiclePart: props.requiredVehiclePart,
stripManagedNames: props.stripManagedCertNames
})
}
}
@@ -319,6 +323,7 @@ watch(
props.enforceSelfVehicleRequired,
props.enforceCarrierVehicleRequired,
props.requiredCertNames,
props.stripManagedCertNames,
props.trailerNo
] as const,
() => {
+19 -5
View File
@@ -21,7 +21,6 @@ export function isRequiredCertSlotComplete(
): boolean {
if (!cert) return false
if (!matchRequiredCertName(cert.certName, requiredName)) return false
if (!cert.certNo?.trim()) return false
if (!cert.expireDate) return false
if (!cert.attachmentUrls?.trim()) return false
return true
@@ -32,10 +31,9 @@ export function getMissingRequiredCertFieldLabels(
requiredName: string
): string[] {
if (!cert || !matchRequiredCertName(cert.certName, requiredName)) {
return ['证号', '有效期', '附件']
return ['有效期', '附件']
}
const missing: string[] = []
if (!cert.certNo?.trim()) missing.push('证号')
if (!cert.expireDate) missing.push('有效期')
if (!cert.attachmentUrls?.trim()) missing.push('附件')
return missing
@@ -133,12 +131,28 @@ function findCertIndexForRequired(
export function mergeCertListWithRequiredSlots(
existing: CertRowWithMeta[],
requiredNames: readonly string[],
options?: { vehiclePart?: string }
options?: { vehiclePart?: string; stripManagedNames?: readonly string[] }
): CertRowWithMeta[] {
const vehiclePart = options?.vehiclePart
const next = existing.map(cert => ({ ...cert }))
const stripManagedNames = options?.stripManagedNames
const normalizedPart = vehiclePart ? normalizeVehicleCertPart(vehiclePart) : null
let source = existing
if (stripManagedNames?.length) {
source = existing.filter(cert => {
if (normalizedPart && normalizeVehicleCertPart(cert.vehiclePart) !== normalizedPart) {
return true
}
if (requiredNames.some(name => matchRequiredCertName(cert.certName, name))) {
return true
}
const isManaged = stripManagedNames.some(name => matchRequiredCertName(cert.certName, name))
return !isManaged
})
}
const next = source.map(cert => ({ ...cert }))
const extras = next.filter(cert => {
if (normalizedPart && normalizeVehicleCertPart(cert.vehiclePart) !== normalizedPart) {
return false
+21 -14
View File
@@ -5,10 +5,20 @@ export const PL_PERSONNEL_CERT_MISSING_SUFFIX = '(证书缺失)'
export const PL_PERSONNEL_DRIVER_REQUIRED_CERT_NAMES = [
'身份证',
'驾驶证',
'从业资格证'
'驾驶员从业资格证'
] as const
export const PL_PERSONNEL_ESCORT_REQUIRED_CERT_NAMES = ['身份证', '从业资格证'] as const
export const PL_PERSONNEL_ESCORT_REQUIRED_CERT_NAMES = [
'身份证',
'押运员从业资格证'
] as const
export const PL_PERSONNEL_BOTH_REQUIRED_CERT_NAMES = [
'身份证',
'驾驶证',
'驾驶员从业资格证',
'押运员从业资格证'
] as const
export type PersonnelCertRole = 'driver' | 'escort'
export type PersonnelType = 'driver' | 'escort' | 'both'
@@ -69,7 +79,10 @@ export function getRequiredPersonnelCertNamesByType(
if (personnelType === 'escort') {
return PL_PERSONNEL_ESCORT_REQUIRED_CERT_NAMES
}
return PL_PERSONNEL_DRIVER_REQUIRED_CERT_NAMES
if (personnelType === 'driver') {
return PL_PERSONNEL_DRIVER_REQUIRED_CERT_NAMES
}
return PL_PERSONNEL_BOTH_REQUIRED_CERT_NAMES
}
/** 车辆关联人员:从人员主数据类型映射为关联角色(司机/押运员) */
@@ -105,7 +118,6 @@ export function isPersonnelCertStrictComplete(
): boolean {
if (!cert?.certName?.trim()) return false
if (!matchRequiredCertName(cert.certName, requiredName)) return false
if (!cert.certNo?.trim()) return false
if (!cert.expireDate) return false
if (!cert.attachmentUrls?.trim()) return false
return true
@@ -117,12 +129,8 @@ export function matchRequiredCertName(
): boolean {
const name = actualName?.trim() || ''
if (!name) return false
if (requiredName === '从业资格证') {
return (
name === '从业资格证' ||
name === '危化品从业资格证' ||
name.includes('从业资格证')
)
if (requiredName === '押运员从业资格证') {
return name === '押运员从业资格证' || name === '押运员证'
}
return name === requiredName
}
@@ -130,7 +138,6 @@ export function matchRequiredCertName(
export function isPersonnelCertUploaded(cert?: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls'>) {
return Boolean(
cert?.certName?.trim() &&
cert?.certNo?.trim() &&
cert?.expireDate &&
cert?.attachmentUrls?.trim()
)
@@ -190,7 +197,8 @@ export function getPersonnelCertMissingSuffix(
export const PL_PERSONNEL_CERT_SHORT_LABELS: Record<string, string> = {
身份证: '身',
驾驶证: '驾',
从业资格证: '业'
驾驶员从业资格证: '业',
押运员从业资格证: '押'
}
export type PersonnelCertBadgeStatus = 'missing' | 'valid' | 'expiring' | 'expired'
@@ -207,10 +215,9 @@ function getPersonnelCertMissingFieldLabels(
requiredName: string
): string[] {
if (!cert || !matchRequiredCertName(cert.certName, requiredName)) {
return ['证号', '有效期', '附件']
return ['有效期', '附件']
}
const missing: string[] = []
if (!cert.certNo?.trim()) missing.push('证号')
if (!cert.expireDate) missing.push('有效期')
if (!cert.attachmentUrls?.trim()) missing.push('附件')
return missing
+1 -3
View File
@@ -79,7 +79,6 @@ export function mergeVehicleCertsByPart<T extends CertRow>(
export function isVehicleCertUploaded(cert?: Pick<CertRow, 'certName' | 'certNo' | 'expireDate' | 'attachmentUrls'>) {
return Boolean(
cert?.certName?.trim() &&
cert?.certNo?.trim() &&
cert?.expireDate &&
cert?.attachmentUrls?.trim()
)
@@ -264,10 +263,9 @@ function getVehicleCertMissingFieldLabels(
requiredName: string
): string[] {
if (!cert || !matchVehicleRequiredCertName(cert.certName, requiredName)) {
return ['证号', '有效期', '附件']
return ['有效期', '附件']
}
const missing: string[] = []
if (!cert.certNo?.trim()) missing.push('证号')
if (!cert.expireDate) missing.push('有效期')
if (!cert.attachmentUrls?.trim()) missing.push('附件')
return missing
+19 -10
View File
@@ -311,6 +311,7 @@
upload-folder="pl/personnel-cert"
:cert-name-options="certNameOptions"
:required-cert-names="requiredPersonnelCertNames"
:strip-managed-cert-names="PL_PERSONNEL_BOTH_REQUIRED_CERT_NAMES"
layout="table"
/>
</el-form>
@@ -388,6 +389,7 @@ import {
getPersonnelAffiliationLabel,
getPersonnelAffiliationTagType,
PL_PERSONNEL_AFFILIATION_OPTIONS,
PL_PERSONNEL_BOTH_REQUIRED_CERT_NAMES,
PL_PERSONNEL_TYPE_OPTIONS
} from '@/utils/pl-personnel-cert'
@@ -431,8 +433,8 @@ const viewDetail = ref<PersonnelDetail | null>(null)
const certNameOptions = [
'身份证',
'驾驶证',
'从业资格证',
'危化品从业资格证',
'驾驶员从业资格证',
'押运员从业资格证',
'押运员证',
'健康证'
]
@@ -462,9 +464,20 @@ const formData = reactive({
})
const requiredPersonnelCertNames = computed(() =>
getRequiredPersonnelCertNamesByType(formData.personnelType)
formData.personnelType
? getRequiredPersonnelCertNamesByType(formData.personnelType)
: []
)
function mergePersonnelCertSlots(list: CertListItem[]) {
if (!formData.personnelType) return list
return mergeCertListWithRequiredSlots(
list,
getRequiredPersonnelCertNamesByType(formData.personnelType),
{ stripManagedNames: PL_PERSONNEL_BOTH_REQUIRED_CERT_NAMES }
)
}
const formRules = {
personnelName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
personnelAffiliation: [{ required: true, message: '请选择人员归属', trigger: 'change' }],
@@ -575,10 +588,7 @@ watch(
() => formData.personnelType,
() => {
if (!formData.personnelType) return
certList.value = mergeCertListWithRequiredSlots(
certList.value,
getRequiredPersonnelCertNamesByType(formData.personnelType)
)
certList.value = mergePersonnelCertSlots(certList.value)
certFormListRef.value?.refreshRequiredSlots?.()
}
)
@@ -715,7 +725,7 @@ async function handleEdit(row: Personnel) {
status: detail.status,
remark: detail.remark || ''
})
certList.value = mergeCertListWithRequiredSlots(
certList.value = mergePersonnelCertSlots(
(detail.certList || []).map(c => ({
_key: c.id || c.certId || `cert-${Math.random().toString(36).slice(2, 10)}`,
certId: c.id || c.certId,
@@ -724,8 +734,7 @@ async function handleEdit(row: Personnel) {
expireDate: c.expireDate,
remindDays: c.remindDays ?? 30,
attachmentUrls: c.attachmentUrls || ''
})),
getRequiredPersonnelCertNamesByType(detail.personnelType || 'both')
}))
)
certFormListRef.value?.refreshRequiredSlots?.()
} catch {
+14 -2
View File
@@ -19,6 +19,14 @@
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item label="产品名称">
<el-input
v-model="searchForm.productName"
placeholder="搜索产品名称"
clearable
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item label="跟进状态">
<el-select v-model="searchForm.followStatus" placeholder="全部" clearable>
<el-option label="草稿" value="draft" />
@@ -398,8 +406,9 @@ const activePlanTypeTab = ref<PlPlanTypeTab>('all')
const searchForm = reactive({
planNo: '',
planType: '',
productName: '',
followStatus: '',
transportStatus: '',
transportStatus: 'arranged',
customerName: '',
archiveStatus: ''
})
@@ -451,6 +460,7 @@ async function fetchData() {
followStatus: searchForm.followStatus || undefined,
transportStatus: searchForm.transportStatus || undefined,
customerName: keyword ? undefined : searchForm.customerName || undefined,
productName: keyword ? undefined : searchForm.productName || undefined,
archiveStatus: searchForm.archiveStatus || undefined,
planDateFrom: dateRange.value?.[0] || undefined,
planDateTo: dateRange.value?.[1] || undefined,
@@ -480,8 +490,9 @@ function handleReset() {
activePlanTypeTab.value = 'all'
searchForm.planNo = ''
searchForm.planType = ''
searchForm.productName = ''
searchForm.followStatus = ''
searchForm.transportStatus = ''
searchForm.transportStatus = 'arranged'
searchForm.customerName = ''
searchForm.archiveStatus = ''
quickSearch.value = ''
@@ -581,6 +592,7 @@ useDbInstantSearch(
() => ({
planNo: searchForm.planNo,
planType: searchForm.planType,
productName: searchForm.productName,
followStatus: searchForm.followStatus,
transportStatus: searchForm.transportStatus,
customerName: searchForm.customerName,