diff --git a/docs/操作日志_运行轨迹关联名称显示优化_20260815.md b/docs/操作日志_运行轨迹关联名称显示优化_20260815.md new file mode 100644 index 0000000..651df41 --- /dev/null +++ b/docs/操作日志_运行轨迹关联名称显示优化_20260815.md @@ -0,0 +1,49 @@ +# 运行轨迹关联名称显示优化操作日志 + +操作时间:2026-08-15 + +操作类型:修改 + +影响模块:平台总后台、气体配送订单运行轨迹 + +## 操作前状态 + +- 运行轨迹列表将配送订单和工作人员直接显示为截断的系统唯一标识。 +- 用户无法从列表直接判断对应订单号和配送人员姓名,与其他已使用关联名称展示的页面不一致。 + +## 具体操作 + +- 配送订单列改为展示业务订单号,列名统一为“配送订单”。 +- 工作人员列改为展示人员姓名,列名统一为“配送人员”。 +- 配送人员关联明确限定 `delivery` 岗位,但历史详情不附加启用、在岗或资质有效过滤,确保离岗人员的历史轨迹仍可回看。 +- 详情接口的 `{ track, points }` 聚合响应新增主记录解包与轨迹点固定中文列契约。 +- 两列继续保留完整系统唯一标识的悬浮提示、复制入口和关联详情跳转能力。 +- 增加展示契约检查,防止运行轨迹退回裸唯一标识展示。 + +## 行为变化 + +- 修改前:列表主文案为截断系统唯一标识,如 `000000000018`、`000000000005`。 +- 修改后:列表主文案分别为订单号和人员姓名;技术标识仅作为辅助排障信息,不丢失、不脱敏。 + +## 代码变更 + +- `frontend/platform_admin/src/api/resources.ts`:补充运行轨迹关联字段的名称展示配置。 +- `frontend/platform_admin/scripts/check-resource-display-contracts.mjs`:补充运行轨迹展示回归检查。 +- `frontend/platform_admin/scripts/check-staff-relation-policy.mjs`:补充历史轨迹人员角色与不过滤当前状态的契约检查。 +- `frontend/platform_admin/src/api/resource-display.ts`:支持运行轨迹主记录解包及轨迹点专属中文字段语义。 +- `frontend/platform_admin/src/api/resource-detail-contract.ts`:声明运行轨迹基本信息顺序、关联资源和轨迹点固定列。 + +## 风险评估 + +- 接口字段及公共组件均未改变,仅复用现有关系名称加载能力。 +- 关联名称加载失败时仍保留唯一标识复制入口,不影响排障。 + +## 验证结果 + +- `pnpm run resource-display-contracts:check` 通过。 +- `pnpm run type:check` 通过。 +- `pnpm run build` 通过。 +- 变更文件执行 Biome 检查通过;全项目 Biome 检查仍有既存的组件命名和未使用变量问题,本次未扩大范围修改。 +- Codex 内置浏览器刷新运行轨迹后,配送订单显示 `MOCK-GASORDER-001`,配送人员显示“王师傅”;原截断标识不再作为主文案,两个完整唯一标识仍可复制。 +- 修复详情页工作人员关联缺少显式角色策略导致的加载失败,并重新验证详情页。 +- 浏览器详情页验证通过:加载失败提示数量为 0;基本信息正确显示订单号、配送人员姓名、尝试次数和时间,关联记录按固定中文列显示轨迹点。 diff --git a/frontend/platform_admin/scripts/check-resource-display-contracts.mjs b/frontend/platform_admin/scripts/check-resource-display-contracts.mjs index 0790cb9..3a008dd 100644 --- a/frontend/platform_admin/scripts/check-resource-display-contracts.mjs +++ b/frontend/platform_admin/scripts/check-resource-display-contracts.mjs @@ -53,6 +53,30 @@ if (missingLabels.length) { throw new Error(`资源字段缺少中文名称:${missingLabels.join('、')}`); } +// 运行轨迹必须复用统一关联名称组件,避免再次把截断唯一标识当成业务名称。 +const trackDefinition = resources.match( + /define\('gasorder_track',[\s\S]*?\n\s*\]\),/, +)?.[0]; +if (!trackDefinition) throw new Error('无法读取运行轨迹资源定义'); +for (const [key, label] of [ + ['gasorder_basic_identity', '配送订单'], + ['staff_account_identity', '配送人员'], +]) { + const field = trackDefinition.match( + new RegExp(`relation\\('${key}'[\\s\\S]*?\\}\\)`), + )?.[0]; + if ( + !field || + !field.includes(`listLabel: '${label}'`) || + !field.includes('listRelationNameOnly: true') || + !field.includes('showIdentityCopy: true') || + (key === 'staff_account_identity' && + !field.includes("staffRelation: { roles: ['delivery'] }")) + ) { + throw new Error(`运行轨迹的${label}必须以业务名称展示并保留唯一标识复制入口`); + } +} + if (/title="ID"|data-index="id"/.test(listPage)) { throw new Error('标准资源列表不得把数据库ID作为业务列展示'); } @@ -79,7 +103,7 @@ assertIncludes( ); for (const collection of [ 'products', 'revisions', 'items', 'assignments', 'statuses', - 'tracks', 'confirmations', 'payments', + 'tracks', 'confirmations', 'payments', 'points', ]) { assertIncludes( detailContract, @@ -87,5 +111,10 @@ for (const collection of [ `缺少关联记录固定列契约:${collection}`, ); } +assertIncludes( + display, + "if (detail.track && typeof detail.track === 'object')", + '运行轨迹详情必须解包 track 聚合主记录', +); process.stdout.write('平台48类资源中文展示契约检查通过\n'); diff --git a/frontend/platform_admin/scripts/check-staff-relation-policy.mjs b/frontend/platform_admin/scripts/check-staff-relation-policy.mjs index 3f092db..8a19802 100644 --- a/frontend/platform_admin/scripts/check-staff-relation-policy.mjs +++ b/frontend/platform_admin/scripts/check-staff-relation-policy.mjs @@ -1,6 +1,6 @@ /** * 功能:验证工作人员关联字段过滤、资质来源校验和路由上下文传递契约。 - * 版本:v1.2.0 + * 版本:v1.3.0 */ import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; @@ -105,8 +105,17 @@ const resourcesSource = await readFile( assert.match(resourcesSource, /organizationScoped: true/); assert.equal( (resourcesSource.match(/staffRelation:/g) ?? []).length, - 3, - '三个工作人员关联场景都必须显式声明策略', + 4, + '四个工作人员关联场景都必须显式声明策略', +); +const trackPolicy = resourcesSource.match( + /define\('gasorder_track',[\s\S]*?staffRelation: \{ roles: \['delivery'\] \}[\s\S]*?\n\s*\]\),/, +)?.[0]; +assert.ok(trackPolicy, '运行轨迹必须显式限定配送人员岗位'); +assert.doesNotMatch( + trackPolicy, + /enabledOnly|workStatus|validCredentialOnly/, + '历史运行轨迹不得按人员当前启用、在岗或资质状态过滤', ); const relationsSource = await readFile( diff --git a/frontend/platform_admin/src/api/resource-detail-contract.ts b/frontend/platform_admin/src/api/resource-detail-contract.ts index f22a7fc..7089311 100644 --- a/frontend/platform_admin/src/api/resource-detail-contract.ts +++ b/frontend/platform_admin/src/api/resource-detail-contract.ts @@ -100,6 +100,24 @@ const contracts: Record = { }, }, }, + gasorder_track: { + leadingKeys: [ + 'gasorder_basic_identity', 'staff_account_identity', 'attempt_no', + 'started_at', 'completed_at', 'identity', 'status', + ], + relationResources: { + gasorder_basic_identity: '/gasorder_basic', + staff_account_identity: '/staff_account', + }, + collections: { + points: { + columns: [ + 'occurred_at', 'received_at', 'longitude', 'latitude', 'accuracy', + 'speed', 'direction', 'source', 'request_no', + ], + }, + }, + }, payment_order: { leadingKeys: [ 'payment_no', 'payment_status', 'request_no', 'business_type', diff --git a/frontend/platform_admin/src/api/resource-display.ts b/frontend/platform_admin/src/api/resource-display.ts index 9d5347c..7c9b2f7 100644 --- a/frontend/platform_admin/src/api/resource-display.ts +++ b/frontend/platform_admin/src/api/resource-display.ts @@ -39,6 +39,7 @@ const aliases: Record = { creator_display_name: '创建方', contract_display_name: '配送合同', assigner_name: '分配人', + points: '轨迹点', }; /** 聚合子表字段可能与全局同名字段具有不同业务语义,按父资源和集合名称覆盖。 */ @@ -85,6 +86,17 @@ const collectionFieldAliases: Record> = { started_at: '开始时间', completed_at: '完成时间', }, + 'gasorder_track.points': { + request_no: '上报流水号', + longitude: '经度', + latitude: '纬度', + occurred_at: '定位时间', + received_at: '接收时间', + source: '定位来源', + accuracy: '定位精度', + speed: '行进速度', + direction: '行进方向', + }, 'gasorder_basic.confirmations': { confirmed_at: '确认时间', confirm_type: '确认方式', @@ -160,6 +172,8 @@ export function primaryRecord(detail: ResourceRow): ResourceRow { return detail.order as ResourceRow; if (detail.contract && typeof detail.contract === 'object') return detail.contract as ResourceRow; + if (detail.track && typeof detail.track === 'object') + return detail.track as ResourceRow; return detail; } diff --git a/frontend/platform_admin/src/api/resources.ts b/frontend/platform_admin/src/api/resources.ts index ec87c23..feabfc2 100644 --- a/frontend/platform_admin/src/api/resources.ts +++ b/frontend/platform_admin/src/api/resources.ts @@ -656,8 +656,22 @@ export const resources: ResourceUiDefinition[] = [ f('from_status'), f('to_status'), f('operator_name'), f('operator_identity'), f('occurred_at'), f('reason'), ]), define('gasorder_track', '运行轨迹', 'readonly', [ - relation('gasorder_basic_identity', '/gasorder_basic'), - relation('staff_account_identity', '/staff_account'), + relation('gasorder_basic_identity', '/gasorder_basic', false, { + label: '配送订单', + listLabel: '配送订单', + listRelationNameOnly: true, + displayRelationLabel: true, + showIdentityCopy: true, + }), + relation('staff_account_identity', '/staff_account', false, { + label: '配送人员', + listLabel: '配送人员', + listRelationNameOnly: true, + displayRelationLabel: true, + showIdentityCopy: true, + // 历史轨迹只限定岗位,不按当前启用、在岗或资质状态过滤。 + staffRelation: { roles: ['delivery'] }, + }), f('attempt_no'), f('started_at'), f('completed_at'), ]), define('gasorder_track_point', '轨迹点', 'readonly', [