From 7c6ac2e5db334a8bf40e2d5327ea70a789971040 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Fri, 2 Oct 2026 17:15:03 +0800 Subject: [PATCH 1/6] =?UTF-8?q?:sparkles:=20=E6=96=B0=E5=A2=9E=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E5=AE=A1=E6=A0=B8-=E5=8F=B8=E6=9C=BA/=E8=BD=A6?= =?UTF-8?q?=E8=BE=86=E9=A1=B5=EF=BC=8C=E5=AE=A1=E6=A0=B8=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E6=8C=89=E5=AD=97=E6=AE=B5=E7=BA=A7=20diff=20=E5=AF=B9?= =?UTF-8?q?=E6=AF=94=E5=BE=85=E5=AE=A1=E5=80=BC=E4=B8=8E=E7=94=9F=E6=95=88?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 审核对象由「司机行 / 车辆行」改为「一条认证记录」,故新增两个待审列表页 (挂在运力管理下,path /transportCapacity/cert-audit-driver|vehicle,与页面 文件名一一对应)与审核详情弹窗: - api/transportCapacity/certification-audit.js:pending-page / detail / audit 三个接口 - utils/cert-audit.js:快照字段元数据(中文名、控件类型、分组)与两侧快照的 字段级 diff 计算。两侧字段名一致(同一套快照结构),故逐键比较;日期归一化后 判等、长期有效联动展示、未登记字段兜底进「其他字段」 - components/cert-audit-diff:当前生效值与待审提交值并排展示,差异行高亮并标注 新增 / 已修改 / 已清空,支持切换只看变更项;从未认证通过(生效快照为空)时 显示「无对比基准」,不报错 - components/cert-audit-detail:审核详情弹窗(提交信息 + diff + 审核意见), 通过 / 驳回动作。驳回原因前端先拦一道(必填且不超过 200 字),通过时不提交原因; 已终态记录不可再审 - components/cert-audit-list + 两个菜单页:分页与按展示键(司机姓名 / 车牌号)筛选, 审核链接按 cert_certification_audit_{driver,vehicle}_audit 权限点控制 检索过的现有实现:driver.vue / vehicle.vue 的列表与弹窗写法(Avue option + search、 权限点 hasPermission 写法)、image-upload-field、section-card、utils/table-menu.js。 未找到可复用的快照或 diff 实现,故新增 utils/cert-audit.js 纯函数承担比对; 两个列表页除 bizType 与权限点外完全相同,故抽出通用列表组件承接分页/搜索, 避免两页逻辑各自演化。 --- .../transportCapacity/certification-audit.js | 48 +++ .../transportCapacity/cert-audit-driver.js | 49 +++ .../transportCapacity/cert-audit-vehicle.js | 49 +++ src/utils/cert-audit.js | 393 ++++++++++++++++++ .../transportCapacity/cert-audit-driver.vue | 30 ++ .../transportCapacity/cert-audit-vehicle.vue | 31 ++ .../components/cert-audit-detail/main.vue | 279 +++++++++++++ .../components/cert-audit-diff/main.vue | 246 +++++++++++ .../components/cert-audit-list/main.vue | 144 +++++++ 9 files changed, 1269 insertions(+) create mode 100644 src/api/transportCapacity/certification-audit.js create mode 100644 src/option/transportCapacity/cert-audit-driver.js create mode 100644 src/option/transportCapacity/cert-audit-vehicle.js create mode 100644 src/utils/cert-audit.js create mode 100644 src/views/transportCapacity/cert-audit-driver.vue create mode 100644 src/views/transportCapacity/cert-audit-vehicle.vue create mode 100644 src/views/transportCapacity/components/cert-audit-detail/main.vue create mode 100644 src/views/transportCapacity/components/cert-audit-diff/main.vue create mode 100644 src/views/transportCapacity/components/cert-audit-list/main.vue diff --git a/src/api/transportCapacity/certification-audit.js b/src/api/transportCapacity/certification-audit.js new file mode 100644 index 0000000..931505b --- /dev/null +++ b/src/api/transportCapacity/certification-audit.js @@ -0,0 +1,48 @@ +import request from '@/axios'; + +/** + * 待审列表:只含「审核中」的认证记录,按提交时间倒序 + * 审核对象是**一条认证记录**,不是一辆车或一名司机,故必须带 bizType 区分 + */ +export const getPendingPage = (bizType, current, size, params) => { + return request({ + url: '/blade-transport/certification-audit/pending-page', + method: 'get', + params: { + ...params, + bizType, + current, + size, + }, + }); +}; + +/** + * 审核详情:同时返回本次待审快照与该对象当前生效快照,供前端做字段级对比 + * 从未认证通过时 effectiveSnapshotJson 为空串 + */ +export const getAuditDetail = id => { + return request({ + url: '/blade-transport/certification-audit/detail', + method: 'get', + params: { + id, + }, + }); +}; + +/** + * 审核一条认证记录 + * 通过时不传驳回原因;驳回时必须传,且原因不超过 200 字 + */ +export const auditCertification = (id, pass, rejectReason) => { + return request({ + url: '/blade-transport/certification-audit/audit', + method: 'post', + params: { + id, + pass, + ...(rejectReason ? { rejectReason } : {}), + }, + }); +}; diff --git a/src/option/transportCapacity/cert-audit-driver.js b/src/option/transportCapacity/cert-audit-driver.js new file mode 100644 index 0000000..ca64df4 --- /dev/null +++ b/src/option/transportCapacity/cert-audit-driver.js @@ -0,0 +1,49 @@ +export const option = { + height: 'auto', + calcHeight: 32, + tip: false, + searchShow: true, + searchMenuSpan: 24, + searchIcon: true, + searchIndex: 4, + searchMenuPosition: 'right', + border: true, + index: true, + indexLabel: '序号', + indexWidth: 70, + selection: false, + addBtn: false, + editBtn: false, + delBtn: false, + viewBtn: false, + columnBtn: false, + menuWidth: 110, + column: [ + { + label: '司机姓名', + prop: 'bizKey', + search: true, + searchOrder: 1, + minWidth: 140, + }, + { + label: '审核状态', + prop: 'statusText', + slot: true, + width: 110, + align: 'center', + }, + { + label: '提交人', + prop: 'submitUserName', + minWidth: 120, + }, + { + label: '提交时间', + prop: 'submitTime', + slot: true, + minWidth: 170, + align: 'center', + }, + ], +}; diff --git a/src/option/transportCapacity/cert-audit-vehicle.js b/src/option/transportCapacity/cert-audit-vehicle.js new file mode 100644 index 0000000..8111393 --- /dev/null +++ b/src/option/transportCapacity/cert-audit-vehicle.js @@ -0,0 +1,49 @@ +export const option = { + height: 'auto', + calcHeight: 32, + tip: false, + searchShow: true, + searchMenuSpan: 24, + searchIcon: true, + searchIndex: 4, + searchMenuPosition: 'right', + border: true, + index: true, + indexLabel: '序号', + indexWidth: 70, + selection: false, + addBtn: false, + editBtn: false, + delBtn: false, + viewBtn: false, + columnBtn: false, + menuWidth: 110, + column: [ + { + label: '车牌号', + prop: 'bizKey', + search: true, + searchOrder: 1, + minWidth: 140, + }, + { + label: '审核状态', + prop: 'statusText', + slot: true, + width: 110, + align: 'center', + }, + { + label: '提交人', + prop: 'submitUserName', + minWidth: 120, + }, + { + label: '提交时间', + prop: 'submitTime', + slot: true, + minWidth: 170, + align: 'center', + }, + ], +}; diff --git a/src/utils/cert-audit.js b/src/utils/cert-audit.js new file mode 100644 index 0000000..f248e1f --- /dev/null +++ b/src/utils/cert-audit.js @@ -0,0 +1,393 @@ +/** + * 认证审核通用工具 + * + * 认证对象(司机 / 车辆)每次提交产生一条不可变快照记录,「审核」针对的是**一条记录**。 + * 记录分两侧:本次**待审快照**与该对象**当前生效快照**(最后一次审核通过的那条)。 + * 待审内容在审核通过前不影响生效值,所以审核页必须逐字段对比两侧,而不是只看一条当前值。 + * + * 本文件提供三件事: + * 1. 审核状态文案与标签(与后端 statusText 同一套词表); + * 2. 快照字段的展示元数据(中文名、控件类型、分组); + * 3. 两侧快照的字段级 diff 计算,含「从未认证通过、无对比基准」的处理。 + * + * 两侧快照字段名完全一致(同一套结构),故可逐键比较。 + */ + +const EMPTY_TEXT = '—'; + +const TYPE_IMAGE = 'image'; +const TYPE_DATE = 'date'; +const TYPE_LONG_TERM = 'longTerm'; + +/** 审核状态取值 */ +export const CERT_RECORD_STATUS = { + PENDING: 0, + APPROVED: 1, + REJECTED: 2, + VOIDED: 3, +}; + +/** 状态文案统一为:审核中 / 已通过 / 已驳回 / 已作废 */ +const CERT_STATUS_TAG = { + 0: { label: '审核中', type: 'warning' }, + 1: { label: '已通过', type: 'success' }, + 2: { label: '已驳回', type: 'danger' }, + 3: { label: '已作废', type: 'info' }, +}; + +/** 变更类型 → 差异标签 */ +export const CERT_CHANGE_TAG = { + added: { label: '新增', type: 'success' }, + modified: { label: '已修改', type: 'warning' }, + cleared: { label: '已清空', type: 'danger' }, +}; + +/** + * 「最新提交状态」展示缓存 → 标签 + * + * 缓存为空表示该对象从未提交过认证(即没有生效快照)。 + * 注意它只是最近一次提交的状态,不等于「是否已认证」——后者由生效快照指针决定。 + */ +export const certStatusTag = status => { + if (status === null || status === undefined || status === '') { + return { label: '未认证', type: 'info' }; + } + return CERT_STATUS_TAG[status] || { label: EMPTY_TEXT, type: 'info' }; +}; + +const pad = value => String(value).padStart(2, '0'); + +/** 后端若改用时间戳毫秒序列化日期,也能落成日期文案 */ +const EPOCH_PATTERN = /^\d{13}$/; + +const isEpochText = text => EPOCH_PATTERN.test(text); + +const toDate = text => { + const date = new Date(Number(text)); + return Number.isNaN(date.getTime()) ? null : date; +}; + +/** 提交时间等日期时间展示,兼容 `yyyy-MM-dd HH:mm:ss`、ISO 串与时间戳毫秒 */ +export const formatCertDateTime = value => { + if (value === null || value === undefined || value === '') return EMPTY_TEXT; + const text = String(value).trim(); + const matched = text.match( + /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2}))?/ + ); + if (matched) { + return ( + `${matched[1]}-${pad(matched[2])}-${pad(matched[3])} ` + + `${pad(matched[4])}:${pad(matched[5])}:${pad(matched[6] || 0)}` + ); + } + const date = isEpochText(text) ? toDate(text) : null; + if (!date) return text; + return ( + `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` + + `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` + ); +}; + +/** + * 快照字段元数据 + * + * 字段名与后端提交 DTO 的 JSON 键一致(司机 PersonCertSubmitRequest / 车辆 VehicleCertSubmitRequest), + * 两侧快照共用同一套结构,故同时用于标签与比较规则。 + */ +const CERT_SNAPSHOT_GROUPS = { + driver: [ + { + title: '身份信息', + fields: [ + { key: 'realName', label: '真实姓名' }, + { key: 'idCardNo', label: '身份证号' }, + { key: 'idCardFrontUrl', label: '身份证正面照', type: TYPE_IMAGE }, + { key: 'idCardBackUrl', label: '身份证反面照', type: TYPE_IMAGE }, + { + key: 'idCardValidStart', + label: '身份证有效期起', + type: TYPE_DATE, + longTermKey: 'idCardLongTerm', + }, + { + key: 'idCardValidEnd', + label: '身份证有效期止', + type: TYPE_DATE, + longTermKey: 'idCardLongTerm', + }, + { key: 'idCardLongTerm', label: '身份证长期有效', type: TYPE_LONG_TERM }, + { key: 'headPhoto', label: '大头照', type: TYPE_IMAGE }, + ], + }, + { + title: '驾驶证信息', + fields: [ + { key: 'driverFileNo', label: '驾驶证档案编号' }, + { key: 'driverClass', label: '准驾车型' }, + { key: 'driverLicenseFrontUrl', label: '驾驶证主页照片', type: TYPE_IMAGE }, + { key: 'driverLicenseBackUrl', label: '驾驶证副页照片', type: TYPE_IMAGE }, + { + key: 'driverValidStart', + label: '驾驶证有效期起', + type: TYPE_DATE, + longTermKey: 'driverLongTerm', + }, + { + key: 'driverValidEnd', + label: '驾驶证有效期止', + type: TYPE_DATE, + longTermKey: 'driverLongTerm', + }, + { key: 'driverLongTerm', label: '驾驶证长期有效', type: TYPE_LONG_TERM }, + ], + }, + { + title: '从业资格证信息', + fields: [ + { key: 'qualificationNo', label: '从业资格证号' }, + { key: 'qualificationUrl', label: '从业资格证照片', type: TYPE_IMAGE }, + { key: 'qualificationValidEnd', label: '从业资格证有效期止', type: TYPE_DATE }, + ], + }, + ], + vehicle: [ + { + title: '车辆基本信息', + fields: [ + { key: 'plateNo', label: '车牌号' }, + { key: 'vehicleType', label: '车辆类型' }, + { key: 'plateColor', label: '车牌颜色' }, + { key: 'vin', label: '车辆识别代号(车架号)' }, + { key: 'engineNo', label: '发动机号' }, + ], + }, + { + title: '行驶证信息', + fields: [ + { key: 'licenseNo', label: '行驶证档案编号' }, + { key: 'licenseFrontUrl', label: '行驶证主页照片', type: TYPE_IMAGE }, + { key: 'licenseBackUrl', label: '行驶证副页照片', type: TYPE_IMAGE }, + { + key: 'licenseValidStart', + label: '行驶证有效期起', + type: TYPE_DATE, + longTermKey: 'licenseLongTerm', + }, + { + key: 'licenseValidEnd', + label: '行驶证有效期止', + type: TYPE_DATE, + longTermKey: 'licenseLongTerm', + }, + { key: 'licenseLongTerm', label: '行驶证长期有效', type: TYPE_LONG_TERM }, + ], + }, + { + title: '道路运输证信息', + fields: [ + { key: 'roadTransportNo', label: '道路运输证号' }, + { key: 'roadTransportUrl', label: '道路运输证照片', type: TYPE_IMAGE }, + { + key: 'roadTransportValidStart', + label: '道路运输证有效期起', + type: TYPE_DATE, + longTermKey: 'roadTransportLongTerm', + }, + { + key: 'roadTransportValidEnd', + label: '道路运输证有效期止', + type: TYPE_DATE, + longTermKey: 'roadTransportLongTerm', + }, + { key: 'roadTransportLongTerm', label: '道路运输证长期有效', type: TYPE_LONG_TERM }, + ], + }, + ], +}; + +/** 提交元信息与主键不参与对比(两侧必相同,展示出来只是噪音) */ +const IGNORED_KEYS = [ + 'id', + 'bizType', + 'bizId', + 'bizKey', + 'certificationStatus', + 'snapshotJson', + 'pendingSnapshotJson', + 'effectiveSnapshotJson', +]; + +/** + * 解析快照 JSON + * + * 后端把 null 序列化为空串,故空串 / 'null' 都按「没有快照」处理,不算解析失败。 + */ +const readSnapshot = json => { + if (json && typeof json === 'object') { + return { value: Object.keys(json).length ? json : null, invalid: false }; + } + const text = String(json === null || json === undefined ? '' : json).trim(); + if (!text || text === 'null') { + return { value: null, invalid: false }; + } + try { + const parsed = JSON.parse(text); + if (parsed && typeof parsed === 'object') { + return { value: parsed, invalid: false }; + } + return { value: null, invalid: true }; + } catch (error) { + return { value: null, invalid: true }; + } +}; + +/** 解析快照 JSON,无快照或解析失败时返回 null */ +export const parseSnapshot = json => readSnapshot(json).value; + +const isEmptyValue = value => value === null || value === undefined || value === ''; + +const isLongTermOn = value => value === 1 || value === '1' || value === true; + +/** 日期归一化到 yyyy-MM-dd,便于 `2026-01-01` 与 `2026-01-01T00:00:00` 判等 */ +const normalizeDate = value => { + if (isEmptyValue(value)) return ''; + const text = String(value).trim(); + const matched = text.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/); + if (matched) return `${matched[1]}-${pad(matched[2])}-${pad(matched[3])}`; + const date = isEpochText(text) ? toDate(text) : null; + if (date) { + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; + } + return text; +}; + +/** 比较用的归一化取值:展示相同即视为未变更 */ +const canonicalOf = (snapshot, field) => { + const value = snapshot ? snapshot[field.key] : undefined; + if (field.type === TYPE_LONG_TERM) { + if (isEmptyValue(value)) return ''; + return isLongTermOn(value) ? '1' : '0'; + } + if (field.type === TYPE_DATE) { + if (field.longTermKey && isLongTermOn(snapshot && snapshot[field.longTermKey])) { + return '长期有效'; + } + return normalizeDate(value); + } + return isEmptyValue(value) ? '' : String(value).trim(); +}; + +/** 展示文案:日期型受「长期有效」影响,图片型返回 URL 由组件渲染缩略图 */ +const textOf = (snapshot, field) => { + const value = snapshot ? snapshot[field.key] : undefined; + if (field.type === TYPE_IMAGE) { + return isEmptyValue(value) ? '' : String(value); + } + if (field.type === TYPE_LONG_TERM) { + if (isEmptyValue(value)) return EMPTY_TEXT; + return isLongTermOn(value) ? '是' : '否'; + } + if (field.type === TYPE_DATE) { + if (field.longTermKey && isLongTermOn(snapshot && snapshot[field.longTermKey])) { + return '长期有效'; + } + return normalizeDate(value) || EMPTY_TEXT; + } + return isEmptyValue(value) ? EMPTY_TEXT : String(value); +}; + +const buildRow = (field, pendingSnapshot, effectiveSnapshot, hasBaseline) => { + const type = field.type || 'text'; + const target = { ...field, type }; + const pendingCanonical = canonicalOf(pendingSnapshot, target); + const effectiveCanonical = canonicalOf(effectiveSnapshot, target); + let changeType = ''; + if (hasBaseline && pendingCanonical !== effectiveCanonical) { + if (!effectiveCanonical) { + changeType = 'added'; + } else if (!pendingCanonical) { + changeType = 'cleared'; + } else { + changeType = 'modified'; + } + } + return { + key: field.key, + label: field.label, + type, + effective: { + raw: effectiveSnapshot ? effectiveSnapshot[field.key] : undefined, + text: textOf(effectiveSnapshot, target), + }, + pending: { + raw: pendingSnapshot ? pendingSnapshot[field.key] : undefined, + text: textOf(pendingSnapshot, target), + }, + changed: Boolean(changeType), + changeType, + }; +}; + +const unionKeys = (pendingSnapshot, effectiveSnapshot) => { + const keys = []; + [pendingSnapshot, effectiveSnapshot].forEach(snapshot => { + Object.keys(snapshot || {}).forEach(key => { + if (!keys.includes(key)) keys.push(key); + }); + }); + return keys; +}; + +/** + * 构造「待审快照 vs 当前生效值」的字段级 diff + * + * @param {string} bizType 认证对象类型:driver / vehicle + * @param {string} pendingJson 本次待审快照 JSON + * @param {string} effectiveJson 该对象当前生效快照 JSON,从未认证通过时为空 + * @returns {Object} { hasBaseline, groups, totalCount, changedCount, ... } + * hasBaseline 为 false 表示无对比基准(从未认证通过),此时不产出任何变更标记 + */ +export const buildSnapshotDiff = (bizType, pendingJson, effectiveJson) => { + const pending = readSnapshot(pendingJson); + const effective = readSnapshot(effectiveJson); + const hasBaseline = effective.value !== null; + const groupsConfig = CERT_SNAPSHOT_GROUPS[bizType] || []; + const knownKeys = []; + + const groups = groupsConfig.map(group => { + const rows = group.fields.map(field => { + knownKeys.push(field.key); + return buildRow(field, pending.value, effective.value, hasBaseline); + }); + return { + title: group.title, + rows, + changedCount: rows.filter(row => row.changed).length, + }; + }); + + // 元数据未登记的字段照样展示,避免后端新增字段时审核员看不到变化 + const extraKeys = unionKeys(pending.value, effective.value).filter( + key => !knownKeys.includes(key) && !IGNORED_KEYS.includes(key) + ); + if (extraKeys.length) { + const rows = extraKeys.map(key => + buildRow({ key, label: key }, pending.value, effective.value, hasBaseline) + ); + groups.push({ + title: '其他字段', + rows, + changedCount: rows.filter(row => row.changed).length, + }); + } + + const rows = groups.reduce((all, group) => all.concat(group.rows), []); + return { + hasBaseline, + pendingInvalid: pending.invalid, + effectiveInvalid: effective.invalid, + groups, + totalCount: rows.length, + changedCount: rows.filter(row => row.changed).length, + }; +}; diff --git a/src/views/transportCapacity/cert-audit-driver.vue b/src/views/transportCapacity/cert-audit-driver.vue new file mode 100644 index 0000000..c746b2b --- /dev/null +++ b/src/views/transportCapacity/cert-audit-driver.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/views/transportCapacity/cert-audit-vehicle.vue b/src/views/transportCapacity/cert-audit-vehicle.vue new file mode 100644 index 0000000..3c3786a --- /dev/null +++ b/src/views/transportCapacity/cert-audit-vehicle.vue @@ -0,0 +1,31 @@ + + + diff --git a/src/views/transportCapacity/components/cert-audit-detail/main.vue b/src/views/transportCapacity/components/cert-audit-detail/main.vue new file mode 100644 index 0000000..825f665 --- /dev/null +++ b/src/views/transportCapacity/components/cert-audit-detail/main.vue @@ -0,0 +1,279 @@ + + + + + diff --git a/src/views/transportCapacity/components/cert-audit-diff/main.vue b/src/views/transportCapacity/components/cert-audit-diff/main.vue new file mode 100644 index 0000000..1137418 --- /dev/null +++ b/src/views/transportCapacity/components/cert-audit-diff/main.vue @@ -0,0 +1,246 @@ + + + + + diff --git a/src/views/transportCapacity/components/cert-audit-list/main.vue b/src/views/transportCapacity/components/cert-audit-list/main.vue new file mode 100644 index 0000000..3f95103 --- /dev/null +++ b/src/views/transportCapacity/components/cert-audit-list/main.vue @@ -0,0 +1,144 @@ + + + From 71a6f7c4f8e7d3253380e4c25842cfb48eb2ec5c Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Fri, 2 Oct 2026 17:15:09 +0800 Subject: [PATCH 2/6] =?UTF-8?q?:recycle:=20=E8=BD=A6=E8=BE=86=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=A7=BB=E9=99=A4=E6=97=A7=E8=AE=A4=E8=AF=81=E5=AE=A1?= =?UTF-8?q?=E6=A0=B8=E5=85=A5=E5=8F=A3=EF=BC=8C=E8=AE=A4=E8=AF=81=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=94=B9=E7=94=A8=E7=BB=9F=E4=B8=80=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E8=AF=8D=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 认证审核职责已迁到「认证审核-车辆」页:审核对象是一条认证记录,生效值与 待审值分离后,原来「看车辆行当前值直接通过/驳回」的单条模型语义不再成立。 - vehicle.vue:移除操作列的「审核」入口与认证审核弹窗及其方法、样式, 同时删除 api/transportCapacity/transport-vehicle.js 的 auditCertification 封装 (后端旧接口仅保留兼容,前端不再调用) - 认证状态列与筛选项统一为 审核中 / 已通过 / 已驳回 / 已作废(新增 已作废, 原先的「认证中/已认证」不再使用),缓存为空时显示「未认证」 - 去掉按证件到期日期推断「已认证 / 已过期」的兜底:是否已认证由生效快照指针 决定(曾审核通过即永久保持),与证件日期无关;到期信息仍由列表顶部到期标签 与日期列标色展示 - driver.vue 选择车辆弹窗的车辆认证状态文案一并统一 --- .../transportCapacity/transport-vehicle.js | 8 - .../transportCapacity/transport-vehicle.js | 7 +- src/views/transportCapacity/driver.vue | 9 +- src/views/transportCapacity/vehicle.vue | 394 +----------------- 4 files changed, 14 insertions(+), 404 deletions(-) diff --git a/src/api/transportCapacity/transport-vehicle.js b/src/api/transportCapacity/transport-vehicle.js index f2bfb67..0eed04c 100644 --- a/src/api/transportCapacity/transport-vehicle.js +++ b/src/api/transportCapacity/transport-vehicle.js @@ -51,14 +51,6 @@ export const changeStatus = (id, status) => { }); }; -export const auditCertification = (id, certificationStatus, rejectReason) => { - return request({ - url: '/blade-transport/transport-vehicle/audit-certification', - method: 'post', - params: { id, certificationStatus, rejectReason }, - }); -}; - export const getExpiryStat = params => { return request({ url: '/blade-transport/transport-vehicle/expiry-stat', diff --git a/src/option/transportCapacity/transport-vehicle.js b/src/option/transportCapacity/transport-vehicle.js index df05326..ffe1bae 100644 --- a/src/option/transportCapacity/transport-vehicle.js +++ b/src/option/transportCapacity/transport-vehicle.js @@ -95,6 +95,8 @@ export const option = { align: 'center', }, { + // 这里是「最新一次提交的状态」缓存,不等于该车当前是否已认证: + // 已认证由生效快照指针决定,曾审核通过即永久保持,重新提交不会让它退回审核中 label: '认证状态', prop: 'certificationStatus', slot: true, @@ -104,9 +106,10 @@ export const option = { type: 'select', dicData: [ { label: '全部', value: '' }, - { label: '认证中', value: 0 }, - { label: '已认证', value: 1 }, + { label: '审核中', value: 0 }, + { label: '已通过', value: 1 }, { label: '已驳回', value: 2 }, + { label: '已作废', value: 3 }, ], minWidth: 110, align: 'center', diff --git a/src/views/transportCapacity/driver.vue b/src/views/transportCapacity/driver.vue index 1066ad6..dad42da 100644 --- a/src/views/transportCapacity/driver.vue +++ b/src/views/transportCapacity/driver.vue @@ -649,6 +649,7 @@ import { getDictionary } from '@/api/system/dictbiz'; import { getLazyTree } from '@/api/base/region'; import { exportBlob } from '@/api/common'; import { getToken, isAdministrator } from '@/utils/auth'; +import { certStatusTag } from '@/utils/cert-audit'; import { getUploadHeaders } from '@/utils/upload'; import { downloadXls } from '@/utils/util'; import ImageUploadField from '@/components/image-upload-field/main.vue'; @@ -1233,13 +1234,9 @@ export default { this.driverForm.drivingVehicle = row.plateNo || ''; this.vehicleSelectorVisible = false; }, + // 车辆认证状态展示的是「最新一次提交的状态」缓存,与审核页使用同一套状态词表 getVehicleCertificationStatus(row) { - const statusMap = { - 0: { label: '认证中', type: 'warning' }, - 1: { label: '已认证', type: 'success' }, - 2: { label: '已驳回', type: 'danger' }, - }; - return statusMap[row.certificationStatus] || { label: '-', type: 'info' }; + return certStatusTag(row.certificationStatus); }, setImage(prop, url) { this.driverForm[prop] = url; diff --git a/src/views/transportCapacity/vehicle.vue b/src/views/transportCapacity/vehicle.vue index 1bfa225..087594a 100644 --- a/src/views/transportCapacity/vehicle.vue +++ b/src/views/transportCapacity/vehicle.vue @@ -105,9 +105,6 @@ 修改 综合台账 - - 审核 - - - - - - - - {{ auditValue('plateNo') }} - - - - - {{ auditValue('vehicleType') }} - - - - -
-
- 长 - - {{ auditValueWithUnit('outerLength', 'mm') }} - -
-
- 宽 - - {{ auditValueWithUnit('outerWidth', 'mm') }} - -
-
- 高 - - {{ auditValueWithUnit('outerHeight', 'mm') }} - -
-
-
-
-
- - - - - {{ auditValueWithUnit('approvedLoadKg', 'KG') }} - - - - - - - {{ auditValueWithUnit('tractionMassKg', 'KG') }} - - - - - - {{ auditValue('businessRelation') }} - - - - - {{ auditValue('energyType') }} - - - - - - - - {{ auditValue('compulsoryScrapDate') }} - - - - - - {{ auditValue('customsRecordNo') }} - - - - - - {{ auditValue('organizationName') }} - - - - - - {{ auditValue('useDepartment') }} - - - -
- - - - - - - {{ auditValue('drivingLicenseEndDate') }} - - - - - - - {{ auditValue('roadTransportCertNo') }} - - - - - - - {{ auditValue('roadTransportCertEndDate') }} - - - - - - - {{ auditValue('annualReviewEndDate') }} - - - - - - - - {{ auditValue('registrationNo') }} - - - - - {{ auditValue('registrationDate') }} - - - - - {{ auditValue('drivingLicenseNo') }} - - - - - - {{ auditValue('drivingLicenseStartDate') }} - - - - - - - - - {{ auditValue('roadTransportCertStartDate') }} - - - - - - - - {{ auditValue('remark') }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - -
- { - this.certificationAuditForm = { ...(res.data.data || {}) }; - this.certificationRejectReason = ''; - this.showRejectReason = false; - this.certificationAuditBox = true; - }); - }, - auditValue(prop) { - // 长期有效字段:勾选后显示「长期有效」,避免与未填写的「-」混淆 - const longTermMap = { - drivingLicenseEndDate: 'drivingLicenseLongTerm', - roadTransportCertEndDate: 'roadTransportCertLongTerm', - annualReviewEndDate: 'annualReviewLongTerm', - compulsoryScrapDate: 'compulsoryScrapLongTerm', - }; - const longTermProp = longTermMap[prop]; - if (longTermProp && this.certificationAuditForm[longTermProp] === 1) { - return '长期有效'; - } - const value = this.certificationAuditForm[prop]; - return value === undefined || value === null || value === '' ? '-' : value; - }, - auditValueWithUnit(prop, unit) { - const value = this.auditValue(prop); - return value === '-' ? value : `${value}${unit}`; - }, - handleCertificationApprove() { - auditCertification(this.certificationAuditForm.id, 1).then(() => { - this.$message.success('认证已通过'); - this.certificationAuditBox = false; - this.onLoad(this.page); - }); - }, - handleCertificationReject() { - if (!this.showRejectReason) { this.showRejectReason = true; return; } - if (!this.certificationRejectReason.trim()) { this.$message.warning('请输入认证驳回原因'); return; } - auditCertification(this.certificationAuditForm.id, 2, this.certificationRejectReason).then(() => { - this.$message.success('认证已驳回'); - this.certificationAuditBox = false; - this.onLoad(this.page); - }); - }, resetVehicle() { this.vehicleForm = emptyForm(); this.vehicleCertificateUploads = {}; @@ -1648,26 +1333,11 @@ export default { } return ''; }, + // 认证状态展示的是「最新一次提交的状态」缓存,不代表该车当前是否已认证 + // (是否已认证由生效快照指针决定,即曾审核通过即永久保持)。 + // 证书是否临近到期另有列表顶部的到期标签与日期列标色展示,不在这里推断。 getCertificationStatus(row) { - if (row.certificationStatus === 0) return { label: '认证中', type: 'warning' }; - if (row.certificationStatus === 1) return { label: '已认证', type: 'success' }; - if (row.certificationStatus === 2) return { label: '已驳回', type: 'danger' }; - const today = this.$dayjs().startOf('day'); - const certificateDates = [ - [row.drivingLicenseEndDate, row.drivingLicenseLongTerm], - [row.roadTransportCertEndDate, row.roadTransportCertLongTerm], - ]; - if (certificateDates.some(([date, longTerm]) => longTerm !== 1 && !date)) { - return { label: '未认证', type: 'info' }; - } - if ( - certificateDates.some( - ([date, longTerm]) => longTerm !== 1 && this.$dayjs(date).isBefore(today, 'day') - ) - ) { - return { label: '已过期', type: 'danger' }; - } - return { label: '已认证', type: 'success' }; + return certStatusTag(row.certificationStatus); }, }, }; @@ -1695,9 +1365,7 @@ export default { } } -// 车辆认证审核弹窗:与编辑弹窗一致,灰底 + section-card 白卡分组 -.certification-audit, -.certification-audit__reason, +// 编辑弹窗:灰底 + section-card 白卡分组 .vehicle-form { :deep(.el-form-item__label) { height: auto; @@ -1723,60 +1391,10 @@ export default { line-height: 18px; margin-right: 4px; } -} -.certification-audit { - :deep(.el-form-item__label) { - flex: 0 0 calc(4em + 12px) !important; - width: calc(4em + 12px) !important; - max-width: calc(4em + 12px); - } -} - -.certification-audit__reason { :deep(.el-form-item__label) { flex: 0 0 calc(4em + 24px) !important; width: calc(4em + 24px) !important; - max-width: calc(4em + 24px); - } -} - -.vehicle-form { - :deep(.el-form-item__label) { - flex: 0 0 calc(4em + 24px) !important; - width: calc(4em + 24px) !important; - } -} - -.certification-audit { - &__value { - display: block; - color: #303133; - word-break: break-all; - } - - // 外廓尺寸长/宽/高组合,排版复用编辑弹窗的 dimension-group - .dimension-group { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 8px; - align-items: center; - } - - .dimension-group__item { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; - } - - .dimension-group__label { - flex-shrink: 0; - color: #303133; - } - - &__reason { - margin-top: 12px; } } From b8d74de4a42f5515ec2f8e06dedb44ae963971cb Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Fri, 2 Oct 2026 19:58:17 +0800 Subject: [PATCH 3/6] =?UTF-8?q?:sparkles:=20=E8=AE=A4=E8=AF=81=E5=AE=A1?= =?UTF-8?q?=E6=A0=B8=E5=88=97=E8=A1=A8=E6=96=B0=E5=A2=9E=E5=BE=85=E5=AE=A1?= =?UTF-8?q?=E6=A0=B8/=E5=B7=B2=E9=80=9A=E8=BF=87/=E5=B7=B2=E9=A9=B3?= =?UTF-8?q?=E5=9B=9E=E9=A1=B5=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 审核对象是「一条认证记录」,每次提交产生一条,原列表只能看审核中的, 已通过与已驳回的记录无从查看。 三个页签默认停在待审核;切换页签回到第一页;只有审核中的记录显示「审核」, 终态记录显示「查看」(详情弹窗本就对终态禁用通过/驳回)。 --- .../transportCapacity/certification-audit.js | 3 +- .../components/cert-audit-list/main.vue | 59 +++++++++++++++++-- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/api/transportCapacity/certification-audit.js b/src/api/transportCapacity/certification-audit.js index 931505b..2501a8c 100644 --- a/src/api/transportCapacity/certification-audit.js +++ b/src/api/transportCapacity/certification-audit.js @@ -1,8 +1,9 @@ import request from '@/axios'; /** - * 待审列表:只含「审核中」的认证记录,按提交时间倒序 + * 认证记录列表:按审核状态分页,按提交时间倒序 * 审核对象是**一条认证记录**,不是一辆车或一名司机,故必须带 bizType 区分 + * status 为审核状态(0审核中 1已通过 2已驳回 3已作废);不传时后端按 0 处理 */ export const getPendingPage = (bizType, current, size, params) => { return request({ diff --git a/src/views/transportCapacity/components/cert-audit-list/main.vue b/src/views/transportCapacity/components/cert-audit-list/main.vue index 3f95103..4e34b59 100644 --- a/src/views/transportCapacity/components/cert-audit-list/main.vue +++ b/src/views/transportCapacity/components/cert-audit-list/main.vue @@ -13,6 +13,21 @@ @refresh-change="refreshChange" @on-load="onLoad" > + + @@ -38,14 +56,15 @@ + + From 715c0dc00c094441e6ee4afd647f6e75e8046e1c Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Sat, 3 Oct 2026 14:36:08 +0800 Subject: [PATCH 4/6] =?UTF-8?q?:sparkles:=20=E5=8F=B8=E6=9C=BA=E5=AE=A1?= =?UTF-8?q?=E6=A0=B8=E4=B8=8E=E8=BD=A6=E8=BE=86=E5=AE=A1=E6=A0=B8=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=8F=B8=E6=9C=BA=E6=89=8B=E6=9C=BA=E5=8F=B7=EF=BC=8C?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=8C=89=E5=BD=93=E5=89=8D=E5=A7=93=E5=90=8D?= =?UTF-8?q?=E5=8F=96=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两个审核页的表格与搜索栏都加上「司机手机号」,姓名列改绑司机档案的当前值 (原绑提交时的历史姓名)。同时去掉「提交人」列:真实路径下提交人就是司机本人, 两列必然同值,而存量记录本就没有提交人,留着一列空值加同值没有意义。 手机号不脱敏:审核员靠它联系司机核实材料,与司机管理页一致。 --- .../transportCapacity/cert-audit-driver.js | 18 +++++++++------- .../transportCapacity/cert-audit-vehicle.js | 21 +++++++++++++------ .../components/cert-audit-list/main.vue | 3 +++ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/option/transportCapacity/cert-audit-driver.js b/src/option/transportCapacity/cert-audit-driver.js index ca64df4..71b9856 100644 --- a/src/option/transportCapacity/cert-audit-driver.js +++ b/src/option/transportCapacity/cert-audit-driver.js @@ -21,10 +21,17 @@ export const option = { column: [ { label: '司机姓名', - prop: 'bizKey', + prop: 'driverName', search: true, - searchOrder: 1, - minWidth: 140, + searchOrder: 3, + minWidth: 120, + }, + { + label: '司机手机号', + prop: 'driverMobile', + search: true, + searchOrder: 2, + minWidth: 130, }, { label: '审核状态', @@ -33,11 +40,6 @@ export const option = { width: 110, align: 'center', }, - { - label: '提交人', - prop: 'submitUserName', - minWidth: 120, - }, { label: '提交时间', prop: 'submitTime', diff --git a/src/option/transportCapacity/cert-audit-vehicle.js b/src/option/transportCapacity/cert-audit-vehicle.js index 8111393..5e93caf 100644 --- a/src/option/transportCapacity/cert-audit-vehicle.js +++ b/src/option/transportCapacity/cert-audit-vehicle.js @@ -23,9 +23,23 @@ export const option = { label: '车牌号', prop: 'bizKey', search: true, - searchOrder: 1, + searchOrder: 3, minWidth: 140, }, + { + label: '司机姓名', + prop: 'driverName', + search: true, + searchOrder: 2, + minWidth: 120, + }, + { + label: '司机手机号', + prop: 'driverMobile', + search: true, + searchOrder: 1, + minWidth: 130, + }, { label: '审核状态', prop: 'statusText', @@ -33,11 +47,6 @@ export const option = { width: 110, align: 'center', }, - { - label: '提交人', - prop: 'submitUserName', - minWidth: 120, - }, { label: '提交时间', prop: 'submitTime', diff --git a/src/views/transportCapacity/components/cert-audit-list/main.vue b/src/views/transportCapacity/components/cert-audit-list/main.vue index 4e34b59..4c66cdc 100644 --- a/src/views/transportCapacity/components/cert-audit-list/main.vue +++ b/src/views/transportCapacity/components/cert-audit-list/main.vue @@ -66,6 +66,9 @@ import CertAuditDetail from '../cert-audit-detail/main.vue'; * 审核对象是「一条认证记录」:每次提交产生一条,按审核状态分为 * 待审核 / 已通过 / 已驳回三个页签,默认停在待审核。 * 列表只负责挑选记录,具体改了什么由审核详情弹窗做字段级对比。 + * + * 列表上的司机姓名与手机号取的是**司机档案当前值**(认证记录里的姓名是提交时的历史值, + * 手机号则从来不在快照里)。审核员靠这两列认人、联系核实,故不脱敏。 */ export default { name: 'CertAuditList', From ce54d2134498d994a61a9c3693602f4c888ce82b Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Sat, 3 Oct 2026 16:34:59 +0800 Subject: [PATCH 5/6] =?UTF-8?q?:sparkles:=20=E6=96=B0=E5=A2=9E/=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E8=BD=A6=E8=BE=86=E8=A1=A8=E5=8D=95=E8=A1=A5=E3=80=8C?= =?UTF-8?q?=E6=89=80=E6=9C=89=E4=BA=BA=E3=80=8D=E4=B8=8E=E3=80=8C=E8=BD=A6?= =?UTF-8?q?=E8=BE=86=E8=AF=86=E5=88=AB=E4=BB=A3=E5=8F=B7=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 车辆表新增这两列(所有人:行驶证登记的单位名称,与「所属组织」是两个字段; 车辆识别代号:司机端车辆认证一直在收这个值,此前车辆档案里没有对应列, 只存在于认证快照中)。管理端表单同步补上,否则两个入口建出来的车字段不一致。 所有人为必填(行驶证上的登记主体),车辆识别代号选填、填了校验 17 位。 --- src/views/transportCapacity/vehicle.vue | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/views/transportCapacity/vehicle.vue b/src/views/transportCapacity/vehicle.vue index 087594a..42553a2 100644 --- a/src/views/transportCapacity/vehicle.vue +++ b/src/views/transportCapacity/vehicle.vue @@ -168,6 +168,24 @@ + + + + + + + + + +
@@ -560,8 +578,10 @@ const emptyForm = () => ({ organizationName: '', useDepartment: '', plateNo: '', + ownerName: '', plateColor: '', vehicleType: '', + vin: '', outerLength: undefined, outerWidth: undefined, outerHeight: undefined, @@ -650,6 +670,8 @@ export default { { validator: this.validatePlateNo, trigger: 'change' }, ], vehicleType: [{ required: true, message: '请选择车辆类型', trigger: 'change' }], + ownerName: [{ required: true, message: '请输入所有人', trigger: 'blur' }], + vin: [{ validator: this.validateVin, trigger: 'blur' }], approvedLoadKg: [{ required: true, message: '请输入核定载质量', trigger: 'blur' }], businessRelation: [{ required: true, message: '请选择业务关系', trigger: 'change' }], energyType: [{ required: true, message: '请选择能源类型', trigger: 'change' }], @@ -783,6 +805,24 @@ export default { } callback(); }, + // 车辆识别代号(车架号)为选填:留空放行,填了就必须是 17 位 + validateVin(rule, value, callback) { + const vin = String(value || '') + .trim() + .toUpperCase(); + if (!vin) { + callback(); + return; + } + if (!/^[A-HJ-NPR-Z0-9]{17}$/.test(vin)) { + callback(new Error('车架号必须是17位字母数字(不含 I、O、Q)')); + return; + } + if (this.vehicleForm.vin !== vin) { + this.vehicleForm.vin = vin; + } + callback(); + }, validateAnnualReviewEndDate(rule, value, callback) { if (this.vehicleForm.annualReviewLongTerm !== 1 && !value) { callback(new Error('请选择道路运输年审有效期')); From 069d0e92ff9921c54317acddb4fcab88a0f71798 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Sat, 3 Oct 2026 17:57:01 +0800 Subject: [PATCH 6/6] =?UTF-8?q?:bug:=20=E4=BF=AE=E8=BD=A6=E8=BE=86?= =?UTF-8?q?=E5=AE=A1=E6=A0=B8=E8=AF=A6=E6=83=85=E5=AD=97=E6=AE=B5=E5=85=83?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9A=84=E6=97=A7=E9=94=AE=E5=90=8D=EF=BC=8C?= =?UTF-8?q?=E6=AD=A2=E6=9C=9F=E4=B9=8B=E5=A4=96=E4=B8=8D=E5=86=8D=E8=A2=AB?= =?UTF-8?q?=E9=95=BF=E6=9C=9F=E6=9C=89=E6=95=88=E9=81=AE=E8=94=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 车辆字段整组停留在改造前的旧键名(licenseNo / licenseValidStart / licenseValidEnd / licenseLongTerm / roadTransportNo / roadTransportValid* / engineNo),而快照早已改名成 drivingLicense* / roadTransportCert* / registration*。键名对不上不报错——每个字段都算「没有值」, 于是车辆审核详情里行驶证与道路运输证两组全显示「—」,真值全落到未翻译的「其他字段」组里 露着 camelCase 键名,审核员既看不到内容、也 diff 不出变更。 - 按 VehicleCertSubmitRequest 重写车辆四组字段(基本信息 / 行驶证 / 道路运输证 / 机动车登记本), 补上此前漏登记的所有人、外廓尺寸、核定载质量、准牵引总质量、能源类型、业务关系、机动车登记编号与六张图; - longTermKey 只挂在被长期有效作废掉的那个字段(有效期止、强制报废日期)上: 起期是独立日期,挂上去会把真实值显示成「长期有效」,还让审核员看不到起期的改动。 --- src/utils/cert-audit.js | 82 +++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/src/utils/cert-audit.js b/src/utils/cert-audit.js index f248e1f..9044ab4 100644 --- a/src/utils/cert-audit.js +++ b/src/utils/cert-audit.js @@ -93,6 +93,11 @@ export const formatCertDateTime = value => { * * 字段名与后端提交 DTO 的 JSON 键一致(司机 PersonCertSubmitRequest / 车辆 VehicleCertSubmitRequest), * 两侧快照共用同一套结构,故同时用于标签与比较规则。 + * + * `longTermKey` 只挂在**被长期有效作废掉的那个字段**上,也就是各组的「有效期止」与强制报废日期: + * 勾了长期有效,它们显示「长期有效」、比较时按同一个值算(避免每次审核都报「已变更」)。 + * 「有效期起」不挂:起期是一个独立的日期,长期有效并不会让它失效, + * 挂上去等于把它自己的值藏起来、还让审核员看不到起期的改动。 */ const CERT_SNAPSHOT_GROUPS = { driver: [ @@ -103,12 +108,7 @@ const CERT_SNAPSHOT_GROUPS = { { key: 'idCardNo', label: '身份证号' }, { key: 'idCardFrontUrl', label: '身份证正面照', type: TYPE_IMAGE }, { key: 'idCardBackUrl', label: '身份证反面照', type: TYPE_IMAGE }, - { - key: 'idCardValidStart', - label: '身份证有效期起', - type: TYPE_DATE, - longTermKey: 'idCardLongTerm', - }, + { key: 'idCardValidStart', label: '身份证有效期起', type: TYPE_DATE }, { key: 'idCardValidEnd', label: '身份证有效期止', @@ -126,12 +126,7 @@ const CERT_SNAPSHOT_GROUPS = { { key: 'driverClass', label: '准驾车型' }, { key: 'driverLicenseFrontUrl', label: '驾驶证主页照片', type: TYPE_IMAGE }, { key: 'driverLicenseBackUrl', label: '驾驶证副页照片', type: TYPE_IMAGE }, - { - key: 'driverValidStart', - label: '驾驶证有效期起', - type: TYPE_DATE, - longTermKey: 'driverLongTerm', - }, + { key: 'driverValidStart', label: '驾驶证有效期起', type: TYPE_DATE }, { key: 'driverValidEnd', label: '驾驶证有效期止', @@ -150,6 +145,13 @@ const CERT_SNAPSHOT_GROUPS = { ], }, ], + /* + * ⚠️ 车辆字段名与 PersonCertSubmitRequest 不同:这一组曾经整组用着改造前的旧键名 + * (licenseNo / licenseValidEnd / roadTransportNo …),而快照早已改名成 drivingLicense* / + * roadTransportCert*。键名对不上**不报错**——每个字段都算「没有值」, + * 于是车辆审核详情里行驶证与道路运输证两组全显示「—」,真值全落到未翻译的「其他字段」组, + * 审核员既看不到内容、也 diff 不出变更。字段名一律以 VehicleCertSubmitRequest 为准。 + */ vehicle: [ { title: '车辆基本信息', @@ -157,49 +159,57 @@ const CERT_SNAPSHOT_GROUPS = { { key: 'plateNo', label: '车牌号' }, { key: 'vehicleType', label: '车辆类型' }, { key: 'plateColor', label: '车牌颜色' }, + { key: 'ownerName', label: '所有人' }, { key: 'vin', label: '车辆识别代号(车架号)' }, - { key: 'engineNo', label: '发动机号' }, + { key: 'outerLength', label: '外廓长度(mm)' }, + { key: 'outerWidth', label: '外廓宽度(mm)' }, + { key: 'outerHeight', label: '外廓高度(mm)' }, + { key: 'approvedLoadKg', label: '核定载质量(KG)' }, + { key: 'tractionMassKg', label: '准牵引总质量(KG)' }, + { key: 'energyType', label: '能源类型' }, + { key: 'businessRelation', label: '业务关系' }, ], }, { title: '行驶证信息', fields: [ - { key: 'licenseNo', label: '行驶证档案编号' }, - { key: 'licenseFrontUrl', label: '行驶证主页照片', type: TYPE_IMAGE }, - { key: 'licenseBackUrl', label: '行驶证副页照片', type: TYPE_IMAGE }, + { key: 'compulsoryScrapDate', label: '强制报废日期', type: TYPE_DATE, longTermKey: 'compulsoryScrapLongTerm' }, + { key: 'compulsoryScrapLongTerm', label: '强制报废长期有效', type: TYPE_LONG_TERM }, + { key: 'drivingLicenseNo', label: '行驶证档案编号' }, + { key: 'drivingLicenseImage', label: '行驶证主页正面', type: TYPE_IMAGE }, + { key: 'drivingLicenseMainBack', label: '行驶证主页反面', type: TYPE_IMAGE }, + { key: 'drivingLicenseViceFront', label: '行驶证副页正面', type: TYPE_IMAGE }, + { key: 'drivingLicenseViceBack', label: '行驶证副页反面', type: TYPE_IMAGE }, + { key: 'drivingLicenseStartDate', label: '行驶证有效期起', type: TYPE_DATE }, { - key: 'licenseValidStart', - label: '行驶证有效期起', - type: TYPE_DATE, - longTermKey: 'licenseLongTerm', - }, - { - key: 'licenseValidEnd', + key: 'drivingLicenseEndDate', label: '行驶证有效期止', type: TYPE_DATE, - longTermKey: 'licenseLongTerm', + longTermKey: 'drivingLicenseLongTerm', }, - { key: 'licenseLongTerm', label: '行驶证长期有效', type: TYPE_LONG_TERM }, + { key: 'drivingLicenseLongTerm', label: '行驶证长期有效', type: TYPE_LONG_TERM }, ], }, { title: '道路运输证信息', fields: [ - { key: 'roadTransportNo', label: '道路运输证号' }, - { key: 'roadTransportUrl', label: '道路运输证照片', type: TYPE_IMAGE }, + { key: 'roadTransportCertNo', label: '道路运输证号' }, + { key: 'roadTransportCertImage', label: '道路运输证照片', type: TYPE_IMAGE }, + { key: 'roadTransportCertStartDate', label: '道路运输证有效期起', type: TYPE_DATE }, { - key: 'roadTransportValidStart', - label: '道路运输证有效期起', - type: TYPE_DATE, - longTermKey: 'roadTransportLongTerm', - }, - { - key: 'roadTransportValidEnd', + key: 'roadTransportCertEndDate', label: '道路运输证有效期止', type: TYPE_DATE, - longTermKey: 'roadTransportLongTerm', + longTermKey: 'roadTransportCertLongTerm', }, - { key: 'roadTransportLongTerm', label: '道路运输证长期有效', type: TYPE_LONG_TERM }, + { key: 'roadTransportCertLongTerm', label: '道路运输证长期有效', type: TYPE_LONG_TERM }, + ], + }, + { + title: '机动车登记本信息', + fields: [ + { key: 'registrationNo', label: '机动车登记编号' }, + { key: 'registrationImage', label: '机动车登记本照片', type: TYPE_IMAGE }, ], }, ],