From fd3e6659c2f59dcb9fb5c01fa69fefbe21e8f9b5 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Thu, 30 Apr 2026 13:59:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(product):=20=E6=96=B0=E5=A2=9E=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=AB=AF=E4=BA=A7=E5=93=81=E6=9C=80=E4=BD=8E=E4=BB=B7?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=20(PR=20#1558=20=E4=BB=BB=E5=8A=A1A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 GET /admin/product/item/{id}/min-price 专用轻量起价接口 通知前端 yst/mmg 把编辑页 MobilePreview "行程报价" 切到新接口 --- .../30_feat_product_admin-min-price-api.md | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 changelogs/2026-04/30_feat_product_admin-min-price-api.md diff --git a/changelogs/2026-04/30_feat_product_admin-min-price-api.md b/changelogs/2026-04/30_feat_product_admin-min-price-api.md new file mode 100644 index 0000000..9382d85 --- /dev/null +++ b/changelogs/2026-04/30_feat_product_admin-min-price-api.md @@ -0,0 +1,122 @@ +# 新增管理端接口:产品最低价(用于编辑页"行程报价"预览) + +**类型**: 后端 FEAT(新增接口) + 前端切换通知 +**关联**: 工单 #1556 / PR #1558 / Release PR #1559 +**日期**: 2026-04-30 +**前端处理者**: yst / mmg +**影响范围**: 管理后台产品编辑页右侧"预览面板 → 行程报价"区域 + +--- + +## 现象(BUG) + +管理后台产品编辑页 (`web.test.1814.love:9443/product/edit?id=...`) 右侧预览面板的"行程报价"区域,**两个档位(舒适/高档)都显示 `¥-- 起/成人`**,即使 Step 4 定价管理已实际配置价格(用户截图: 平日价 ¥45,455 已落 product_price_calendar)。 + +## 根因 + +不是字段 BUG,详情接口 `/admin/product/item/{id}` 已通过 `PriceCalendarAggregator` 回填 startPrice/tierPrices, 但 3 个边界场景导致预览 ¥--: + +1. **全过期 calendar** 时 `keepEmptyTiers=false` 把无价档全过滤掉 +2. **`product.tiers` 配多档但 calendar 只配了 tier 1** → tier 2 在详情接口被剔除前端拿不到 +3. **详情接口 17 次 DB 读太重**, Step 4 改完价后预览不刷新 + +## 解法 — 新增专用轻量起价接口 + +```http +GET /admin/product/item/{productId}/min-price +Authorization: Bearer {adminToken} +``` + +### Response (`ProductMinPriceRespVO`) + +```jsonc +{ + "code": 0, + "message": "success", + "data": { + "productId": "2049468625294708737", + "productType": "CUSTOM", + "priceSource": "PRICE_CALENDAR", // PRICE_CALENDAR=价格日历, GROUP_BATCH=班期表 + "globalMinAdultPrice": 45455.00, // 全档汇总最低成人价(无价时 null) + "currency": "CNY", + "futurePriceFromDate": "2026-05-01", + "futurePriceToDate": "2026-05-31", + "hasMultiTier": true, // tiers.size > 1 + "tiers": [ + { + "tierSeq": 1, + "tierName": "舒适", + "tierDescription": "4钻酒店", + "minAdultPrice": 45455.00, // null 表示该档无未来可售价 + "priceFromDate": "2026-05-01", + "priceToDate": "2026-05-31" + }, + { + "tierSeq": 2, + "tierName": "高档", + "tierDescription": "5星酒店", + "minAdultPrice": null, // 未配置 → null,前端按 null 显 ¥-- + "priceFromDate": null, + "priceToDate": null + } + ] + } +} +``` + +### 字段说明 +- `productType` 字典 `product_type`: CORE=核心 / GROUP=小蒙马 / CUSTOM=私人定制 +- `priceSource` 字典(代码内): + - `PRICE_CALENDAR` — CORE/CUSTOM 走 `product_price_calendar`,按 tierSeq 分组取 MIN(adultSellPrice) + - `GROUP_BATCH` — GROUP 走 `group_tour_batch.adult_price` MIN +- 仅查未来可售(`date >= today` 且 `adultSellPrice > 0`) +- **保留所有档位**: 即使该档无价,`tiers[]` 里仍有该档的元素,`minAdultPrice=null` — 前端按 null 显 `¥--` 与列表语义一致 +- `globalMinAdultPrice` 是所有 tier 中非 null 价的全局 min,前端"整体起价"用 +- DB 读仅 2 次(产品主表 + 价格源),详情接口 17 次 + +### 错误码 +- `code=0` — 成功 +- `code=404` — 产品不存在或当前 admin 无 DataScope 权限(行级权限拦截) + +## 前端要做的改动 ⚠️ + +### 替换数据源 +`hl-ui` 中产品编辑页右侧 `MobilePreview.vue / PreviewPricing.vue` 等组件,把"行程报价"区的取价逻辑由"复用详情接口的 startPrice/tierPrices"改为**单独调用** `GET /admin/product/item/{id}/min-price`。 + +### 触发时机 +- 进入编辑页:产品基础信息加载完之后调一次 +- Step 4 定价管理保存成功后:**主动 refetch** `/min-price`(详情接口缓存了 17 次 DB 聚合,不会立刻反映改价;新接口 2 次 DB 读,可频繁刷) +- 切换 Step 时无需 refetch(价格不会因切 Step 变化) + +### 渲染规则 +- `data.tiers` 非空 → 按 `tierSeq` 排序逐档渲染 + - `minAdultPrice != null` → 显示金额 + "起/成人" + - `minAdultPrice == null` → 显示 `¥--` 起/成人(用户配置了该档位但未配价) +- `data.globalMinAdultPrice == null && data.tiers` 全 null → 整个面板都显 `¥--`(产品完全没配价) +- 不需要再走老的 `tierPrices/startPrice` 字段链路降级 — 新接口语义完整覆盖 + +### 测试场景(测试服已 round-trip 通过) +| 场景 | 接口返回 | 前端预期 | +|---|---|---| +| 双档全有价 | tiers[0].min=7960, tiers[1].min=7960, hasMultiTier=true | 两档都显 ¥7960 起 | +| 单档已配价 | tiers[0].min=1440, hasMultiTier=false | 一档显 ¥1440 起 | +| 双档配了 tier1 没配 tier2 | tiers[0].min=有价, tiers[1].min=null | 舒适显价 + 高档显 `¥--` | +| 全过期/未配价 | globalMinAdultPrice=null, tiers 全 null | 全档 `¥--` | +| GROUP 小蒙马 | priceSource=GROUP_BATCH, 价格从班期表取 MIN | 同 CORE 渲染 | + +### 接口参考产品(测试服) +- `2047268852147875842` — PUBLISHED CORE 双档舒适+品质,全 7960 +- `2046104743003971585` — DRAFT CORE 单档轻奢,1440 + +## 部署状态 + +- ✅ PR #1558 合并到 dev (sha 30904060) +- ✅ Deploy Panel task 1b109e5c success, 双实例 hl-product-service-v2:8083+8183 滚动重启完成 +- ✅ 测试服经网关 9443 round-trip 3 个产品全通过 +- ⏳ Release PR #1559 dev → main 待用户网页合,合后通知正式服务器管理员部署正式 + +## 向后兼容 + +- `/admin/product/item/{id}` 详情接口字段**完全不变**(仍返 `tierPrices/startPrice/priceCalendars`) +- 前端老的 `MobilePreview.vue / PreviewPricing.vue` 三级 fallback 链路不会因后端报错(只是仍显 ¥--) +- 切换可分阶段:先把"行程报价"区切到新接口,其他依赖 startPrice/tierPrices 的地方按需后切