--- schema: "hl-changelog/v2" ticket: "8457" title: "车务首页新增待确认接送变更状态卡" consumer: "admin" author: "wx(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "implemented" frontend_owner: "mmg" frontend_ref: "6b0198595a1debda7e35b86954faa8d5dd1823ab" target_release: "v2.1" verified_at: "2026-09-28" status_note: "前端已交付(车务首页待确认接送变更状态卡),详见 hl-admin v2.1 提交 6b019859。" updated_at: "2026-09-28" base: "dev-v3" --- # hl-user-service: 车务首页新增待确认接送变更状态卡 **服务**: hl-user-service(计数由 hl-fleet-service 汇总,第三张卡的数据来自 hl-order-service-v3) **PR**: #8471(已合入 `dev-v3`,squash `d10aada4d`) **Issue**: #8457 **日期**: 2026-09-28 **影响范围**: 管理后台首页 `GET /admin/profile/dashboard`,仅车务管理员角色(`VEHICLE_MANAGER`)的响应 --- ## ⚠️ 关键变化 - 车务角色响应里的 `pendingStatusCards` 由**固定两项**变为**固定三项**,新增第三项 `status=transfer_change_pending`、`statusLabel=待确认接送变更`。前两项的 `status`、`statusLabel`、顺序不变。 - `pendingStatusCards[].count` 可以是 `null`:**只有第三项可能为 null**,表示「取不到、未知」;`0` 表示「确实没有」。前端对 `null` 和缺字段都按未知处理,显示「—」,不要显示成 0。 - 第三项是**全局计数**,不受首页「近 7 天」窗口限制。守恒关系随之收窄:`unassigned` + `holding` 两项之和 == `pendingArrangeVehicle` == `upcomingTrips.length`,**第三项不参与**。不要把三张卡相加当作总数。 --- ## 一、背景 大交通变更后需要车务确认的接送变更(#8435 引入的确认流程)此前只能在车务看板上逐单看到,车务首页没有入口,车务不知道还有多少单没确认。本单在车务首页加第三张状态卡,显示全部待确认接送变更的订单数,点卡片跳车务看板处理。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 管理后台首页仪表盘 | GET | `/admin/profile/dashboard` | 修改 | 车务角色响应的 `pendingStatusCards` 由两项扩为三项,`count` 可为 null | --- ## 三、接口详情 ### 1. 管理后台首页仪表盘 `GET /admin/profile/dashboard` **VO**: `无 ReqVO(Query 参数 period)→ LogisticsDashboardVO` 声明返回类型为 `Result`,按登录角色返回不同的实现;车务管理员角色返回 `LogisticsDashboardVO`,本单只改这一个实现。 #### 使用场景 车务管理员登录后台进入首页,展示待安排车辆总数、三张未完成状态卡与近 7 天用车列表。第三张卡「待确认接送变更」点击后跳车务看板处理。 #### 入参字段表 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | period | Query | String | 否 | 默认 `today`;取值 `today` / `week` / `month` | 其他角色仪表盘的时间范围。车务角色不使用该参数,传什么都返回同样的结果 | 无请求体。 #### 出参字段表 以下为车务角色 `data` 的全部字段;`pendingArrangeVehicle`、`upcomingTrips` 本单未改。 | 字段 | 类型 | 说明 | |------|------|------| | pendingArrangeVehicle | Integer | 待安排车辆数:行程开始日在今天起 7 天内(含今天)、存在待派车或待确认执行派车的订单数,按订单去重。本单未改 | | pendingStatusCards | Array<Object> | 未完成状态卡,固定三项,顺序固定为待派车、待确认、待确认接送变更 | | pendingStatusCards[].status | String | 状态键:`unassigned` / `holding` / `transfer_change_pending` | | pendingStatusCards[].statusLabel | String | 展示文案:`待派车` / `待确认` / `待确认接送变更` | | pendingStatusCards[].count | Integer \| null | 订单级计数,每单只计一次。前两项恒为整数;第三项为 null 表示未知,0 表示确实没有 | | upcomingTrips | Array<Object> | 即将出行/用车(近 7 天)。本单未改 | #### 请求示例 ```http GET /admin/profile/dashboard Authorization: Bearer <车务管理员登录 JWT> ``` 无请求体。 #### 响应示例 测试服车务测试账号实测原文(2026-09-28 12:51:10): ```json { "code": 200, "message": "成功", "data": { "pendingArrangeVehicle": 0, "pendingStatusCards": [ {"status": "unassigned", "statusLabel": "待派车", "count": 0}, {"status": "holding", "statusLabel": "待确认", "count": 0}, {"status": "transfer_change_pending", "statusLabel": "待确认接送变更", "count": 2} ], "upcomingTrips": [] }, "traceId": null, "success": true } ``` 第三项与前两项口径不同(见「业务边界」),两者数值互不相关,前两项为 0 时第三项可以非 0。 #### 空数据 / 降级响应 - 没有任何待确认接送变更:第三项 `count=0`。 - 取不到第三项计数(订单服务不可达、超时或返回失败):第三项 `count=null`;前两项、`pendingArrangeVehicle`、`upcomingTrips` 照常返回。 - 取不到整份车务汇总(车务服务不可达或返回失败):仍返回三项,`count` 依次为 `0`、`0`、`null`;`pendingArrangeVehicle=0`,`upcomingTrips=[]`。 - 车务服务未返回状态卡(旧版本车务服务):前两项由 `upcomingTrips` 推算,第三项 `count=null`。 - 以上降级都返回 `code=200`、`success=true`,不报错;判断未知只看第三项 `count` 是否为 null 或缺字段。降级场景只有单测覆盖,测试服未造。 #### 错误响应 鉴权失败由网关统一拦截,HTTP 状态恒为 200,看 body 的 `code`。下例为同一网关对 `/admin/` 路径缺 `Authorization` 头时的实测原文(取自另一个 `/admin/` 端点,网关对所有 `/admin/` 路径返回同一形态): ```json { "code": 401, "message": "缺少有效的 Authorization 头", "data": null, "traceId": "c34c4a77f4c449dc", "success": false } ``` | 错误码 | 触发条件 | |---|---| | 401 | 网关拦截:缺 `Authorization` 头(「缺少有效的 Authorization 头」)、Token 无效(「Token 无效」)、Token 已过期或被撤销(「Token 已过期或被撤销」) | | 403 | 网关拦截:小程序用户 Token 访问管理端接口(「无权访问管理端接口」) | | 200208 | 用户服务未取到登录管理员身份(「未认证」) | 非车务角色调用不会报错,而是返回该角色自己的仪表盘,见「业务边界」。 #### 业务边界 - 三项顺序固定为 `unassigned`、`holding`、`transfer_change_pending`;前端按 `status` 取卡即可。 - 前两项与第三项口径不同:前两项只看行程开始日在今天起 7 天内(含今天)的订单;第三项是全局计数,没有日期条件,也不受 `period` 影响。 - 第三项只计非团期订单:已归入团期的订单改大交通不计入(团期接送变更不走车务确认);已删除订单不计入。 - 第三项按订单计:一个订单无论改过几次大交通,只计 1 次;车务在看板上确认该订单的接送变更后,它立即不再计入。 - `count=null` 与 `count=0` 含义不同:null 是「取不到」,0 是「确实没有」。 - 只有车务管理员角色(`VEHICLE_MANAGER`)的响应带 `pendingStatusCards`。超管、管理员、定制师、房务、财务、物料等角色调用同一接口返回各自的仪表盘,不含这组字段。 --- ## 四、契约约束与正确调用方式 | 场景 | 第三项 `count` | 前端展示 | |------|------|------| | 有待确认接送变更 | 正整数 | 显示数字 | | 没有待确认接送变更 | `0` | 显示 0 | | 取不到计数 | `null` 或缺字段 | 显示「—」,不显示 0 | - 按 `status` 取卡并匹配样式;遇到不认识的 `status` 不要报错。 - 首张统计「待安排车辆」用 `pendingArrangeVehicle`,不要用三张状态卡相加代替。 - 点第三张卡跳车务看板 `/fleet/board` 时要清空出发日期范围。车务看板默认只列今年 1 月 1 日至今日出发的订单(按行程开始日过滤),而第三项是全局计数,不清空日期会看不到出发日在今天之后的待确认订单。不加状态筛选,也不加「只看待确认」。 - 车务确认接送变更走 #8435 的 `POST /admin/fleet/board/orders/{orderId}/transfer-change/confirm`;确认成功后重新拉一次首页即可看到计数减少。 --- ## 五、数据库行为 无表结构变更,不写库。三张卡都是只读计数。 --- ## 六、边界行为 - 第三项与首页 7 天窗口无关:行程开始日在几个月以后的订单只要有待确认接送变更,也会计入第三项,但不会出现在前两项和 `upcomingTrips` 里。 - 前两项之和恒等于 `pendingArrangeVehicle`,也等于 `upcomingTrips.length`;这一关系对第三项不成立。 - 第三项取不到时不影响前两项和其他字段。 ### 向后兼容 响应结构只在 `pendingStatusCards` 数组末尾追加一项,前两项的键、文案、顺序不变;`pendingArrangeVehicle`、`upcomingTrips` 不变。新增的是 `count` 可为 null 这一取值,前端对数字做运算或格式化前要先判空。 --- ## 六.5、枚举 / 数据字典 `pendingStatusCards[].status`: | status | statusLabel | 含义 | count 口径 | |------|------|------|------| | unassigned | 待派车 | 用车需求已展开但还没排车/司机 | 行程开始日在今天起 7 天内的订单数,整数 | | holding | 待确认 | 已锁定车/司机,待车务确认执行 | 行程开始日在今天起 7 天内的订单数,整数 | | transfer_change_pending | 待确认接送变更 | 客人改了大交通后接送需求有变化,待车务在看板确认(#8435) | 全局订单数(非团期、未删除),整数或 null | --- ## 六.6、修改前后对比 | 项 | 修前 | 修后 | |------|------|------| | `pendingStatusCards` 项数 | 固定 2 项 | 固定 3 项 | | 第三项 | 无 | `transfer_change_pending` / `待确认接送变更` | | `count` 取值 | 恒为整数 | 前两项恒为整数;第三项为整数或 null | | 守恒关系 | 两项之和 == `pendingArrangeVehicle` == `upcomingTrips` 唯一订单数 | `unassigned` + `holding` 两项之和 == `pendingArrangeVehicle` == `upcomingTrips.length`;第三项不参与 | 测试服实测第三项读数(车务测试账号): | 时刻 | 操作 | 第三项 `count` | |------|------|------| | 12:51:10 | 基线 | 2 | | 13:05:24 | 核心订单车务已派车后,客人改到达航班时间 | 3 | | 13:05:54 | 车务在看板确认该订单的接送变更 | 2 | | 13:10:38 → 13:10:57 | 团期订单改大交通前后 | 2 → 2 | --- ## 六.7、影响评估 - **是否破坏向后兼容**: 否。只在数组末尾追加一项,`count` 新增 null 取值。 - **前端是否必须同步上线**: 否。车务首页状态卡按数组循环渲染,第三张卡会自动出现(未配样式的键显示为灰色);但现有取值对 `count` 做了 `?? 0` 兜底,第三项为 null 时会显示成 0,与「确实没有」混淆。 - **前端需要改的点**(依据工单 #8457「文档与交接」节,对照 hl-ui `v2.1` `1c555f544` 核对): 1. 第三张卡 `count` 为 null 或缺字段时显示「—」,不显示 0(`VehicleDashboard.vue` 里 `count` 的 `?? 0` 兜底要改)。 2. 为 `transfer_change_pending` 补卡片样式(`STATUS_CARD_PRESENTATION`)。 3. 统计区由 3 张变 4 张(首张 `pendingArrangeVehicle` 加三张状态卡),栅格列数与骨架屏数量跟着调。 4. 点第三张卡跳 `/fleet/board` 并清空出发日期范围,不加状态筛选,也不加「只看待确认」。 5. 车务看板页目前不读任何路由参数(`openFleetBoard` 传的 `orderId` / `orderNo` / `statuses` 没有消费方),要支持读跳转参数来清空日期;参数名由前端定。 - **前端 workaround 清理点**: 无。 --- ## 七、不影响范围 - 超管、管理员、定制师、房务、财务、物料等角色的首页仪表盘。 - `period` 参数的取值与含义。 - 车务角色的 `pendingArrangeVehicle`、`upcomingTrips` 口径,以及前两张状态卡的口径。 - 车务看板列表与详情接口、接送变更确认接口(#8435)。 - 网关路由与鉴权规则(本单未改网关)。 --- ## 八、测试环境已验证 部署:测试服 `hl-fleet-service`、`hl-order-service-v3`、`hl-user-service` 于 2026-09-28 12:19:44 至 12:22:27 先后滚到 `dev-v3 d10aada4d`(即本单合并提交),deploy-status 显示三者落后 0;`hl-gateway` 为 2026-09-27 部署的 `71def6dc5`,本单未改网关。 ### 测试服网关实测(`GET /admin/profile/dashboard`,测试专用账号与自造夹具) | 场景 | 检验点 | 结果 | |------|--------|------| | 车务账号请求首页 | 返回三项、顺序固定,第三项 `count=2`,前两项与 `upcomingTrips` 为空 | ✅ 原文见响应示例 | | 核心订单车务已派车后,客人改到达航班时间(该单接送服务日为 12 月) | 第三项 `count` 由 2 变 3,前两项仍为 0 | ✅ | | 车务在看板确认该订单接送变更(确认接口返回 `outcome=BASELINE_REFRESHED`) | 第三项 `count` 回到 2 | ✅ | | 团期订单改大交通 | 第三项 `count` 前后都是 2;该团期订单在车务看板详情里 `transferChange=null` | ✅ | | 超管、管理员、定制师调用同一接口 | 响应均不含 `pendingStatusCards`;超管与管理员顶层键为 `overview`、`publishedProducts`、`ranking`、`todos`、`totalAdmins`、`totalMiniAppUsers`、`totalProducts`、`trend`、`upcomingTrips`,定制师为 `calendarEvents`、`customerStats`、`funnel`、`overview`、`productStats`、`ranking`、`reviewStats`、`shortcuts`、`todos`、`trend`、`upcomingTrips` | ✅ | ### 单测覆盖(测试服未造「服务不可达」场景) | 场景 | 用例 | |------|------| | 订单服务返回计数:第三项带出该值 | `FleetDashboardSummaryServiceTest.getSummary_transferChangeCountAvailable_thirdCardCarriesCount` | | 订单服务取不到:第三项 null,前两项不受影响 | `FleetDashboardSummaryServiceTest.getSummary_transferChangeCountNull_thirdCardNullOthersUnaffected` | | 无未完成订单:三项齐全 | `FleetDashboardSummaryServiceTest.getSummary_noIncompleteOrders_returnsThreeCardsAllZero` | | 订单服务返回 0 时给 0 不给 null;抛异常、返回失败、data 为 null、负数时给 null | `OrderQueryFacadeTest.countTransferChangePendingOrNull_*`(7 个) | | 订单服务调用失败走降级时不伪装成 0 | `TransferChangePendingCountFeignFallbackFactoryTest.create_transportFailure_returnsFailedResultNotZero` | | 车务服务不可达:三项 0 / 0 / null | `LogisticsDashboardServiceTest.getDashboard_allServicesFail_returnsAllZeros`、`FleetDashboardStatusCardsTest.degradedCards_fleetUnreachable_firstTwoZeroThirdNull`、`FleetDashboardSummaryFeignFallbackFactoryTest.fallback_returnsStableZeroStatusCards` | | 车务服务返回失败:三项、第三项 null | `LogisticsDashboardServiceTest.getDashboard_fleetReturnsNonSuccess_degradesToThreeCardsThirdNull` | | 透传保真:车务服务给 null 时输出 null,给 0 时输出 0 | `LogisticsDashboardServiceTest.getDashboard_fleetThirdCardNull_passesThroughNullNotZero`、`LogisticsDashboardServiceTest.getDashboard_fleetThirdCardZero_passesThroughZeroNotNull` | | 旧版本车务服务未返回状态卡:前两项推算、第三项 null | `LogisticsDashboardServiceTest.getDashboard_legacyFleetResponse_derivesCardsFromUpcomingTrips` | | 订单服务内部计数接口返回计数 | `OrderInternalForFleetControllerTest.countTransferChangePending_success_returnsCountAsData` | --- ## 十、相关文档 - 工单 #8457: https://git.1814.love/wx/HL/issues/8457 - 同族工单 #8435(大交通改变时接送需求车务确认与看板标记):https://git.1814.love/wx/HL/issues/8435 - 同族 changelog:`changelogs-v2/2026-09/28_8435_大交通改变时接送需求车务确认与看板标记-新增接口-管理后台.md` - 契约文档:`docs/order-v3/api/API-SPEC-FLEET-V1.5.html` §13.10 车务工作台摘要(版本历史 v1.5.105) --- ## 关联 / 联系人 ### 链接 - **Issue**: [#8457](https://git.1814.love/wx/HL/issues/8457) - **PR**: [#8471](https://git.1814.love/wx/HL/pulls/8471) - **Merge commit**: [d10aada4d](https://git.1814.love/wx/HL/commit/d10aada4d) ### 联系人 - **后端负责人**: @wx