9.4 KiB
schema, ticket, title, consumer, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 5299 | 车务矩阵车辆司机预选与常驻标记 | admin | 修改接口 | deployed | verified | implemented | hl-admin | bfcfafc69335f289071fe3e6dce74bacb636c55d | 2026-07-27T22:12:19+08:00 | 前端实现 bfcfafc6 已回写,但测试页面 FleetAssignModal 出现响应式递归更新并阻断候选加载;修复并完成页面复验前不得流转为 released/verified | 2026-07-28 | dev-v3 |
Fleet:矩阵车辆司机预选与常驻标记
服务:
hl-fleet-serviceIssue:#5299 影响页面:管理后台车务管理 → 矩阵派单 兼容性:仅新增响应字段,旧客户端可继续忽略
问题与目标
当前矩阵车辆行虽然已有常驻司机姓名,但缺少常驻司机稳定 ID;订单甘特条也没有直接返回该派车组的实际车辆、司机及常驻关系。页面因此无法稳定完成以下行为:
- 从具体车辆上下文发起派单时自动带出车辆和可派常驻司机;
- 行头展示车辆常驻司机;
- 已排订单条块展示实际执行司机,并区分常驻/临时司机。
本次后端补齐稳定读契约。前端不得按姓名判断常驻关系,也不得复制相邻订单的临时司机作为新派单默认值。
2026-07-28 前端运行态阻断:FleetAssignModal 递归更新
现场结论
前端实现提交 bfcfafc69335f289071fe3e6dce74bacb636c55d 已进入 v2.1,但当前测试页面打开逐日派车弹窗并加载车辆、司机候选后稳定出现:
Maximum recursive updates exceeded in component <FleetAssignModal>
浏览器 Console 同时记录多次 unhandledrejection;Network 中多条 POST /admin/fleet/assignments/candidates 均返回 HTTP 200,但车辆、司机区域持续停留在 loading,无法进入下一步。因此本次不是候选接口、日期冲突或后端状态机错误,而是前端实现后的响应式自反馈回归。frontend_status: "implemented" 仅表示代码已存在,不代表已发布或页面闭环。
高可信自反馈链
当前 AssignModal.vue 的深度 watcher 同时观察 candidateEvidence、candidateSelectionReady、autoSelectedResidentDriverSource 等候选派生状态,并在回调 syncActiveSlotSelection() 中无条件重建 dailyPlan、回写当前槽位。dailyPlan 又参与计算其他槽位排除 ID、候选可选态和新的 candidateEvidence;即使业务值没有变化,新数组/对象身份仍会再次触发同一 watcher,形成“观察候选派生值 → 回写逐日方案 → 候选派生值重新计算 → 再次回写”的闭环。
前端修复必须同时满足:
syncActiveSlotSelection()先比较当前日格与待写 payload;语义完全相同时直接返回,不创建新dailyPlan/槽位对象。- watcher 使用稳定原始值或稳定 fingerprint,不深度监听会被自身回写间接失效的派生对象;候选响应、用户选择和槽位同步应有单向边界。
updateDailyVehiclePlanCell()或等价更新器在无真实字段变化时返回原引用,禁止仅因对象重建触发后续 effect。- 保留 #5283 的稳定
initializationIdentity与requestSeq旧响应隔离;不得退回监听props.order对象身份或通过删除并发保护掩盖循环。 - 正常打开弹窗只允许一次初始候选请求;需要自动常驻司机二次校验时最多再请求一次。状态稳定后不得继续请求,loading 必须收敛。
前端回归验收
- 打开订单逐日派车弹窗并取得候选成功响应后,Console 不再出现
Maximum recursive updates或相关unhandledrejection。 - 候选请求数量有明确上界;自动常驻司机场景最多“初始查询 + 携两侧 ID 二次校验”,不存在持续请求。
- 车辆、司机列表结束 loading,接口返回的分页总数与页面一致,可正常进入下一步。
- 已选车辆、司机、逐日费用和跨常驻确认写回一次后保持稳定,多轮
nextTick不再重建相同dailyPlan。 - 相同业务身份的 SSE/列表对象替换仍保留候选与草稿;真实订单、需求、槽位或模式变化时才重新初始化。
- 新增真实挂载
FleetAssignModal的回归测试,模拟候选成功和自动常驻二次校验,断言无未处理 Promise、请求次数有界且草稿稳定;仅做静态源码断言不足以验收。
变更接口
GET /admin/fleet/matrix/grid
data.vehicles[] 新增:
| 字段 | 类型 | 空值规则 | 说明 |
|---|---|---|---|
primaryDriverId |
String(Long) |
无常驻司机为 null |
车辆主档的权威常驻司机 ID |
既有 primaryDriverName、primaryDriverPhone 继续返回;手机号保持脱敏。三个字段共同用于行头展示,常驻判断以 ID 为准。
data.vehicles[].assignments[] 新增:
| 字段 | 类型 | 空值规则 | 说明 |
|---|---|---|---|
vehicleId |
String(Long) |
未派为空 | 该甘特条对应派车组的实际车辆 ID |
vehiclePlate |
String |
未派为空 | 实际车牌 |
driverId |
String(Long) |
未派为空 | 该派车组实际司机 ID |
driverName |
String |
未派为空 | 该派车组实际司机姓名 |
residentMatch |
Boolean |
无实际司机为 null |
true=实际司机是该车常驻司机;false=实际司机存在但不是该车常驻司机或车辆无常驻 |
矩阵继续排除取消切片;同一派车组存在取消日缺口时按有效连续服务段拆成多个甘特条,不得把外包络日期当作司机持续占用。
前端必须调整
1. 从矩阵车辆上下文发起派单
- 将所在行
vehicles[].id直接作为当前选择车辆,并在候选请求中传selectedVehicleId。 - 候选接口
POST /admin/fleet/assignments/candidates会返回既有字段selectedVehicleResidentDriver。 - 仅当该快照存在且
available=true时,才把其司机 ID 作为默认司机。 - 无常驻司机、常驻司机冲突或不可派时,车辆仍保持预选,司机保持“待选择”,并展示候选返回的不可用原因。
- 不得取前后相邻订单的实际司机作为新订单默认司机。
2. 矩阵司机展示
- 车辆行头:
primaryDriverId != null:展示primaryDriverName(常驻);可按需展示脱敏手机号。primaryDriverId == null:展示“无常驻司机”,不要继续统一显示“待派司机”。
- 已排订单条块:展示本条
driverName。residentMatch=true:标记“常驻”。residentMatch=false:标记“临时”。residentMatch=null:显示“待派司机”。
不要从 drivers 页面列表或姓名/车牌文本反推常驻关系;使用本接口稳定 ID 与 residentMatch。
3. 同步完成 #5292 页面改造
用户截图仍出现“收取车费日期”,说明当前测试页面仍在使用旧构建或旧逻辑。新页面必须继续执行 #5292:
- 删除“收取车费日期”及旧免费日期提交逻辑;
- 使用
dailyVehiclePlan渲染“服务日期 × 稳定车辆槽位”; - 保存时只提交
dailyPlan,不得同时提交旧items、chargeableServiceDates、vehicleFeeWaiverReason或confirmAllServiceDatesFree。
响应示例
{
"id": "9007199254740993",
"plate": "蒙A-88888",
"primaryDriverId": "9007199254740994",
"primaryDriverName": "王师傅",
"primaryDriverPhone": "138****1234",
"assignments": [
{
"id": "9007199254740995",
"assignmentGroupId": "9007199254740996",
"vehicleId": "9007199254740993",
"vehiclePlate": "蒙A-88888",
"driverId": "9007199254740994",
"driverName": "王师傅",
"residentMatch": true
}
]
}
所有 Long ID 仍按 JSON 字符串处理,禁止转为 JS Number。
不影响范围
- 不修改派单状态机、车辆/司机占用、保险、对账或常驻关系写入。
- 不自动选择不可派司机,不绕过候选接口与最终派单锁内校验。
- 不修改
hl-ui仓库;前端消费状态独立流转。
验证证据
- 后端 PR wx/HL#5300 已 squash 合并至
dev-v3,合并提交fca8cedc6。 hl-fleet-service已滚动部署测试环境,8087/8187 双实例健康。- 定向矩阵测试:43 tests,0 failures,0 errors,0 skipped。
- Fleet reactor verify:2456 tests,0 failures,0 errors,1 skipped;模块 Spotless、Jar、JaCoCo 成功。
- 真实测试网关
GET /admin/fleet/matrix/grid?year=2026&month=8&season=active:HTTP/code 200;返回 19 辆车、38 个派车段,新增车辆/司机字段完整,Long ID 均为字符串,residentMatch三态合法,常驻司机手机号全部脱敏,无业务写入。 - 网关证据:
D:/work2/HL-v3/.tmp/5299-gateway.json,SHA-256d94ee78fbe52cc193a28e5bf182e1353d2eca9d87824b1c2eb526c62a4fe645d。 - OpenAPI/oasdiff:项目尚未配置可复现 Swagger2→OAS3 与 oasdiff,状态为
not_configured;使用源码字段对比、Controller 序列化测试及真实网关响应作为人工回退证据,未冒充工具通过。
当前状态:后端已部署、网关已验证;前端代码状态为 implemented,但运行态复验失败,修复并通过上述页面验收前不得流转为 released 或 verified。
关联:#5299、#5292、#5283。