11 KiB
【前端接线·管理后台】「新建订单」向导第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 调通 |
| 卡片「国内·西藏」地区标签 | ✅ wx 拍 A:不显示 | 第1步主题 VO 不返回,后端零改动 | 地区标签不显示 |
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}
响应示例(节选,实测):
{
"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 2026-06-17 拍 A)
页面每张主题卡片左上原有「国内·西藏 / 国内·云南」地区标签。第1步 order-picker(OrderPickerThemeVO)不返回任何标签字段(数据虽存在于 product_line.tags、第2步产品 VO 也返 tags,但第1步主题 VO 未透出)。
结论:分类 tab 既已删,卡片地区标签一并不显示,后端零改动。 前端去掉卡片上的地区标签,不需要地区字段。(备查:若日后又要显示,最低成本是后端给 OrderPickerThemeVO 补 tags 字段,1 个字段、低风险,但「国内·」国别前缀无干净来源。)
测试服实测(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步卡片网格:接
GET /admin/product/line/order-picker(无参)。 - 「热」角标:用返回项
hot渲染;「热门主题·直接进入」= 本地filter(hot==true),不要新接口。 - 统计头:本地算(
length/Σ skuCount/min(fromPrice))。 - 搜索框:本地按
name/description匹配,不传后端。 - 删除分类筛选 tab(后端无数据源)。
- 「最近选过」:
localStorage本地实现。 - 第2步选产品:
GET /admin/product/item/order-picker?lineId={上一步lineId};选档位GET /admin/product/item/{productId}/tiers,带tierSeq进算价/下单。 - 卡片地区标签:不显示(wx 已拍 A,后端零改动)。
- 金额字段
fromPrice/startPrice是字符串,展示前parseFloat,注意null兜底。