文件
hl-api-changelog/changelogs-v2/2026-09/22_frontend_团期详情页切走后重放请求groupBatchId塌缩-前端缺陷-管理后台.md
2026-09-22 10:34:40 +08:00

30 KiB

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, updated_at, base, status_note
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at updated_at base status_note
hl-changelog/v2 frontend 团期详情页切走后重放请求导致 groupBatchId 塌缩(前端侧修复) admin wx(GIT) 前端缺陷 deployed not_required verified mmg b7c20a81945f986f9e5edc6e09adbe5da93220d4 v2.1 2026-09-22 2026-09-22 dev-v3 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 字段契约构造,非测试服抓包实测)

{
  "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 路径段为空串时的实际报错(本次事故复现的原样响应):

{"code": 400, "message": "参数 groupBatchId 格式错误,请检查后重试", "data": null, "success": false}

团期不存在:

{"code": 589500, "message": "团期不存在", "data": null, "success": false}

当前角色未持 group-batch:view 权限,或该团期不在本人名下:

{"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 字段契约构造,非测试服抓包实测)

{
  "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 路径段整体消失时的实际报错(本次事故复现的原样响应,两段字面量拼接后不匹配任何映射):

{"code": 404, "message": "接口不存在: GET /v3/admin/order/group-batch/requirement/hotel-households", "data": null, "success": false}

团期不存在:

{"code": 589500, "message": "团期不存在", "data": null, "success": false}

无权限:

{"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 字段契约构造,非测试服抓包实测)

{
  "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 路径段为空串时的实际报错(本次事故复现的原样响应):

{"code": 400, "message": "参数 groupBatchId 格式错误,请检查后重试", "data": null, "success": false}

团期不存在:

{"code": 589500, "message": "团期不存在", "data": null, "success": false}

无权限:

{"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