# 订单列表售后 Tab — 修改接口（管理后台）

- **日期**：2026-06-22
- **端类型**：管理后台
- **Issue**：[#4222](https://git.1814.love:8443/wx/HL/issues/4222)
- **PR**：[#4225](https://git.1814.love:8443/wx/HL/pulls/4225)
- **Commit**：[82c669b2a](https://git.1814.love:8443/wx/HL/commit/82c669b2a)
- **后端负责人**：腰苏图

---

## 1. 接口背景

管理后台订单列表原有 5 个 Tab（全部 / 出行前 / 出行中 / 核单 / 异常取消），均以 `orderStatus`（订单状态）为过滤维度。

本次新增第 6 个「售后」Tab，以 `aftersale_status=IN_PROGRESS` 为过滤维度。售后是**横切（叠加）语义**，与出行阶段正交：一个订单可同时出现在某出行阶段 Tab 和售后 Tab，且售后计数**不计入**「全部」（全部仍 = 出行前 + 出行中 + 核单 + 异常取消四项之和）。

---

## 2. 变更清单

| # | 接口 | 变更类型 | 具体内容 |
|---|------|----------|----------|
| 1 | `GET /v3/admin/order/status-group-counts` | ⚠️ 出参新增 | 返回数组由 5 项变 6 项，末尾新增 `{ group: "AFTERSALE", label: "售后", orderCount: N }` |
| 2 | `GET /v3/admin/order` | ✨ 入参枚举新增 | `statusGroup` 新增可选值 `AFTERSALE` |

---

## 3. 接口详情

### 接口一：订单列表 Tab 分组计数

| 项 | 内容 |
|----|------|
| 方法 + 路径 | `GET /v3/admin/order/status-group-counts` |
| 功能描述 | 返回各 Tab 的订单计数，供顶部 Tab 展示角标数字 |
| 认证 | Bearer Token（管理员 JWT，必填） |
| 幂等性 | 只读，天然幂等 |
| 限流 | 无独立限流策略 |

### 接口二：订单列表

| 项 | 内容 |
|----|------|
| 方法 + 路径 | `GET /v3/admin/order` |
| 功能描述 | 管理后台订单列表，支持多维度过滤与分页 |
| 认证 | Bearer Token（管理员 JWT，必填） |
| 幂等性 | 只读，天然幂等 |
| 限流 | 无独立限流策略 |

---

## 4. 接口入参

### 4.1 接口一入参（Query 参数，与 GET /v3/admin/order 共用过滤字段）

> 计数接口忽略分页字段（page / pageSize），只消费过滤字段。

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| keyword | String | 否 | 关键词（订单号 / 客户姓名 / 手机号） |
| departureDateFrom | String | 否 | 出发日期起（yyyy-MM-dd） |
| departureDateTo | String | 否 | 出发日期止（yyyy-MM-dd） |
| consultantName | String | 否 | 定制师姓名 |
| createSource | String | 否 | 订单来源（字典值） |
| tagNames | Array\<String\> | 否 | 标签名列表，多值 AND |

### 4.2 接口二入参（Query 参数）

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| page | Integer | 否 | 页码，默认 1 |
| pageSize | Integer | 否 | 每页数量，默认 20 |
| keyword | String | 否 | 关键词 |
| departureDateFrom | String | 否 | 出发日期起（yyyy-MM-dd） |
| departureDateTo | String | 否 | 出发日期止（yyyy-MM-dd） |
| consultantName | String | 否 | 定制师姓名 |
| createSource | String | 否 | 订单来源字典值 |
| tagNames | Array\<String\> | 否 | 标签名列表 |
| orderStatus | String | 否 | 订单状态下拉（与 statusGroup 并存时为 AND） |
| statusGroup | String | 否 | Tab 分组标识，见枚举表，**本次新增 `AFTERSALE`** |

---

## 5. 出参字段

### 接口一出参

`Result<List<StatusGroupCountVO>>`，数组共 **6 项**（固定顺序）。

**StatusGroupCountVO 字段：**

| 字段名 | 类型 | 说明 |
|--------|------|------|
| group | String | 分组标识，见枚举表 |
| label | String | 分组中文标签（直接展示） |
| orderCount | long | 该分组订单数 |

**6 项固定顺序：**

| 顺序 | group | label | 语义 |
|------|-------|-------|------|
| 1 | ALL | 全部 | 全部订单（= 出行前+出行中+核单+异常取消之和，**不含**售后独立计数） |
| 2 | BEFORE_TRIP | 出行前 | orderStatus IN (PENDING_PAY, CUSTOMIZING, PENDING_DEPARTURE) |
| 3 | ON_TRIP | 出行中 | orderStatus = TRAVELLING |
| 4 | SETTLEMENT | 核单 | orderStatus = COMPLETED |
| 5 | ABNORMAL | 异常/取消 | orderStatus = CANCELLED |
| 6 | AFTERSALE | 售后 | aftersale_status = IN_PROGRESS（横切，不限出行阶段） |

### 接口二出参

`PageResult<OrderListItemVO>`，字段结构与本次无变化。

---

## 6. 枚举 / 数据字典

### statusGroup 枚举值（接口二入参 + 接口一出参 group 字段）

| 枚举值 | 含义 | 过滤维度 | 是否横切 |
|--------|------|----------|----------|
| ALL | 全部 | 不过滤（含取消） | 否 |
| BEFORE_TRIP | 出行前 | orderStatus IN (PENDING_PAY, CUSTOMIZING, PENDING_DEPARTURE) | 否 |
| ON_TRIP | 出行中 | orderStatus = TRAVELLING | 否 |
| SETTLEMENT | 核单 | orderStatus = COMPLETED | 否 |
| ABNORMAL | 异常/取消 | orderStatus = CANCELLED | 否 |
| **AFTERSALE** | **售后（新增）** | **aftersale_status = IN_PROGRESS** | **是** |

**横切说明**：`AFTERSALE` 不按 orderStatus 过滤，而是按 `aftersale_status=IN_PROGRESS` 过滤。一个出行中的订单如果同时有在途售后工单，该订单会出现在「出行中」Tab 和「售后」Tab 两处。

---

## 7. 错误码

| 错误码 | 含义 | 触发场景 |
|--------|------|----------|
| 401 | 未认证 | 未携带或 Token 失效 |
| 403 | 无权限 | 非管理员角色访问 |
| 422 | 参数校验失败 | statusGroup 传了无法识别的值（后端宽松处理：未知 group 忽略，不报 422；前端只传枚举表中的值即可） |

---

## 8. 示例

### 8.1 典型成功——获取各 Tab 计数（含售后）

**请求：**
```
GET /v3/admin/order/status-group-counts
Authorization: Bearer <admin_token>
```

**响应：**
```json
{
  "code": 200,
  "msg": "success",
  "data": [
    { "group": "ALL",        "label": "全部",    "orderCount": 120 },
    { "group": "BEFORE_TRIP","label": "出行前",  "orderCount": 50  },
    { "group": "ON_TRIP",    "label": "出行中",  "orderCount": 30  },
    { "group": "SETTLEMENT", "label": "核单",    "orderCount": 35  },
    { "group": "ABNORMAL",   "label": "异常/取消","orderCount": 5  },
    { "group": "AFTERSALE",  "label": "售后",    "orderCount": 8   }
  ]
}
```

> 注意：ALL.orderCount(120) = 50+30+35+5 = 120，不含 AFTERSALE 的 8。

### 8.2 边界情况——无任何在途售后工单

**请求：**
```
GET /v3/admin/order/status-group-counts
Authorization: Bearer <admin_token>
```

**响应（售后 orderCount = 0）：**
```json
{
  "code": 200,
  "data": [
    { "group": "ALL",       "label": "全部",    "orderCount": 30 },
    { "group": "BEFORE_TRIP","label": "出行前", "orderCount": 20 },
    { "group": "ON_TRIP",   "label": "出行中",  "orderCount": 8  },
    { "group": "SETTLEMENT","label": "核单",    "orderCount": 2  },
    { "group": "ABNORMAL",  "label": "异常/取消","orderCount": 0 },
    { "group": "AFTERSALE", "label": "售后",    "orderCount": 0  }
  ]
}
```

### 8.3 业务失败——筛售后 Tab + 订单状态 AND 组合

**请求（出行中且在售后的单）：**
```
GET /v3/admin/order?statusGroup=AFTERSALE&orderStatus=TRAVELLING&page=1&pageSize=20
Authorization: Bearer <admin_token>
```

**响应（无出行中的在售后单时返回空列表）：**
```json
{
  "code": 200,
  "data": {
    "total": 0,
    "records": []
  }
}
```

---

## 9. 业务边界

**适用：**
- `GET /v3/admin/order/status-group-counts` 在任意过滤条件下均会返回 6 项（含 AFTERSALE）
- `GET /v3/admin/order` 传 `statusGroup=AFTERSALE` 返回所有 `aftersale_status=IN_PROGRESS` 的订单，不限出行阶段（含已取消订单，只要售后工单仍在途）

**不适用：**
- 售后 Tab 不会从其他 Tab 移走订单：用户在「出行中」Tab 看到的订单不会因为开了售后工单而消失
- 售后计数不纳入「全部」总数，前端角标与「全部」Tab 数字不加 AFTERSALE.orderCount

**特殊边界：**
- `statusGroup=AFTERSALE` 与 `orderStatus=XXX` 同时传为 AND 关系（先按 aftersale_status=IN_PROGRESS 过滤，再按 orderStatus 收窄）
- `statusGroup=ALL` 时包含取消的单，即 `cancelled=true`，行为不变

---

## 10. 修改前后对比

### 出参对比（接口一 status-group-counts）

**字段级：**

| 字段/项 | 修改前 | 修改后 |
|---------|--------|--------|
| 返回数组长度 | 5 项（ALL + 4 阶段） | **6 项**（ALL + 4 阶段 + 售后） |
| 第 6 项 group | 无 | `AFTERSALE` |
| 第 6 项 label | 无 | `售后` |
| 第 6 项 orderCount | 无 | long，`aftersale_status=IN_PROGRESS` 的订单数 |
| ALL.orderCount 口径 | 四阶段之和 | **不变**，仍 = 四阶段之和，不含 AFTERSALE |

**行为级：**

| 行为 | 修改前 | 修改后 |
|------|--------|--------|
| 下拉菜单 statusGroup 可选值 | ALL / BEFORE_TRIP / ON_TRIP / SETTLEMENT / ABNORMAL | 同上 + **AFTERSALE** |
| 传 AFTERSALE 的过滤逻辑 | 传入忽略（未知 group） | 按 `aftersale_status=IN_PROGRESS` 过滤 |

---

## 11. 影响评估 / 回滚

**前端同步上线：**
- 若前端对 status-group-counts 返回长度做了硬编码（如 `data.length === 5`）或索引访问，需更新为动态渲染
- 需在 Tab 列表末尾渲染新的「售后」Tab，点击时传 `statusGroup=AFTERSALE`
- ALL.orderCount 口径不变，前端逻辑无需调整

**兼容性：**
- 不传 statusGroup 或传旧值的请求行为不变，向下兼容
- status-group-counts 固定多返回 1 项，前端旧版不会报错（JSON 数组多一项无副作用），但售后 Tab 不会渲染，属预期降级

**回滚方案：**
- 后端回滚：恢复 `OrderStatusGroup` 枚举（删除 AFTERSALE），countByStatusGroup 重新返回 5 项
- 前端回滚：无感知（旧前端代码仍正常，只是不展示售后 Tab）

---

## 12. 注意事项

1. **横切语义，计数不累加到 ALL**：前端不要把 AFTERSALE.orderCount 加到 ALL 的角标里，否则显示总数虚高。

2. **AND 过滤**：`statusGroup=AFTERSALE` 与 `orderStatus` 下拉同时传是合法且有意义的用法（筛"出行中且在售后"），前端过滤 UI 无需屏蔽两者同时选择。

3. **硬编码 5 个 Tab 需更新**：若之前按固定 5 项渲染 Tab，须改为遍历 API 返回数组动态渲染，否则「售后」Tab 不出现。

4. **售后 Tab 包含已取消订单**：`aftersale_status=IN_PROGRESS` 的订单不论 orderStatus 是什么（含 CANCELLED）都会出现在售后 Tab。

---

## 13. 关联 / 联系人

- **Issue**：[#4222 管理后台订单列表加「售后」Tab](https://git.1814.love:8443/wx/HL/issues/4222)
- **PR**：[#4225 feat(order-v3): 管理后台订单列表加「售后」Tab](https://git.1814.love:8443/wx/HL/pulls/4225)
- **Commit**：[399288d4d](https://git.1814.love:8443/wx/HL/commit/399288d4d97b26238d292b7c7768b69b67f46300)
- **关联 PR**：[#4197 订单售后中展示 aftersale_status（售后状态激活）](https://git.1814.love:8443/wx/HL/pulls/4197)
- **后端负责人**：腰苏图（yaosutu）
