--- schema: hl-changelog/v2 ticket: "frontend" title: "团期详情页切走后重放请求导致 groupBatchId 塌缩(前端侧修复)" consumer: admin author: "wx(GIT)" change_type: "前端缺陷" backend_status: "deployed" gateway_status: "not_required" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "b7c20a81945f986f9e5edc6e09adbe5da93220d4" target_release: "v2.1" verified_at: "2026-09-22" updated_at: "2026-09-22" base: dev-v3 status_note: "2026-09-22 测试服 test.1814.love:9443 实测:账号 admin/团期管理员从团期详情页切换到 /notification/my-messages 后弹出三条报错(两条 400『参数 groupBatchId 格式错误』+ 一条 404『接口不存在』)。order-v3 日志(8186/8086 两实例,09:06:44/50)显示 MethodArgumentTypeMismatchException 收到 value=requirement-summary/vehicle-requirement,NoHandlerFoundException 命中 GET /v3/admin/order/group-batch/requirement/hotel-households;同一时段 09:06:48 带真实 id 的请求全部成功,hl-gateway 两实例 0 条路由未命中记录。根因是前端 order-v2/batch/detail/index.vue 用 computed(() => decodeURIComponent(String(route.params.code || ''))) 反应式推导 id,该页 keep-alive 缓存,切到消息中心(meta keepAlive/hidden/noTabs)后 route.params.code 变 undefined,id 求值为空串;『查看需求』Tab 下 RoomSummarySection/RoomHouseholdsSection/GroupVehicleRequirementSection 三个 watcher 缺空值守卫,重放请求把空串拼进本该是路径参数的位置,导致两个后缀字面量落进 GET /v3/admin/order/group-batch/{groupBatchId}(A2 团期详情)的 groupBatchId 位,另一个因少了中间段直接 404。同页 DisbandBanner 组件已有判空守卫,调用全程 200,是本页面内的阳性对照。三条后端接口路径/参数/权限码均未变更、已在测试服部署并正常工作;既有交接件 10_7316/15_7441/20_8046 记录的实现与本次核对一致。建议修法:三个子组件 watcher 照 DisbandBanner 写法补判空;或详情页不用 useRoute() 反应式推导 code,改挂载时快照/onDeactivated 停 watcher。同页 confirm-check/orders/status-logs 三个 Tab 本次未复现报错,是否另有 active 门控,列为待排查项。 前端修复(2026-09-22): 已交付 hl-admin b7c20a81——三个组件(RoomSummarySection/RoomHouseholdsSection/GroupVehicleRequirementSection)load() 首行照阳性对照 DisbandBanner 补 if(!props.groupBatchId) return 空值守卫,watcher/immediate/reload 统一被拦;待排查项实证闭环:ItineraryTab/SuppliesPanel/RoomPlansTab/FinanceTab/StatusLogsPanel/OverviewTab 本就有 active&&id 或 if(id) 守卫,RequirementTab 只 watch active 不在塌缩链路;三 spec 各补塌缩不重放/恢复重拉/首开空 id 不拉取回归(Vitest 定向 42/42,checkpoint 全绿)。" --- # 团期详情页切走后重放请求导致 groupBatchId 塌缩(前端侧修复) ## ⚠️ 关键变化 - **不是后端问题**:三条报错全部由前端在 id 为空串时仍发起请求触发;本次涉及的三条后端接口路径、参数、权限码均未变更,已在测试服部署并实测正常。 - **触发条件**:团期详情页 `order-v2/batch/detail/index.vue` 是 keep-alive 缓存页,从该页切换到 `/notification/my-messages`(路由 meta `keepAlive/hidden/noTabs`)后,详情页仍存活在缓存中,其 `computed(() => decodeURIComponent(String(route.params.code || '')))` 推导出的 id 因 `route.params.code` 变为 `undefined` 而塌缩成**空字符串**。 - **传导机制**:「查看需求」Tab 下 `RoomSummarySection`、`RoomHouseholdsSection`、`GroupVehicleRequirementSection` 三个组件的请求 watcher 缺空值守卫,空串重放请求后,URL 路径段整体消失,后面的字面量后缀滑进了 `groupBatchId` 应该在的位置,命中了两种不同的服务端拒绝形态(400 参数类型错误 / 404 无映射),而不是权限或路由错误。 - **正对照**:同页 `DisbandBanner` 组件已有 `if (!props.groupBatchId) return;` 守卫,全程请求 200,可作为直接抄的修复模板。 ## 一、背景 ### 现象(2026-09-22 测试服 test.1814.love:9443,账号 admin / 角色 团期管理员) 用户从团期详情页 `/order-v2/batch/detail/2100856430494973953` 切换到 `/notification/my-messages` 后,页面弹出三条报错: - `参数 groupBatchId 格式错误,请检查后重试` × 2 - `接口不存在: GET /v3/admin/order/group-batch/requirement/hotel-households` ### 后端实测日志(order-v3,`hl-order-service-v3.log`,09:06:44 与 09:06:50 各一波,分别落在 8186 / 8086 两实例) ``` WARN GlobalExceptionHandler - 参数类型错误: param=groupBatchId, value=requirement-summary, requiredType=Long WARN GlobalExceptionHandler - 参数类型错误: param=groupBatchId, value=vehicle-requirement, requiredType=Long WARN org.springframework.web.servlet.PageNotFound - No mapping for GET /v3/admin/order/group-batch/requirement/hotel-households WARN GlobalExceptionHandler - No handler found: GET /v3/admin/order/group-batch/requirement/hotel-households ``` 同一时段 09:06:48,带真实 id(`groupBatchId=2100856430494973953`)的请求全部成功;hl-gateway 两实例 0 条路由未命中记录——请求确实到达了 order-v3,并被服务端正确拒绝(不是网关/路由问题)。 ### 「表面看起来像」vs「实际是」 | 表面报错 | 看起来像 | 实际是 | |---|---|---| | `参数 groupBatchId 格式错误,请检查后重试` | 接口参数校验变严了 / 接口签名变了 | 前端把空串拼进了 URL,字面量后缀(`requirement-summary`/`vehicle-requirement`)落进了 `groupBatchId` 位,被 `GroupBatchQueryController` 的团期详情端点 `GET /v3/admin/order/group-batch/{groupBatchId}` 接住,Long 转换失败 | | `接口不存在: GET /v3/admin/order/group-batch/requirement/hotel-households` | 接口被删了 / 路由配错了 | 空串导致中间路径段整体消失,剩下两段字面量拼在一起,天然不匹配任何 `@GetMapping`,属于合法的 404,接口本体从未变化 | ### 根因链路(前端产物级实测,测试服 `/var/www/hl-admin/assets`,2026-09-22 06:28 部署) 1. `order-v2/batch/detail/index.vue` 的 id 取值是反应式推导:`computed(() => decodeURIComponent(String(route.params.code || '')))`。 2. 该页是 keep-alive 缓存页;`/notification/my-messages` 路由 meta 为 `keepAlive/hidden/noTabs`,切过去后详情页仍在缓存中存活,而 `route.params.code` 已变 `undefined` → id 求值为空串。 3. 「查看需求」Tab 下三个子组件的 watcher 没有空值守卫,于是各重放一次请求: - `RoomSummarySection`:`watch([active, groupBatchId], ...)` 只挡了 `active`,不挡空 id; - `RoomHouseholdsSection`:同上; - `GroupVehicleRequirementSection`:`watch(() => props.groupBatchId, ..., {immediate: true})`,无任何守卫。 4. 空串导致 URL 路径段整体消失,后缀滑进了 id 位置: - `/v3/admin/order/group-batch/requirement-summary` → 被团期详情端点 `GET /v3/admin/order/group-batch/{groupBatchId}` 匹配,`{groupBatchId}` 位拿到字符串 `requirement-summary` → Long 转换失败; - `/v3/admin/order/group-batch/vehicle-requirement` → 同上; - `/v3/admin/order/group-batch/requirement/hotel-households` → 两段字面量后缀,中间没有 `{groupBatchId}`,不匹配任何映射 → 404。 5. **阳性对照(同一页面内)**:`DisbandBanner` 组件已有 `if (!props.groupBatchId) return;` 守卫,所以它调的 `approvals/page` 全程 200、没有报错。 ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|---|---|---|---|---| | 1 | 全团需求汇总 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement-summary` | 复用不改 | 契约未变,本次仅确认前端调用方式 | | 2 | 团期子订单订房记录 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement/hotel-households` | 复用不改 | 契约未变,本次仅确认前端调用方式 | | 3 | 读团期正式用车需求 | GET | `/v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement` | 复用不改 | 契约未变,本次仅确认前端调用方式 | ## 三、接口详情 ### 1. 全团需求汇总 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement-summary` **VO**: 无独立请求体(仅路径参数)→ `GroupRequirementSummaryRespVO` #### 使用场景 团期详情页「查看需求」Tab 的「用房 · 汇总」板块,展示全团逐日 × 酒店 × 房型的用房间数合计、大巴座位合计、各子订单特殊需求标签。 #### 入参字段表 | 字段 | 位置 | 类型 | 必填 | 说明 | |---|---|---|---|---| | groupBatchId | Path | Long | 是 | 团期 ID(雪花 id)。**本次报错的病灶**:该字段取自路由 `code` 参数,页面切走后再重放请求时其反应式取值会塌缩为空串,导致这个路径段被 URL 后面的字面量后缀顶替 | #### 出参字段表 | 字段 | 类型 | 说明 | |---|---|---| | activeOrderCount | int | 在团子订单数(仅排除 CANCELLED,含 COMPLETED) | | hotelNeededOrderCount | int | 需要订房的户数(needsHotel=true 并上已提交有效用房需求的户) | | hotelSubmittedOrderCount | int | 已提交有效用房需求(非打回态)且计入 dailyRoomBreakdown 的户数 | | vehicleRequirementCount | int | 已提交用车需求的子订单数 | | dailyRoomBreakdown[].dayNumber | int | 行程天数(从 1 开始) | | dailyRoomBreakdown[].stayDate | LocalDate | 该晚住宿日期;团期无出发日时为 null | | dailyRoomBreakdown[].hotels[].hotelId | Long(字符串序列化) | 酒店 ID;无候选或未填酒店时为 null | | dailyRoomBreakdown[].hotels[].rooms[].roomCategory | String | 房型大类编码 | | dailyRoomBreakdown[].hotels[].rooms[].totalRoomCount | int | 该天该酒店该房型合计间数 | | vehicleSeatSummary[].vehicleType | String | 车型大类编码(suv/mpv/bus/sedan) | | vehicleSeatSummary[].totalSeats | int | 合计座位数 | | orderSpecialTags[].orderId | Long | 子订单 ID | | orderSpecialTags[].specialTags | String[] | 特殊需求标签列表 | #### 请求示例 ``` GET /v3/admin/order/group-batch/2100856430494973953/requirement-summary Authorization: Bearer {token} ``` 无请求体。 #### 响应示例 (按 VO 字段契约构造,非测试服抓包实测) ```json { "code": 0, "message": "success", "data": { "activeOrderCount": 12, "hotelRequirementCount": 10, "hotelNeededOrderCount": 10, "hotelFlagMismatchOrderCount": 0, "hotelSubmittedOrderCount": 9, "vehicleRequirementCount": 8, "dailyRoomBreakdown": [ { "dayNumber": 1, "stayDate": "2026-09-12", "rooms": [{"roomCategory": "STANDARD", "roomCategoryName": "标间", "totalRoomCount": 5}], "hotels": [{"hotelId": "2023714929877450753", "hotelName": "海堂酒店", "totalRoomCount": 5, "rooms": [{"roomCategory": "STANDARD", "roomCategoryName": "标间", "totalRoomCount": 5}]}] } ], "vehicleSeatSummary": [{"vehicleType": "bus", "vehicleTypeName": "35座大巴", "totalSeats": 35, "totalCount": 1}], "orderSpecialTags": [{"orderId": 2101506167043985410, "requirementType": "HOTEL", "specialTags": ["连通房"]}] } } ``` #### 空数据 / 降级响应 各汇总数组在无对应需求时返回空数组,不会返回 null;`hotelName`/`vehicleTypeName` 在资源服务不可用或字典缺失时可能为 null,间数照常返回。 #### 错误响应 groupBatchId 路径段为空串时的实际报错(本次事故复现的原样响应): ```json {"code": 400, "message": "参数 groupBatchId 格式错误,请检查后重试", "data": null, "success": false} ``` 团期不存在: ```json {"code": 589500, "message": "团期不存在", "data": null, "success": false} ``` 当前角色未持 `group-batch:view` 权限,或该团期不在本人名下: ```json {"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "data": null, "success": false} ``` #### 业务边界 - `groupBatchId` 必须是真实存在的 Long 型团期 id;空串、`undefined` 字面量、非数字字符串都会被当成非法值处理,统一走 400,不会得到空数据。 - 需要角色权限码 `group-batch:view`;无权限或团期不在名下统一报 589507(不区分两种子原因)。 ### 2. 团期子订单订房记录 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/hotel-households` **VO**: 无独立请求体(仅路径参数)→ `GroupHotelHouseholdsRespVO` #### 使用场景 团期详情页「查看需求 · 用房」板块的下半区,按子订单(户)展示订房记录:每户一张卡(团号/联系人/人数/定制师/状态/配房需求)+ 逐晚填报明细(住宿日期/酒店/城市/房型/间数)。与「全团需求汇总」是同一板块的上下两块,各走各的接口。 #### 入参字段表 | 字段 | 位置 | 类型 | 必填 | 说明 | |---|---|---|---|---| | groupBatchId | Path | Long | 是 | 团期 ID(雪花 id)。同上,是本次报错的病灶字段 | #### 出参字段表 | 字段 | 类型 | 说明 | |---|---|---| | groupBatchId | Long(字符串序列化) | 团期 ID | | departDate | LocalDate | 团期出发日期;未定出发日时为 null,此时各晚 stayDate 也全为 null | | householdCount | int | 本列表的户数(=需订房户数),可能大于 countedHouseholdCount,差值是正被打回的户 | | countedHouseholdCount | int | 其中计入上方汇总间数的户数 | | households[].orderId | Long(字符串序列化) | 子订单 ID | | households[].orderNo | String | 子订单团号 | | households[].status | String | 需求状态编码:PENDING_REVIEW/PENDING/PROCESSING/DONE/REJECTED_TO_CONSULTANT/REJECTED_TO_ADMIN;该户尚未提交需求时为 null | | households[].countedInSummary | boolean | 该户是否计入了「用房汇总」的间数;两个打回态为 false | | households[].days[].dayNumber | int | 第几晚(从 1 起) | | households[].days[].customerSelfBooked | boolean | 该晚是否客户自订;自订晚 hotels 恒为空且不计入汇总间数 | | households[].days[].hotels[].hotelId | Long(字符串序列化) | 酒店 ID;无候选或未填酒店时为 null | | households[].days[].hotels[].district | String | 酒店所在区县中文名,页面展示应优先用本字段而非 city(同团期酒店 city 常同为一个地级市) | | households[].days[].hotels[].rooms[].roomCount | int | 间数 | #### 请求示例 ``` GET /v3/admin/order/group-batch/2100856430494973953/requirement/hotel-households Authorization: Bearer {token} ``` 无请求体。 #### 响应示例 (按 VO 字段契约构造,非测试服抓包实测) ```json { "code": 0, "message": "success", "data": { "groupBatchId": "2100856430494973953", "departDate": "2026-09-12", "householdCount": 10, "countedHouseholdCount": 9, "households": [ { "orderId": "2101506167043985410", "orderNo": "GT-26-0081", "customerName": "张三", "participantCount": 3, "consultantId": "10086", "consultantName": "李定制", "status": "DONE", "statusName": "已完成", "countedInSummary": true, "remark": "希望安排有窗房间", "specialTags": ["连通房"], "returnRemark": null, "returnedAt": null, "days": [ { "dayNumber": 1, "stayDate": "2026-09-12", "customerSelfBooked": false, "hotels": [ { "hotelId": "3001000000000000005", "hotelName": "海堂酒店", "city": "呼伦贝尔市", "district": "海拉尔区", "totalRoomCount": 2, "rooms": [{"roomTypeId": "1001", "roomTypeName": "亲子房", "roomCategory": "PARENT_CHILD", "roomCategoryName": "亲子房", "roomCount": 2}] } ] } ] } ] } } ``` #### 空数据 / 降级响应 该户尚未提交需求时 `status` 为 null、`days` 为空列表(不是缺少该户,仍会出现在 `households` 中,仅 `countedInSummary=false`)。 #### 错误响应 groupBatchId 路径段整体消失时的实际报错(本次事故复现的原样响应,两段字面量拼接后不匹配任何映射): ```json {"code": 404, "message": "接口不存在: GET /v3/admin/order/group-batch/requirement/hotel-households", "data": null, "success": false} ``` 团期不存在: ```json {"code": 589500, "message": "团期不存在", "data": null, "success": false} ``` 无权限: ```json {"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "data": null, "success": false} ``` #### 业务边界 - 与「全团需求汇总」判权同码:`group-batch:view`。 - `groupBatchId` 路径段一旦缺失(不是格式错,而是整段消失),会命中 404 而不是 400——这与另外两个端点的失败形态不同,是「哪个路径段消失」决定的,不是接口行为不一致。 ### 3. 读团期正式用车需求 `GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement` **VO**: 无独立请求体(仅路径参数)→ `GroupVehicleRequirementRespVO` #### 使用场景 团期详情页「查看需求 · 用车」板块,展示团期管理员已确认/正在编辑的正式用车需求(乘车分组、逐日人数、审核留痕),以及配车计划刷新的只读观测状态。 #### 入参字段表 | 字段 | 位置 | 类型 | 必填 | 说明 | |---|---|---|---|---| | groupBatchId | Path | Long | 是 | 团期 ID(雪花 id)。同上,是本次报错的病灶字段 | #### 出参字段表 | 字段 | 类型 | 说明 | |---|---|---| | requirementId | Long(字符串序列化) | 正式需求主键 | | groupBatchId | Long(字符串序列化) | 团期聚合主键 | | status | String | DRAFT/CONFIRMED/DISPATCHED/DONE/PENDING_RECONFIRM/CANCELLED | | version | Integer | 版本号(下次提交须回传做乐观锁) | | remark | String | 整份备注;撤回/免车会把那次操作追加进来,不覆盖原备注 | | confirmedBy | String | 整份确认人;DRAFT 时为 null | | confirmedAt | LocalDateTime | 整份确认时间;DRAFT 时为 null | | planRefreshState | String | 配车刷新状态原值:null=从未登记过刷新(多数团期正常态)/PENDING/DONE/FAILED | | planRefreshStalled | Boolean | 刷新是否已停滞、不会自愈(恒非 null);true=必须有人处置 | | planRefreshStalledReason | String | 停滞归因:STATE_FAILED/COMMAND_FAILED/TIMEOUT;未停滞为 null | | planRefreshReplayExhausted | Boolean | 人工重投额度是否已耗尽(恒非 null) | | groups[].groupCode | String | 分组键,直接作为车费 alloc_group | | groups[].days[].headcount | Integer | 该组该日用车人数(乘车人数,非户数) | | groups[].days[].memberOrderCount | Integer | 当日成员户数,供填人数时对照 | #### 请求示例 ``` GET /v3/admin/order/group-batch/2100856430494973953/vehicle-requirement Authorization: Bearer {token} ``` 无请求体。 #### 响应示例 (按 VO 字段契约构造,非测试服抓包实测) ```json { "code": 0, "message": "success", "data": { "requirementId": "1867000000101", "groupBatchId": "2100856430494973953", "status": "CONFIRMED", "version": 3, "remark": "全程 33 座", "confirmedBy": "10086", "confirmedAt": "2026-09-14T10:30:00", "planRefreshState": null, "planRefreshReplayCount": null, "blockedStage": null, "planRefreshStalled": false, "planRefreshStalledReason": null, "planRefreshTimeoutAt": null, "planRefreshReplayExhausted": false, "groups": [ { "groupId": "1867000000009", "groupCode": "BUS", "vehicleType": "35座大巴", "serviceStartDate": "2026-09-12", "serviceEndDate": "2026-09-16", "days": [{"tripDate": "2026-09-12", "headcount": 9, "memberOrderIds": ["2101506167043985410"], "memberOrderCount": 3}] } ] } } ``` #### 空数据 / 降级响应 该团期尚未形成正式需求时接口返回 `data = null`(`GroupBatchRequirementController` 方法 javadoc 原文:「读团期正式用车需求(未形成时返回 null;带配车刷新状态只读投影)」),不是报错——编辑页首次打开就是这个状态,前端应按空态渲染而不是当异常处理。 #### 错误响应 groupBatchId 路径段为空串时的实际报错(本次事故复现的原样响应): ```json {"code": 400, "message": "参数 groupBatchId 格式错误,请检查后重试", "data": null, "success": false} ``` 团期不存在: ```json {"code": 589500, "message": "团期不存在", "data": null, "success": false} ``` 无权限: ```json {"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "data": null, "success": false} ``` #### 业务边界 - 判权码与前两个端点不同:本端点用 `group-batch:demand:confirm`,而不是 `group-batch:view`;三条接口不是同一权限码守卫的,前端如需按权限隐藏 Tab 内容,要分别判断。 - `status` 不是恒定值,`PUT`/`withdraw`/`waive` 各自会把它改成不同的值,前端不要假设「有数据就是 CONFIRMED」。 - `planRefreshStalled=true` 时应引导「联系后台排查」,`planRefreshReplayExhausted=true` 时再点确认只会收到 809210,与本次问题无关但同属该接口只读契约,一并列出供前端识别。 ## 四、契约约束与正确调用方式 | 场景 | 结果 | |---|---| | ✅ 用当前团期真实数字 id 拼接 | `/v3/admin/order/group-batch/2100856430494973953/requirement-summary` → 200 | | ❌ id 为空串,路径只少一段 | `/v3/admin/order/group-batch/requirement-summary` → 400『参数 groupBatchId 格式错误』,`requirement-summary` 落进了 `groupBatchId` 位 | | ❌ id 为空串,路径中间少一段 | `/v3/admin/order/group-batch/requirement/hotel-households` → 404『接口不存在』,两段字面量直接拼接,不匹配任何映射 | ### 发起请求前的必要动作 前端在这三个组件的请求 watcher 里,发起请求前必须先判断 `groupBatchId` 是否为空/`undefined`,为空时直接 `return`,不要依赖 URL 层面的容错——同一路径下不同位置的空段,服务端表现不同(一种落成合法但类型错误的字符串触发 400,另一种导致整条路径不匹配触发 404),两种表现都不能被前端当作可忽略的静默失败来处理。keep-alive 缓存页在路由离开后仍存活,是触发这一问题的必要条件,仅靠「首次挂载时判空」不足以覆盖切走再切回的场景。 ## 五、数据库行为 本次三条接口均为只读 GET,均无数据库写操作,无字段/索引/事务变更。 ## 六、边界行为 - 未登录 → 401(网关拦截)。 - `groupBatchId` 路径段为空串/非数字字符串 → 400『参数 groupBatchId 格式错误,请检查后重试』。 - `groupBatchId` 路径段整体缺失,落地路径与任何 `@GetMapping` 都不匹配 → 404『接口不存在』。 - 团期不存在 → 589500。 - 当前角色未持对应权限码,或该团期不在本人名下 → 589507(三条接口分持 `group-batch:view` / `group-batch:view` / `group-batch:demand:confirm` 两种权限码,见各自「接口详情·业务边界」)。 - 该团期尚未形成正式用车需求时,`GET vehicle-requirement` 返回 `data=null`,不是 404/500。 ## 六.5 枚举 ### status(`GroupVehicleRequirementRespVO.status`) **所属字段**: `data.status` | **类型**: `String` | 值 | 说明 | |---|---| | DRAFT | 草稿;`PUT`/`withdraw` 后均落此值 | | CONFIRMED | 已确认;`waive`(整团免车)后也落此值 | | DISPATCHED | 已配车 | | DONE | 已完成 | | PENDING_RECONFIRM | 受控重开窗口内的中间态 | | CANCELLED | 已取消 | ### status(`GroupHotelHouseholdsRespVO.HouseholdItem.status`,需求状态编码 RequirementStatus) **所属字段**: `data.households[].status` | **类型**: `String`(该户尚未提交需求时为 null) | 值 | 说明 | |---|---| | PENDING_REVIEW | 待审核 | | PENDING | 待处理 | | PROCESSING | 处理中 | | DONE | 已完成 | | REJECTED_TO_CONSULTANT | 打回定制师 | | REJECTED_TO_ADMIN | 打回管理员 | ## 六.6 修改前后对比 本文档不涉及接口契约改造(三条端点均为「复用不改」),无字段级契约对比;以下是前端实现层面的行为级对比。 ### 行为级对比 | 行为 | 改前 | 改后 | |---|---|---| | 团期详情页切换到其他路由(如消息中心)后,详情页仍在 keep-alive 缓存中存活 | 「查看需求」Tab 下三个子组件的 watcher 用塌缩为空串的 groupBatchId 重放请求,弹出 3 条报错(2 个 400 + 1 个 404) | 请求前先判断 groupBatchId 是否为空,为空直接跳过 watcher 回调,不发起请求,不报错 | ## 六.7 影响评估 - 是否破坏向后兼容:否,接口契约本身未改动。 - 影响范围:仅限团期详情页「查看需求」Tab 下依赖反应式 `groupBatchId` 的组件,在 keep-alive 缓存页被切走后再次触发 watcher 的场景;正常打开、停留、直接操作详情页不受影响。 - 前端 workaround 清理点:无——这是需要新增的判空守卫,不是要撤销的旧代码。 ## 七、不影响范围 - 后端代码:本次零改动。 - 数据库 schema:无变更。 - 网关路由:三条端点均已在既有 `/v3/admin/**` 通配路由内,本次事故期间 hl-gateway 两实例 0 条路由未命中记录。 - 团期详情页正常打开、停留、直接操作时的行为:不触发本问题,仅路由切走再重放的场景触发。 - `DisbandBanner` 等已有判空守卫组件的行为:不受影响,本次事故期间全程 200。 - 三条接口在非空 `groupBatchId` 场景下的既有契约与行为:未变化。 ## 八、测试环境已验证 真实日志(✓ = 已实测): ``` 2026-09-22 09:06:44 order-v3(8186) WARN GlobalExceptionHandler - 参数类型错误: param=groupBatchId, value=requirement-summary, requiredType=Long ✓ 2026-09-22 09:06:44 order-v3(8186) WARN org.springframework.web.servlet.PageNotFound - No mapping for GET /v3/admin/order/group-batch/requirement/hotel-households ✓ 2026-09-22 09:06:44 order-v3(8186) WARN GlobalExceptionHandler - No handler found: GET /v3/admin/order/group-batch/requirement/hotel-households ✓ 2026-09-22 09:06:50 order-v3(8086) WARN GlobalExceptionHandler - 参数类型错误: param=groupBatchId, value=vehicle-requirement, requiredType=Long ✓ 2026-09-22 09:06:48 同一时段带真实 groupBatchId 的请求全部成功 ✓ hl-gateway 两实例 0 条路由未命中记录 ✓ ``` 验证团期:`groupBatchId=2100856430494973953`。 前端产物级取证(测试服 `/var/www/hl-admin/assets`,2026-09-22 06:28 部署): - `order-v2/batch/detail/index.vue` 的 id `computed` 反应式推导逻辑 ✓ - `/notification/my-messages` 路由 meta 为 `keepAlive/hidden/noTabs` ✓ - `RoomSummarySection`/`RoomHouseholdsSection`/`GroupVehicleRequirementSection` 三个 watcher 缺空值守卫 ✓ - `DisbandBanner` 组件已有 `if (!props.groupBatchId) return;` 守卫,同页阳性对照 ✓ 源码引用(`origin/dev-v3`): ``` GroupBatchQueryController.java:108 GET /v3/admin/order/group-batch/{groupBatchId}(A2团期详情)存在,是空 id 被误路由到的落点 ✓ GroupBatchRequirementController.java:79-86 GET .../requirement-summary 端点存在,判权 group-batch:view ✓ GroupBatchRequirementController.java:102-112 GET .../requirement/hotel-households 端点存在,判权 group-batch:view ✓ GroupBatchRequirementController.java:219-226 GET .../vehicle-requirement 端点存在,判权 group-batch:demand:confirm ✓ GlobalExceptionHandler.java MethodArgumentTypeMismatchException → 400『参数%s格式错误』 ✓ GlobalExceptionHandler.java NoHandlerFoundException → 404『接口不存在』 ✓ GroupBatchErrorCode.java:15 589500 团期不存在 ✓ GroupBatchPermissionGuard.java:50 PERMISSION_VIEW = "group-batch:view" ✓ GroupBatchPermissionGuard.java:96 PERMISSION_DEMAND_CONFIRM = "group-batch:demand:confirm" ✓ GroupVehicleRequirementRespVO.java status 枚举、配车刷新观测块字段定义 ✓ GroupHotelHouseholdsRespVO.java households/days/hotels/rooms 字段结构 ✓ GroupRequirementSummaryRespVO.java dailyRoomBreakdown/vehicleSeatSummary/orderSpecialTags 字段结构 ✓ ``` 【需确认】未取证项:同页 `confirm-check`/`orders`/`status-logs` 三个 Tab 本次未复现报错,是否另有 `active` 门控或取值路径不同,属前端待排查项,本文档未验证,不作为确认结论。 ## 十、相关文档 - 全团需求汇总完整历史契约:`changelogs-v2/2026-09/10_7316_团期全团需求汇总补权限码与在团口径-结算汇总已核单数纠正-修改接口-管理后台.md` - 团期正式用车需求完整历史契约:`changelogs-v2/2026-09/15_7441_团期正式用车需求声明-新增接口-管理后台.md` - 团期子订单订房记录完整历史契约:`changelogs-v2/2026-09/20_8046_团期用房新增子订单订房记录接口-新增接口-管理后台.md` ## 关联 / 联系人 ### 链接 - Issue:无(纯前端缺陷,无后端工单号) - PR:无(本次零后端代码改动,未产生新 PR) ### 联系人 - **后端负责人**: @wx