changelog(frontend): 团期看板展开行子订单列表恒空——store.currentGroupBatchId 只在详情页赋值致 PeriodRow 门控恒空;GB-ADM-003 实测正确(关联 #7204/#7188/#7189/#7190)
changelog-filename-gate / validate (push) Successful in 2s

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7Xpgcv9nncadAXQtWhg4P
这个提交包含在:
API Changelog Bot
2026-09-06 18:57:13 +08:00
共同撰写人 Claude Fable 5.1
父节点 d80b3ee60b
当前提交 077845c8aa
@@ -0,0 +1,260 @@
---
schema: "hl-changelog/v2"
ticket: "frontend"
title: "团期看板展开行子订单列表恒空"
consumer: "admin"
author: "wx(GIT)"
change_type: "前端缺陷"
backend_status: "not_required"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "后端 GB-ADM-003 已在测试服 dev-v3 网关实测返回 1 条活跃子订单;纯前端门控 bug:store.currentGroupBatchId 只在详情页 fetchDetail 赋值,看板展开走的 fetchBatchOrders 不赋值,PeriodRow 子订单列表恒空。首选按 groupBatchId 键缓存(PeriodRow 原注释的设计),并把 ordersError 显示出来。"
updated_at: "2026-09-06"
base: "dev-v3"
---
# 团期订单:看板展开行子订单列表恒空(前端缺陷)
## ⚠️ 关键变化
**现象**:管理后台「团期订单」看板,产品「冻干粉发短信给」班期 2026-10-01「没,那你」这一行显示「已订房 1/8 · 剩 7 · 应收 5850.00 · 已收 5850.00」,点击展开却显示「本期暂无子订单。」,也没有加载态。
**根因(hl-ui `gitea/v2.1` 提交 e78ba5be)**:`src/stores/orderV2Batch.js:147-166` `fetchBatchOrders(groupBatchId)` 请求成功后只写 `batchOrders`,**不写 `currentGroupBatchId`**;该字段全文件唯一赋值点是 `:129-131` `fetchDetail()`(团期详情页才调)。而 `src/views/order-v2/batch/components/PeriodRow.vue:233-235` 的子订单列表和 `:236-238` 的加载态都门控于 `String(store.currentGroupBatchId) === groupBatchId.value`。看板页展开只走 `onToggle → store.fetchBatchOrders`(`:240-245`),`currentGroupBatchId` 保持初始 `''`,门控永远不等 → `orders` 恒 `[]` → `SubOrderTable.vue:4-7` `v-if="!subs.length"` 渲染「本期暂无子订单。」。只有在同一 SPA 会话里先进过**该团期**的详情页(`fetchDetail` 把 `currentGroupBatchId` 设成它)再回看板展开同一行,才碰巧显示正确;刷新页面后直接展开必现。
**结论**:后端零改动(GB-ADM-001 / GB-ADM-003 实测正确)。前端修法首选**按 `groupBatchId` 键缓存子订单**(`PeriodRow.vue:232` 原注释「按 groupBatchId 缓存」就是这个设计,现实现漂移成了借用详情页的 `currentGroupBatchId` 单值门控);同时把 `store.ordersError` 显示出来,否则接口 4xx/5xx 时也会伪装成「暂无子订单」。
行上的「1/8」「5850」和「房/导/摄」橙色芯片**都是当时那条被挡住的活跃子订单 HL20260906093733163 的真实状态**,不是看板算错:已订房按「1 单 = 1 房」持久计数器算(`order_group_batch.enrolled_rooms`),产品侧「线下占位房数」不参与已订房/剩余;橙 = 进行中(`batchLifecycle.js:64-70` DOING → doing),不是「配置完了」。导/摄「零指派却橙」的后端口径已另立 #7204 修正。补充说明:这条子订单本是 2026-09-06 01:37 已取消的测试单,14 时许被团期房务会话为 #7149 实测用 SQL 临时改成「定制中 / 已付订金」,18:51 已复原为已取消——因此该班期现在已订房回到 0/8、展开区显示「本期暂无子订单」是**正确**的;复现本缺陷请换任一有活跃子订单的团期(见复现步骤)。
---
## 一、背景
### 复现步骤
页面:管理后台 `192.168.100.219:9527/order-v2/batch`(hl-ui 团期看板,`src/views/order-v2/batch/index.vue`)。
1. 刷新页面(确保本会话没进过任何团期详情页)
2. 产品页签选任一**行头 `orderCount>0`** 的团期(2026-09-06 18:55 测试库可用:「测试小蒙马-多档-固定金额」的 12-20 出团「#7158验收班期」、12-27 出团「#7178验收班期」,各 1 个活跃子订单;wx 截图用的「冻干粉发短信给」10-01「没,那你」行当时也是 1 单,但那单已于 18:51 复原为已取消,现在该行是真的没有子订单)
3. 点击行左侧展开箭头
4. **结果**:展开区显示「本期暂无子订单。」,无加载态(而 `GET /v3/admin/order/group-batch/{该行 groupBatchId}/orders` 返回 ≥1 条)
5. 对照:先点「进入团期」进详情页再返回看板展开同一行 → 显示子订单(这是 bug 的偶然绕过路径,不是修复)
### 调用链
1. `PeriodRow.vue:240-245` `onToggle()` → `store.fetchBatchOrders(groupBatchId)`
2. `src/stores/orderV2Batch.js:147-166` `fetchBatchOrders` → `src/api/orderV2GroupBatch.js` `getGroupBatchOrders(id, {includeTravelers: true, includeNeeds: true, includeCancelled: false})` → `GET /v3/admin/order/group-batch/{groupBatchId}/orders`
3. hl-gateway 路由 `/v3/admin/**` → `lb://hl-order-service-v3`
4. hl-order-service-v3 `GroupBatchQueryController.listSubOrders`(`order/groupbatch/controller/admin/GroupBatchQueryController.java:96-`)→ `GroupBatchQueryService.listSubOrders`(`:253-`,缺省剔除 `CANCELLED`)→ 返回 1 条
5. store 把结果写进 `batchOrders`(`:157`),**不写 `currentGroupBatchId`**
6. `PeriodRow.vue:233-235` `orders = currentGroupBatchId === groupBatchId ? batchOrders : []` → `[]`
7. `SubOrderTable.vue:4-7` 空态
### 地面真相(测试服 dev-v3 e9a8dd27,2026-09-06 网关实测)
| 属性 | 值 |
|------|-----|
| 产品 | 冻干粉发短信给 `productId=2044306857534636034`(GROUP) |
| 班期 | product 侧 `productBatchId=2052935476557328386`(2026-10-01 出团、10-03 返团) |
| 团期主订单 | `groupBatchId=2096412454643802114`,`batchStatus=RESOURCE_PREPARING`,`maxRooms=8`,`enrolledRooms=1`,`enrolledPeople=2`,`orderCount=1` |
| 活跃子订单 | `orderId=2096412454488612866` `orderNo=HL20260906093733163`:`orderStatus=CUSTOMIZING`,`payStatus=DEPOSIT_PAID`,`paidAmount=5850.00`,1 成人 1 儿童,`order_main.room_count=1` |
| 同班期其余 13 单 | 全部 `CANCELLED`(`includeCancelled=true` 时返回 14 条) |
| 备注 | 以上是 wx 截图时段(2026-09-06 17:20 前后网关实测)的状态。该单 09:37 建、01:37(UTC 09-05 17:37)已取消,14 时许被团期房务会话为 #7149 实测用 SQL 临时改成 CUSTOMIZING / DEPOSIT_PAID / 5850 并改写房务需求(`roomCount=2` KING,`specialNeeds` 含「#7149 网关实测」),**18:51 已复原为 CANCELLED / UNPAID / 0.00**;复原后该班期 `enrolledRooms=0`、无活跃子订单。它与产品侧「线下占位房数=0」无关 |
---
## 二、变更接口清单
| # | 接口 | 方法 | 网关路径 | 前端函数 | 变更 | 说明 |
|---|------|------|---------|---------|------|------|
| 1 | 团期子订单列表 GB-ADM-003 | GET | `/v3/admin/order/group-batch/{groupBatchId}/orders` | `getGroupBatchOrders()` | 复用不改 | 后端已实测返回 1 条,前端未渲染 |
---
## 三、接口详情
### 1. 团期子订单列表 `GET /v3/admin/order/group-batch/{groupBatchId}/orders`
**VO**: 无请求 VO(路径参数 + 三个可选 query)→ `Result<List<GroupBatchOrderItemRespVO>>`
#### 使用场景
看板行展开(`PeriodRow.onToggle`)与团期详情名单页拉该团期的子订单摘要;缺省只返活跃集(`orderStatus != CANCELLED`)。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | path | Long(JSON 字符串) | 是 | 团期主订单 ID | 不是 product 侧 `productBatchId` |
| includeTravelers | query | Boolean | 否 | 缺省 false | true 时附 `travelers[]`(证件号一律不返回) |
| includeNeeds | query | Boolean | 否 | 缺省 false | true 时附 `roomCount / roomType / specialNeeds` |
| includeCancelled | query | Boolean | 否 | 缺省 false | true 时含已取消子订单 |
#### 出参 `Result<List<GroupBatchOrderItemRespVO>>`
| 字段 | 类型 | 说明 |
|------|------|------|
| data[].orderId | String(Long) | 子订单 ID |
| data[].orderNo | String | 订单编号 |
| data[].customerName | String | 客户姓名 |
| data[].contactPhone | String | 联系人手机号(脱敏,前 3 后 4) |
| data[].adultCount / childCount / youngChildCount / babyCount | Integer | 成人 / 儿童 / 幼童 / 婴儿数 |
| data[].participantCount | Integer | 出行人总数(四项之和) |
| data[].orderStatus | String | 订单状态码:`PENDING_PAY / CUSTOMIZING / PENDING_DEPARTURE / TRAVELLING / COMPLETED / CANCELLED` |
| data[].orderStatusName | String | 订单状态中文名 |
| data[].payStatus | String | `UNPAID / DEPOSIT_PAID / FULLY_PAID` |
| data[].paidAmount | String(BigDecimal) | 已支付金额(订金 + 尾款) |
| data[].totalPrice | String(BigDecimal) | 本户应收 = orderAmount + 增项 − 优惠(下限 0);取消单返 0 |
| data[].balanceAmount | String(BigDecimal) | 待支付尾款 = 应收 − 已退 − 已付(≥0) |
| data[].estimatedCost | String(BigDecimal) | 下单时点预估成本快照,可为 null |
| data[].contractStatus | String | `GENERATING / SIGNED / VOIDED / RESIGNING / FAILED`;无合同 null |
| data[].insuranceStatus | String | `INSURING / INSURED / CANCELLED / FAILED`;无保险 null |
| data[].hotelRequirementStatus | String | 房需求提报状态 `PENDING / SUBMITTED / REJECTED`(实测也会透出需求状态机原值如 `PENDING_REVIEW`) |
| data[].vehicleRequirementStatus | String | 车需求提报状态 `PENDING / SUBMITTED / REJECTED` |
| data[].roomCount | Integer | 房数(includeNeeds=true;来源 `order_hotel_requirement.days`,缺省 ceil(人数/2))——**与 `order_main.room_count` 是两个字段**,看板「已订房」用的是后者 |
| data[].roomType | String | 房型文本(includeNeeds=true) |
| data[].specialNeeds | String | 特殊需求(includeNeeds=true;缺需求行回落 customer_remark) |
| data[].tierCode | String | 档位码,如 `1A1C` |
| data[].tierName | String | 档位名,如 `1成人1儿童` |
| data[].travelerInfoComplete | Boolean | 出行人资料是否齐全 |
| data[].travelers[] | Array | includeTravelers=true 时返回:`name / type(ADULT/CHILD/YOUNG_CHILD/BABY) / age / birthdayInTrip` |
| data[].consultantName | String | 定制师姓名(创单时固化) |
#### 请求示例
```http
GET /v3/admin/order/group-batch/2096412454643802114/orders?includeTravelers=true&includeNeeds=true HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <token>
```
#### 响应示例
实测响应(2026-09-06,`travelers[]` 略):
```json
{
"code": 200,
"message": "成功",
"data": [
{
"orderId": "2096412454488612866",
"orderNo": "HL20260906093733163",
"customerName": "测试团期指引",
"adultCount": 1,
"childCount": 1,
"youngChildCount": 0,
"babyCount": 0,
"participantCount": 2,
"orderStatus": "CUSTOMIZING",
"orderStatusName": "定制中",
"payStatus": "DEPOSIT_PAID",
"paidAmount": "5850.00",
"totalPrice": "5850.00",
"balanceAmount": "0.00",
"estimatedCost": null,
"contractStatus": null,
"insuranceStatus": null,
"hotelRequirementStatus": "PENDING_REVIEW",
"vehicleRequirementStatus": "PENDING",
"roomCount": 2,
"roomType": "KING",
"specialNeeds": "#7149 网关实测 AC-1 招募中",
"tierCode": "1A1C",
"tierName": "1成人1儿童",
"travelerInfoComplete": false,
"consultantName": "admin"
}
],
"success": true
}
```
#### 空数据 / 降级响应
该团期没有活跃子订单时 `data` 为空数组(不是 null);前端只有在 `code=200 && data.length===0` 时才应显示「本期暂无子订单」,请求失败或异常码一律显示错误态。
```json
{
"code": 200,
"message": "成功",
"data": [],
"success": true
}
```
#### 错误响应
```json
{
"code": 589500,
"message": "团期不存在",
"data": null,
"success": false
}
```
其他:`589507` 无操作权限(非团期管理员 / 非本定制师名下)。HTTP 始终 200,按 `code` 判断。
#### 业务边界
- 缺省剔除 `CANCELLED`;`includeCancelled=true` 才含已取消单
- 路径参数是 order 侧 `groupBatchId`,看板行 VO 里的 `groupBatchId` 字段直接可用;不要传 `productBatchId`
- `roomCount`(房务需求)与看板「已订房」(`order_main.room_count` 计数器,1 单 = 1 房)口径不同,不要互相校验
---
## 四、前端修复要点与自测清单
### 修复要点
1. **首选(方案 B)按 `groupBatchId` 键缓存**:`orderV2Batch.js` 把 `batchOrders`(单值数组)改成按 ID 的 map(如 `ordersByBatch = ref({})`),`fetchBatchOrders` 写 `ordersByBatch.value[id] = list`;`PeriodRow.vue:233-238` 的 `orders / ordersLoading` 直接读 `store.ordersByBatch[groupBatchId.value]`,去掉对 `currentGroupBatchId` 的依赖(该字段语义上只服务详情页)。多行同时展开互不覆盖;`PeriodRow.vue:232` 原注释「按 groupBatchId 缓存」即此设计。保留旧的 `batchOrders` 导出(`:202` 一带)直到 grep 确认详情页 / `ChipDetailModal.vue` 没有直接读它。
2. **治标(方案 A)**:只在 `fetchBatchOrders` 请求前写 `currentGroupBatchId.value = id`。一行改动,但 `currentGroupBatchId` 是单值:keep-alive(`index.vue:170-172`)下先展开 X 行、进 Y 详情再返回,X 行会退回空态;多行同时展开互相覆盖。不推荐单独采用。
3. **错误态**:`PeriodRow.vue` 目前不渲染 `store.ordersError`(`orderV2Batch.js:159-162` 失败时置 `batchOrders=[]`),接口失败会伪装成「暂无子订单」;展开区应区分「加载中 / 加载失败(可重试)/ 暂无子订单」,且行上 `orderCount>0`(GB-ADM-001 有该字段)而列表为空时显示「加载失败」而非「暂无」。
4. **让橙色芯片可自证**:`SubOrderTable.vue:10-19` 表头只有联系人 / 人数 / 房型·间数 / 电话 / 应收 / 毛利 / 资料,GB-ADM-003 已返回的 `hotelRequirementStatus / vehicleRequirementStatus` 没有展示;建议加「房需求 / 车需求」状态列,或在芯片上加 tooltip 文案「进行中:N 户待审核」(数据来自 GB-ADM-090~095 逐户明细),否则运营看到橙色仍不知道它对应哪户的什么状态。芯片图例建议:灰=未开始、橙=进行中、绿=已完成、红=异常。
5. **顺手核对**:`ChipDetailModal.vue`(`index.vue:174` 引入)是否也依赖 `currentGroupBatchId`;详情页离开时是否需要清空 `currentGroupBatchId`。
### 自测清单
1. 刷新看板页,直接展开任一 `orderCount>0` 的行(如「测试小蒙马-多档-固定金额 · #7158验收班期」)→ 出现与 GB-ADM-003 返回条数一致的子订单
2. 先展开 A 行再展开 B 行 → 各自显示各自的子订单,收起再展开不串行
3. 展开瞬间出现加载态
4. 断网或把接口改成 500 → 展开区显示「加载失败」而非「本期暂无子订单」
5. 进详情页再返回看板展开 → 仍正确(keep-alive 场景)
6. 真正没有活跃子订单的团期(如把该单取消后)→ 显示「本期暂无子订单」
---
## 五、验证证据
### 网关实测(测试服 2026-09-06 17:20 前后,登录后切 ADMIN 角色;该单 18:51 已复原为已取消,下表是截图时段的证据)
| 请求 | 结果 |
|------|------|
| `GET /v3/admin/order/group-batch/2096412454643802114/orders` | 200 / code 200,1 条(HL20260906093733163) |
| `GET .../orders?includeTravelers=true&includeNeeds=true`(前端缺省参数) | 200 / code 200,1 条,含 roomCount=2 / roomType=KING |
| `GET .../orders?includeCancelled=true` | 200 / code 200,14 条(1 活跃 + 13 CANCELLED) |
| `GET /v3/admin/order/group-batch/2096412454643802114` | enrolledRooms=1、enrolledPeople=2、remainRooms=7、maxRooms=8 |
| `GET /v3/admin/order/group-batch/board?productId=2044306857534636034` 该行 | enrolledRooms=1、orderCount=1、groupBatchId=2096412454643802114 |
### DB 落库(只读)
`hl_order_service_v3.order_main` 中 `product_batch_id=2052935476557328386` 共 14 行:13 行 `order_status=CANCELLED`,1 行 `CUSTOMIZING`(`pay_status=DEPOSIT_PAID`、`paid_amount=5850.00`、`room_count=1`、`group_batch_id=2096412454643802114`);`order_group_batch` 该团期 `enrolled_rooms=1`、`enrolled_people=2`。
---
## 六、影响与不影响范围
- **影响**:hl-ui `src/stores/orderV2Batch.js`、`src/views/order-v2/batch/components/PeriodRow.vue`、`SubOrderTable.vue`(可选加列);团期详情页若直接读 `store.batchOrders` 需同步改用新缓存
- **不影响**:后端 GB-ADM-001 / GB-ADM-003 契约与实现;网关;小程序;订单列表页
---
## 七、关联
- 后端工单 #7204:导/摄芯片零指派却 DOING(橙)的聚合口径修正(wx 2026-09-06 拍板:零指派灰 / 部分指派橙 / 全部指派绿)——与本缺陷无依赖,但同一现场
- 后端团期看板三单:#7188(「第N期」序号透出)、#7189(看板以产品全班期为基底 + 班期范围筛选)、#7190(「出行完毕」状态)——均不改 GB-ADM-003
- 同日前端缺陷:`06_frontend_团期产品新建订单向导创单漏传productBatchId-前端缺陷-管理后台.md`(已由 mmg 修复 03a985b6)