docs(changelog): 新建订单选主题向导接口对照-后端已实现仅需前端接线
这个提交包含在:
父节点
a8f1e490db
当前提交
e71ce5cef6
@ -0,0 +1,146 @@
|
||||
# 【前端接线·管理后台】「新建订单」向导第1步「选主题」接口对照——后端核心全已实现,仅需前端接线(含联动第2步「选产品」)
|
||||
|
||||
> 页面:管理后台 → 订单 → 新建订单(`order-v2/new`)第1步「选主题」、联动第2步「选产品」 | 服务:hl-product-service-v2(order-picker 端点)+ hl-order-service-v3(热门下单量,内部 Feign)| **无 PR,后端零改动** | 负责:mmg
|
||||
> 核验方式:2026-06-17 源码全链路审计(热门链路追到 order-v3 Mapper 真查订单表)+ 测试服 admin token 经网关 9443 实测 3 端点全 200
|
||||
|
||||
## ⚠️ 关键判定:核心接口全部已实现且实测可用,没有缺失的必备接口
|
||||
|
||||
这个页面用到的后端接口**全部已上线**,热门标识是**真实计算**(不是写死/Mock)。需要前端处理的只有 **2 处「前端本地实现」**(搜索、最近选过)、**1 处「确认删除」**(分类 tab,wx 已批注不需要)、**1 处「待 wx 拍板」**(卡片地区标签)。
|
||||
|
||||
> 术语对照(必读,否则字段对不上):前端「**主题**」= 后端「**产品线 ProductLine**」(lineId);前端「**产品/版本**」= 后端「**产品 Product**」(productId);前端「**SKU/规格/档位**」= 后端「**档位 tier**」(tierSeq)。
|
||||
|
||||
## 接口对照总表
|
||||
|
||||
| 页面数据块 | 后端落地 | 取数方式 | 前端动作 |
|
||||
|---|---|---|---|
|
||||
| 主题卡片网格 | ✅ 已实现 | `GET /admin/product/line/order-picker` | 直接接 |
|
||||
| 「热」角标 + 「热门主题·直接进入」 | ✅ 已实现(真实算) | 同上 `hot` 字段;入口=`filter(hot==true)` | 本地筛,**不要新接口** |
|
||||
| 顶部统计头(在售数/SKU总/最低人均) | ⚠️ 无接口 | 从上面列表前端派生 | 本地算 |
|
||||
| 搜索框(主题/目的地/关键词) | ⚠️ 无入参 | 在已返回列表本地模糊匹配 | 本地搜 |
|
||||
| 分类筛选 tab(西藏/云南/日本…) | ✅ 确认删除 | 后端无目的地数据源,无法过滤 | **直接删** |
|
||||
| 「最近选过」 | ⚠️ 无接口 | localStorage 本地历史 | 本地存取 |
|
||||
| 第2步「选择产品」 | ✅ 已实现 | `GET /admin/product/item/order-picker?lineId=` | 直接接 |
|
||||
| 选档位/规格 | ✅ 已实现 | `GET /admin/product/item/{productId}/tiers` | 直接接 |
|
||||
| 网关路由 | ✅ 全放行 | `/admin/product/**` → product-v2 | 经网关 9443 调通 |
|
||||
| 卡片「国内·西藏」地区标签 | ❓ 待拍板 | 第1步主题 VO 暂不返回(数据在 tags 里) | 见文末 |
|
||||
|
||||
## 1. 第1步「选主题」主接口(直接接)
|
||||
|
||||
**`GET /admin/product/line/order-picker`**(经网关 9443,路由已配 `/admin/product/**`)
|
||||
|
||||
- **入参**:无(不分页,一次性返回全部在售主题;数据权限由后端按当前管理员自动过滤)
|
||||
- **返回**:`Result<List<OrderPickerThemeVO>>`
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| `lineId` | String(雪花,序列化为字符串) | 主题ID,**下一步选产品要传它** |
|
||||
| `name` | String | 主题名称(布达拉宫朝圣…) |
|
||||
| `description` | String | 主题描述 |
|
||||
| `coverImageUrl` | String | 主题封面图 URL |
|
||||
| `productType` | String | `CORE`=核心产品 / `GROUP`=小蒙马 / `CUSTOM`=私人定制 |
|
||||
| `skuCount` | Integer | 该主题下已上架产品的档位总数(= 页面「X 个 SKU」) |
|
||||
| `fromPrice` | **String**(金额,可能为 null) | 最低成人起价(= 页面「起 ¥XXXX / 人」;全无价返 `null`,前端兜底) |
|
||||
| `hot` | Boolean | 是否热门(见第 2 节) |
|
||||
|
||||
- 只返「ACTIVE + 下挂 ≥1 个已上架产品」的在售主题,按排序号升序。
|
||||
- ⚠️ `fromPrice` 是**字符串**(后端 `@JsonSerialize(ToStringSerializer)`,防大数精度丢失),前端展示前自行 `parseFloat`。
|
||||
|
||||
请求示例:
|
||||
```
|
||||
GET https://api.test.1814.love:9443/admin/product/line/order-picker
|
||||
Authorization: Bearer {adminToken}
|
||||
```
|
||||
响应示例(节选,实测):
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{ "lineId": "2056937785918369794", "name": "呼伦贝尔产品-yst", "productType": "CORE",
|
||||
"skuCount": 6, "fromPrice": "3105.00", "hot": true,
|
||||
"coverImageUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/...png", "description": "..." },
|
||||
{ "lineId": "2043694738304880641", "name": "草原深度游", "productType": "CORE",
|
||||
"skuCount": 3, "fromPrice": "2980.00", "hot": false, "coverImageUrl": "...", "description": "..." }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 热门(「热」角标 + 「热门主题·直接进入」入口)——真实落地,无需新接口
|
||||
|
||||
- `hot` 是**唯一数据源**:近 90 天下单量在当前返回列表内**降序排名前 30%(向上取整、至少 1 名)且下单量 > 0** → `true`。
|
||||
- 链路已核到底:`product-v2` → 内部 Feign → `order-v3 GET /v3/internal/order/stats/product-order-count` → 真 `selectCount` 查 `order_main` 表(**非 Mock**)。实测当前测试库返回 1 个 `hot=true` 主题,证明实时计算生效。
|
||||
- **「热门主题·直接进入」不是独立接口** = 同一份 order-picker 列表里 `hot==true` 的子集,前端本地 `filter` 即可,**不要再找/要求新接口**。
|
||||
- **降级行为**:order-v3 不可用时,列表照常返回但所有 `hot=false`(不报错、不阻断),前端按 `hot=false` 正常渲染即可(此时无角标、无热门入口属预期降级)。
|
||||
- 口径说明(非 bug,设计如此):热门按「**下单量(含未支付/已取消单)**」算,不是「成交量」;在售主题很少时可能出现「几乎都标热」。如要改成「成交热」口径需 wx 另拍,目前保持现状。
|
||||
|
||||
## 3. 统计头 / 搜索框 / 最近选过 —— 前端本地实现,后端无接口(也不需要)
|
||||
|
||||
- **统计头**(在售主题数 / SKU 总数 / 最低人均):无独立接口,从第 1 节列表直接派生:
|
||||
- 在售主题数 = `list.length`
|
||||
- SKU 总数 = `Σ skuCount`
|
||||
- 最低人均 = `min(fromPrice)`(后端口径是「最低成人**起价**」,与「最低人均」基本等价;后端无单独「人均」概念)
|
||||
- **搜索框**:`order-picker` 零入参、不分页、一次返全量(量级 = 公司在售主题数,很小)。前端在已返回列表上按 `name` / `description` 本地模糊匹配即可,**后端无需加参**。
|
||||
- **「最近选过」**:后端无最近浏览接口(全范围 grep `recent/viewed/footprint/history` 仅命中小程序 C 端,与后台无关)。前端用 `localStorage` 自存浏览历史,无需对接后端。
|
||||
|
||||
## 4. 分类筛选 tab(全部/西藏/内蒙古/云南…)—— 确认删除
|
||||
|
||||
- wx 批注「不需要」正确,**额外实锤**:`product_line` 表**根本没有** region / 目的地 / 分类 任何结构化列(只有 name / product_type / description / seasons / tags / cover…),后端本就**无法**按目的地过滤主题。
|
||||
- → 前端**直接删**这排 tab,**不要让后端补地区维度**(那是另一个数据建模工程,非本页范畴)。
|
||||
|
||||
## 5. 第2步「选择产品」+ 选档位(直接接)
|
||||
|
||||
**5.1 选产品列表**:`GET /admin/product/item/order-picker?lineId={第1步选中的lineId}&keyword=&pageNo=1&pageSize=20`
|
||||
- `lineId` **必填**;`keyword` 可选(按产品名模糊);分页。
|
||||
- 返回 `PageResult<OrderPickerProductVO>`,字段(实测全有):
|
||||
|
||||
| 字段 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| `productId` | String | 产品ID(前端「版本」) |
|
||||
| `name` / `subtitle` | String | 产品名 / 副标题 |
|
||||
| `tripDays` / `tripNights` | Integer | 行程天数 / 晚数 |
|
||||
| `tags` | List\<String> | 产品标签(**实测返回 `["国内"]` 之类**) |
|
||||
| `coverImageUrl` | String | 封面图 |
|
||||
| `startPrice` | String(金额,可 null) | 起步价(各档位未来可售最低成人价的最小值) |
|
||||
| `tierPrices` | List | 档位起价列表(档位序号/名称/描述/该档起价) |
|
||||
| `hot` | Boolean | 是否热销(同主题热门口径,order-v3 90 天下单量前 30%) |
|
||||
| `nextSaleDate` | LocalDate(可 null) | 最近可售日(CORE/CUSTOM 取价格日历最早未来可售日,GROUP 取最近未来班期出发日) |
|
||||
|
||||
**5.2 选档位/规格**:`GET /admin/product/item/{productId}/tiers`
|
||||
- 返回 `List`,字段 `tierSeq`(规格序号,1 起)/ `tierName`(如「舒适」)/ `tierDescription`。
|
||||
- `tierSeq` 是**后续算价/下单的入参**,选档后带上。
|
||||
|
||||
## 6. 网关路由(无缺口)
|
||||
|
||||
- `/admin/product/**` → `lb://hl-product-service-v2`,**同时覆盖** `/admin/product/line/**` 与 `/admin/product/item/**`,两个 order-picker 端点经网关 9443 正常调通。
|
||||
- order-v3 的 `/v3/internal/...` 是 product-v2 内部 Feign LB 直连(不经网关,admin token 直调会被安全过滤返 403),**前端不直接调它**,不算路由缺口。
|
||||
|
||||
## ❓ 待 wx 拍板:卡片「国内·西藏」地区标签
|
||||
|
||||
页面每张主题卡片左上有「国内·西藏 / 国内·云南」地区标签,但:
|
||||
- 第1步 `order-picker`(`OrderPickerThemeVO`)**当前不返回任何标签字段**;
|
||||
- 不过实测发现**第2步产品 VO 已返回 `tags=["国内"]`**,且 `product_line` 表本身有 `tags`(JSON,字典 `line_tag`)——数据是存在的,只是第1步主题 VO 没透出。
|
||||
|
||||
两条路(**wx 定,后端不擅自改**):
|
||||
- **A(默认,零改动)**:分类 tab 既已删、搜索也只匹配名称/描述,卡片地区标签**一并不显示**,语义自洽,前端无需地区字段。
|
||||
- **B(要显示)**:后端给 `OrderPickerThemeVO` 补 `tags` 字段(数据已存在、第2步 VO 与 `InternalProductLineSimpleVO` 都已这么返,1 个字段、低风险),前端取 `tags` 渲染。**注**:`tags` 是自由打标的标签数组,「国内·」这层国别前缀目前**无干净来源**,B 路也只能显示打了什么标。
|
||||
|
||||
> 选 B 的话回复一句,我派后端加 `tags` 字段(小改,走批量 PR)。不回复默认按 A 处理,本页对接即闭环。
|
||||
|
||||
## 测试服实测(admin token 经网关 9443,2026-06-17)
|
||||
|
||||
| 端点 | 结果 |
|
||||
|---|---|
|
||||
| `GET /admin/product/line/order-picker` | code=200,9 个在售主题,SKU 总 33,最低起价 ¥10,热门 1 个(`hot=true` 实时算出) |
|
||||
| `GET /admin/product/item/order-picker?lineId=...` | code=200,返回字段含 productId/name/subtitle/tripDays/tripNights/tags/coverImageUrl/startPrice/tierPrices/hot/nextSaleDate |
|
||||
| `GET /admin/product/item/{productId}/tiers` | code=200,返回 tierSeq/tierName/tierDescription |
|
||||
|
||||
## 前端 TODO 清单
|
||||
|
||||
1. 第1步卡片网格:接 `GET /admin/product/line/order-picker`(无参)。
|
||||
2. 「热」角标:用返回项 `hot` 渲染;「热门主题·直接进入」= 本地 `filter(hot==true)`,不要新接口。
|
||||
3. 统计头:本地算(`length` / `Σ skuCount` / `min(fromPrice)`)。
|
||||
4. 搜索框:本地按 `name`/`description` 匹配,不传后端。
|
||||
5. **删除分类筛选 tab**(后端无数据源)。
|
||||
6. 「最近选过」:`localStorage` 本地实现。
|
||||
7. 第2步选产品:`GET /admin/product/item/order-picker?lineId={上一步lineId}`;选档位 `GET /admin/product/item/{productId}/tiers`,带 `tierSeq` 进算价/下单。
|
||||
8. 卡片地区标签:默认不显示(A);要显示回我一句走 B。
|
||||
9. 金额字段 `fromPrice`/`startPrice` 是**字符串**,展示前 `parseFloat`,注意 `null` 兜底。
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户