diff --git a/changelogs/2026-04/2026-04-17-admin-product-detail-safeguard-tier-price.md b/changelogs/2026-04/2026-04-17-admin-product-detail-safeguard-tier-price.md new file mode 100644 index 0000000..f44734e --- /dev/null +++ b/changelogs/2026-04/2026-04-17-admin-product-detail-safeguard-tier-price.md @@ -0,0 +1,123 @@ +# 管理端产品详情 扩展 档位起步价 + Step5 模板展开字段 + +- **日期**: 2026-04-17 +- **服务**: hl-product-service-v2(端口 8083) +- **PR**: https://git.1814.love:8443/wx/HL/pulls/763 +- **Issue**: https://git.1814.love:8443/wx/HL/issues/760 +- **类型**: feat(非破坏性扩展,纯字段新增) +- **影响**: 管理端产品编辑/查看页可直接拿到起步价与 Step5 补充模板展开内容,无需二次查模板接口 + +## 变更类型 + +**非破坏性扩展**:对已有接口 `GET /admin/product/item/{id}` 的响应体新增若干字段。 +- 所有老字段不变、含义不变、类型不变 +- 旧前端客户端忽略新字段即可继续运行 +- 无需迁移 + +## 受影响接口 + +### `GET /admin/product/item/{id}` + +管理端产品详情接口。**签名与请求参数不变**。响应 VO `ProductDetailRespVO` 新增以下字段。 + +## 新增字段清单 + +### Step4 价格/班期 区块(3 个字段) + +| 字段 | 类型 | 含义 | 可能为 null 的情况 | +|------|------|------|-------------------| +| `startPrice` | BigDecimal | 起步价(未来可售最低成人售价) | 没有未来可售价格日历时为 `null` | +| `tierPrices` | `List` | 各档位起步价(管理端即使单档产品也会返回) | 全部档位价格已过期时为 `[]`(空数组,不是 null) | +| `priceCalendars` | `List` | 价格日历全量回显(含已过期、含零价,不做过滤) | 从未配置价格日历时为 `[]` | + +> **管理端 vs 小程序端**:小程序端通常会过滤掉过期与零价,管理端此处返回全量,方便后台编辑/校对。 + +### Step5 补充信息 区块(5 个字段,模板 ID 自动展开) + +前端无需再发 GET 模板详情请求,后端已根据 `supplement.xxxTemplateId` / `supplement.xxxId` 展开内容: + +| 字段 | 类型 | 含义 | 展开自 | 可能为 null 的情况 | +|------|------|------|--------|-------------------| +| `guaranteeItems` | Object/Array(透传 JSON) | 行程保障项 | `supplement.guaranteeTemplateId` 对应模板的 `items` JSON | `guaranteeTemplateId` 为 null 或模板不存在时为 `null` | +| `safetyItems` | Object/Array(透传 JSON) | 安全保障项 | `supplement.safetyTemplateId` 对应模板的 `items` JSON | `safetyTemplateId` 为 null 或模板不存在时为 `null` | +| `faqItems` | Object/Array(透传 JSON) | FAQ 问答 | `supplement.faqTemplateId` 对应模板的 `items` JSON | `faqTemplateId` 为 null 或模板不存在时为 `null` | +| `bookingTermsContent` | String | 预订条款内容(富文本) | `supplement.bookingTermsId` 对应的 `content` | `bookingTermsId` 为 null 或记录不存在时为 `null` | +| `warmTipsContent` | String | 温馨提示内容(富文本) | `supplement.warmTipsId` 对应的 `content` | `warmTipsId` 为 null 或记录不存在时为 `null` | + +> **设计原则**:若对应 ID 为 null,后端不会查询无意义的数据,直接回空。 + +## 内部类结构:`TierPriceInfo` + +`tierPrices` 数组的元素结构: + +```json +{ + "tierSeq": 1, + "tierName": "标准档", + "tierDescription": "标间 / 四人桌", + "startPrice": 2980.00 +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `tierSeq` | Integer | 档位序号(1、2、3…) | +| `tierName` | String | 档位名称 | +| `tierDescription` | String | 档位描述(房型/餐标等) | +| `startPrice` | BigDecimal | 该档位未来可售最低成人售价 | + +## 响应示例(节选,只展示新增部分) + +```json +{ + "code": 200, + "data": { + "productId": 1001, + "name": "呼伦贝尔6日游", + + "startPrice": 2980.00, + "tierPrices": [ + { "tierSeq": 1, "tierName": "标准档", "tierDescription": "标间", "startPrice": 2980.00 }, + { "tierSeq": 2, "tierName": "豪华档", "tierDescription": "大床房", "startPrice": 3480.00 } + ], + "priceCalendars": [ + { "date": "2026-05-01", "tierSeq": 1, "adultPrice": 2980.00, "childPrice": 1980.00, "stock": 20 } + ], + + "supplement": { + "guaranteeTemplateId": 11, + "safetyTemplateId": 12, + "faqTemplateId": 13, + "bookingTermsId": 21, + "warmTipsId": 22 + }, + "guaranteeItems": [ { "title": "全程WIFI", "desc": "..." } ], + "safetyItems": [ { "title": "随车医药箱", "desc": "..." } ], + "faqItems": [ { "q": "可以加床吗?", "a": "可以,需付费" } ], + "bookingTermsContent": "

下单后 24 小时内完成支付...

", + "warmTipsContent": "

请携带身份证原件...

" + } +} +``` + +## 使用建议 + +| 场景 | 推荐字段 | +|------|---------| +| 列表页/卡片展示"起步价¥xxx" | `startPrice` | +| 编辑页档位 tab 切换展示各档起价 | `tierPrices` | +| 价格日历编辑/核对(含过期、含零价) | `priceCalendars` | +| Step5 预览页面渲染"行程保障/安全保障/FAQ" | `guaranteeItems` / `safetyItems` / `faqItems` | +| Step5 预览页面渲染"预订条款/温馨提示"富文本 | `bookingTermsContent` / `warmTipsContent` | + +> 编辑模板 ID 选择时仍然使用 `supplement.xxxTemplateId` / `supplement.xxxId` 字段;本次新增的展开字段仅用于展示。 + +## 迁移建议 + +**无需迁移**。纯新增字段,老字段与老行为完全保持。 + +## 后端需重启的服务 + +- **hl-product-service-v2**(端口 8083) + +拉最新 `dev` 分支后重启该服务即可生效,其它服务不受影响。