feat(order-v2): admin 订单列表+详情综合改造 (搜索/排序/行程/状态 tab)
覆盖今日 4 PR: - PR #2903 详情新增 itinerary (景点+酒店 按天) - PR #2906 列表 keyword 加联系电话 + 排序加 depositAmount - PR #2909 完整 11 位手机号精确加密匹配 (修 #2906 假阳性) - PR #2914 displayStatus 参数对齐原型 5 tab (修待出行 0 单 BUG) 前端动作: - 搜索框 placeholder 提示完整手机号 - 排序下拉新增"订金金额" - tab 切换迁到 displayStatus 参数 - 详情页"行程概览"段渲染 itinerary 字段 向后兼容: 不读新字段/不传新参数行为完全不变。 关联: Closes #2899 #2905 #2908 #2912
这个提交包含在:
父节点
933adcdcad
当前提交
bf1788ae5e
@ -0,0 +1,225 @@
|
|||||||
|
# feat(order-v2): /admin/order/list + /admin/order/{id} 综合改造(搜索 / 排序 / 详情行程 / 状态 tab)
|
||||||
|
|
||||||
|
> **仓库**: HL (后端 hl-order-service-v2)
|
||||||
|
> **关联 PR/Issue**: PR #2903 / #2906 / #2909 / #2914(Closes #2899 #2905 #2908 #2912)
|
||||||
|
> **日期**: 2026-05-22
|
||||||
|
> **影响范围**: 管理后台「订单管理」列表 + 详情
|
||||||
|
> **接收方**: mmg (前端)
|
||||||
|
> **前端**: **需要改动 — 搜索框 placeholder / 排序下拉新增订金 / tab 切换迁到 displayStatus / 详情页渲染行程概览**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 关键变化总览
|
||||||
|
|
||||||
|
| 维度 | 改动 | 前端动作 |
|
||||||
|
|---|---|---|
|
||||||
|
| 搜索 keyword | 现支持 **完整 11 位手机号精确匹配**(加密列等值),姓名/订单号/产品名仍 LIKE | placeholder 加"完整手机号" |
|
||||||
|
| 列表排序 | 新增 `depositAmount`(订金金额)排序 | 排序下拉加"订金金额"选项 |
|
||||||
|
| 列表 tab 筛选 | 新增 `displayStatus` 参数,**原型 5 tab 必须用这个**(status 是内部细粒度,对不上 tab) | tab 切换的 status 参数改名为 `displayStatus` |
|
||||||
|
| 订单详情 | 新增 `itinerary` 字段(行程按天结构化,含景点 + 酒店) | 详情页"行程概览"段渲染 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、`GET /admin/order/list` 搜索框(keyword)
|
||||||
|
|
||||||
|
### 改前
|
||||||
|
|
||||||
|
`keyword` 对 4 字段做 LIKE 模糊匹配:`orderNo` / `contactName` / `productName`(联系电话 LIKE 是一次预备改造,对加密列实际无效)。
|
||||||
|
|
||||||
|
### 改后
|
||||||
|
|
||||||
|
| 输入 | 行为 |
|
||||||
|
|---|---|
|
||||||
|
| 完整 11 位手机号(如 `17600545225`) | **后端加密后等值匹配 contact_phone 密文列** → 精确命中所有持此号订单 |
|
||||||
|
| 部分手机号(如 `176`、`5225`) | 不识别为手机号,不查 contact_phone(部分号匹不到加密订单是天然限制) |
|
||||||
|
| 姓名(`米明光`) | 仍 LIKE 模糊匹配 contact_name |
|
||||||
|
| 订单号(`HL20260509`) | 仍 LIKE 模糊匹配 order_no |
|
||||||
|
| 产品名(`冻干粉`) | 仍 LIKE 模糊匹配 product_name |
|
||||||
|
| 不传 / 空 | 不过滤(行为不变) |
|
||||||
|
|
||||||
|
### 前端建议
|
||||||
|
|
||||||
|
搜索框 placeholder 改为类似:
|
||||||
|
|
||||||
|
```
|
||||||
|
姓名 / 完整手机号 / 订单号 / 产品名
|
||||||
|
```
|
||||||
|
|
||||||
|
不要再写"手机号"模糊(部分手机号匹不到,会误导用户)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、`GET /admin/order/list` 排序(sortField)
|
||||||
|
|
||||||
|
### 改后枚举
|
||||||
|
|
||||||
|
| sortField | sortOrder | 含义 |
|
||||||
|
|---|---|---|
|
||||||
|
| `createTime` | desc(默认) / asc | 创建时间 |
|
||||||
|
| `departureDate` | desc / asc | 出发日期 |
|
||||||
|
| `totalPrice` | desc / asc | 订单金额 |
|
||||||
|
| `depositAmount` ⭐ | desc / asc | **订金金额(新增)** |
|
||||||
|
| 不传 / 未知值 | — | 兜底按 createTime DESC |
|
||||||
|
|
||||||
|
### 前端建议
|
||||||
|
|
||||||
|
排序下拉选项追加"订金金额(高→低)/(低→高)",业务场景是财务对账(找订金大但未付尾款的订单优先催)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、`GET /admin/order/list` 订单 tab 筛选 ⚠️ 重要
|
||||||
|
|
||||||
|
### 改前
|
||||||
|
|
||||||
|
只有 `status` 参数(匹配内部 `OrderStatus` 枚举 11 状态)。**原型"待出行" tab 传 `status=PENDING_DEPARTURE` 命中 0 单**(数据库实际分散在 DEPOSIT_PAID / CONFIRMED 等状态)。
|
||||||
|
|
||||||
|
### 改后
|
||||||
|
|
||||||
|
新增 `displayStatus` 参数,与 `status` 并存(AND 联合)。**原型 5 tab 必须用 `displayStatus`**:
|
||||||
|
|
||||||
|
| 原型 Tab | 前端传 `displayStatus` 值 | 后端命中范围 |
|
||||||
|
|---|---|---|
|
||||||
|
| 全部 | (不传) | 所有未删除订单 |
|
||||||
|
| 待支付 | `PENDING_PAY` | 内部 status = PENDING_PAY |
|
||||||
|
| **待出行** | `PENDING_DEPARTURE` | **折叠** DEPOSIT_PAID / PAID / CONFIRMED / PENDING_BALANCE / PENDING_DEPARTURE 5 个内部状态 |
|
||||||
|
| 待评价 | `PENDING_REVIEW` | (状态机决定) |
|
||||||
|
| 已完成 | `COMPLETED` | 内部 status = COMPLETED(且已评价) |
|
||||||
|
| 售后中 | `AFTER_SALE` | **折叠** REFUNDING / 部分 REFUNDED |
|
||||||
|
| 已取消 | `CANCELLED` | **折叠** CANCELLED / 部分 REFUNDED |
|
||||||
|
|
||||||
|
### 前端动作
|
||||||
|
|
||||||
|
按 tab 切换的参数从 `status` 改为 `displayStatus`。两个值的枚举不一样,**千万不要混用** —— 把内部 `status=PENDING_DEPARTURE` 当成 tab 传过去会拿到 0 单。
|
||||||
|
|
||||||
|
### 测试服实测对照
|
||||||
|
|
||||||
|
| 请求 | 命中 |
|
||||||
|
|---|---|
|
||||||
|
| `?displayStatus=PENDING_DEPARTURE` | **8 单** ⭐ |
|
||||||
|
| `?displayStatus=CANCELLED` | 26 单 |
|
||||||
|
| `?displayStatus=AFTER_SALE` | 2 单 |
|
||||||
|
| `?status=PENDING_DEPARTURE`(错用法) | 0 单 ❌ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、`GET /admin/order/{orderId}` 详情新增 `itinerary` 字段
|
||||||
|
|
||||||
|
### 字段结构
|
||||||
|
|
||||||
|
`OrderDetailVO` 新增:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
itinerary: OrderItineraryDayVO[] // 全部天数,不截断
|
||||||
|
|
||||||
|
OrderItineraryDayVO {
|
||||||
|
dayNumber: number // 1, 2, 3...
|
||||||
|
dayTitle: string // "京都·古都漫步"
|
||||||
|
route: string // "大阪 → 京都"
|
||||||
|
attractions: ItineraryAttractionVO[] // 当天 SCENIC + ACTIVITY 节点
|
||||||
|
hotels: ItineraryHotelVO[] // 当天酒店(已按订单档位 tierSeq 过滤)
|
||||||
|
}
|
||||||
|
|
||||||
|
ItineraryAttractionVO {
|
||||||
|
resourceId: string // Long 序列化为字符串
|
||||||
|
name: string // "清水寺"
|
||||||
|
nodeType: 'SCENIC' | 'ACTIVITY'
|
||||||
|
startTime: string | null // "09:30"
|
||||||
|
durationMinutes: number | null
|
||||||
|
description: string | null
|
||||||
|
coverImageUrl: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
ItineraryHotelVO {
|
||||||
|
hotelId: string
|
||||||
|
hotelName: string // "京都八坂町京町家"
|
||||||
|
roomTypeName: string // "豪华双床房"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 注意点
|
||||||
|
|
||||||
|
- **数据来源是订单下单时固化的产品快照**,不会随产品变化
|
||||||
|
- 老订单(快照 key 是 `itineraryDays`)也兼容解析
|
||||||
|
- 快照损坏时 `itinerary` 返回空数组(不会让整个详情接口挂)
|
||||||
|
- `hotels` 已经按订单 `tierSeq` 过滤 —— 不要担心一天显示 3 档候选酒店
|
||||||
|
|
||||||
|
### 真实示例(订单 `HL20260509114457-6539`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"itinerary": [
|
||||||
|
{
|
||||||
|
"dayNumber": 1,
|
||||||
|
"dayTitle": "京都古都漫步",
|
||||||
|
"route": "海拉尔区 → 额尔古纳市",
|
||||||
|
"attractions": [
|
||||||
|
{
|
||||||
|
"name": "中俄边境公路(卡线)",
|
||||||
|
"nodeType": "SCENIC",
|
||||||
|
"description": "沿额尔古纳河蜿蜒前行约200公里...",
|
||||||
|
"coverImageUrl": "https://hlgl-test.oss.../62943d9b....jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "驯鹿拉车体验",
|
||||||
|
"nodeType": "ACTIVITY",
|
||||||
|
"description": "..."
|
||||||
|
}
|
||||||
|
// ... 一天可能 6-7 个节点
|
||||||
|
],
|
||||||
|
"hotels": [
|
||||||
|
{ "hotelName": "呼伦贝尔香格里拉大酒店", "roomTypeName": "普通标间" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
// ... 多天行程
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 前端建议
|
||||||
|
|
||||||
|
详情页"行程概览"段:
|
||||||
|
- 按 `dayNumber` 排序,渲染 D1 / D2 / D3...
|
||||||
|
- 每天先 `route` + `dayTitle`,下面 `attractions` 列表 + `hotels` 列表(末日可能 `hotels` 为空,正常)
|
||||||
|
- `attractions` 可按 `nodeType` 分组("景点 SCENIC" / "活动 ACTIVITY"),也可不分组直接列
|
||||||
|
- `coverImageUrl` 是 OSS 完整 URL 可直接 `<img src>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、向后兼容
|
||||||
|
|
||||||
|
| 老前端不传新参数 / 不读新字段 | 行为 |
|
||||||
|
|---|---|
|
||||||
|
| 仅用 `status`(不用 `displayStatus`) | 完全不变(仍按内部状态 eq) |
|
||||||
|
| 不读 `itinerary` | 完全不变(多了字段不影响其他) |
|
||||||
|
| 不传 `sortField=depositAmount` | 完全不变 |
|
||||||
|
| keyword 输部分手机号 | 不影响其他字段 LIKE 命中(只是不再误命中加密列的明文残留) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、错误码 / 异常
|
||||||
|
|
||||||
|
无新增错误码。所有改动都是请求参数扩展或响应字段新增。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、测试建议(前端联调时验证)
|
||||||
|
|
||||||
|
| 验证项 | 操作 | 期望 |
|
||||||
|
|---|---|---|
|
||||||
|
| 完整手机号搜 | 搜 `17600545225` | 精确命中(如有该号订单) |
|
||||||
|
| 部分号搜 | 搜 `176` | 不命中加密订单(只命中订单号/姓名/产品名含 "176" 的) |
|
||||||
|
| 订金排序 | sortField=depositAmount&sortOrder=desc | 单调递减 |
|
||||||
|
| 待出行 tab | displayStatus=PENDING_DEPARTURE | 应命中(测试服 8 单) |
|
||||||
|
| 详情行程 | 打开任一已下单订单详情 | itinerary 非空数组 |
|
||||||
|
| 不传新参数 | 老接口调用方式 | 行为完全不变 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、关联
|
||||||
|
|
||||||
|
- PR #2903(itinerary)→ commit `c8f34411a`
|
||||||
|
- PR #2906(搜索+排序)→ commit `689d47013`
|
||||||
|
- PR #2909(手机号精确匹配,修 #2906 假阳性)→ commit `fbf803b1b`
|
||||||
|
- PR #2914(displayStatus 修待出行 tab)→ commit `8be30a562c`
|
||||||
|
- Issue #2899 / #2905 / #2908 / #2912 已全部 closed
|
||||||
|
- 负责人:yst(后端)
|
||||||
正在加载...
x
在新工单中引用
屏蔽一个用户