# 【前端接线·管理后台】「新建订单」向导第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>` | 字段 | 类型 | 含义 | |---|---|---| | `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`,字段(实测全有): | 字段 | 类型 | 含义 | |---|---|---| | `productId` | String | 产品ID(前端「版本」) | | `name` / `subtitle` | String | 产品名 / 副标题 | | `tripDays` / `tripNights` | Integer | 行程天数 / 晚数 | | `tags` | List\ | 产品标签(**实测返回 `["国内"]` 之类**) | | `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. 第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. 卡片地区标签:**不显示**(wx 已拍 A,后端零改动)。 9. 金额字段 `fromPrice`/`startPrice` 是**字符串**,展示前 `parseFloat`,注意 `null` 兜底。