diff --git a/changelogs/2026-04/0402-product-grade.md b/changelogs/2026-04/0402-product-grade.md new file mode 100644 index 0000000..4cda517 --- /dev/null +++ b/changelogs/2026-04/0402-product-grade.md @@ -0,0 +1,117 @@ +# 产品等级功能 (PR #129) + +> 日期: 2026-04-02 | 服务: hl-product-service | 需重启: 是 + +## 新增功能 + +### 产品等级分组 +同一行程的不同档次(如轻奢版、高档版)可以关联为一组,小程序列表页合并为一张卡片,详情页可切换等级。 + +## 接口变更 + +### 1. 产品列表接口(C端) +**接口**: `GET /mp/product/list` +**变更类型**: 返回字段新增 + +列表中同等级分组的产品只返回一条(主等级),附带所有等级选项。 + +**新增返回字段**: +| 字段 | 类型 | 说明 | +|------|------|------| +| `productGrade` | String | 产品等级编码,null=无等级。值: `LIGHT_LUXURY`=轻奢, `HIGH_END`=高档 | +| `productGradeLabel` | String | 等级中文标签,如"轻奢" | +| `gradeGroupId` | String | 等级分组ID,null=独立产品 | +| `gradeOptions` | Array | 同组所有等级选项(含自己),null=无等级 | +| `gradeOptions[].productId` | String | 该等级对应的产品ID | +| `gradeOptions[].productGrade` | String | 等级编码 | +| `gradeOptions[].productGradeLabel` | String | 等级中文 | +| `gradeOptions[].startPrice` | Number | 该等级起步价 | +| `gradeOptions[].startPriceLabel` | String | "起" 或 "订金起" | + +**返回示例(有等级的产品)**: +```json +{ + "productId": "123", + "name": "呼伦贝尔5天亲子深度游", + "productGrade": "LIGHT_LUXURY", + "productGradeLabel": "轻奢", + "gradeGroupId": "123", + "gradeOptions": [ + {"productId": "123", "productGrade": "LIGHT_LUXURY", "productGradeLabel": "轻奢", "startPrice": 3999, "startPriceLabel": "起"}, + {"productId": "456", "productGrade": "HIGH_END", "productGradeLabel": "高档", "startPrice": 6999, "startPriceLabel": "起"} + ] +} +``` + +**返回示例(无等级的产品,不受影响)**: +```json +{ + "productId": "789", + "name": "丽江3天自由行", + "productGrade": null, + "gradeGroupId": null, + "gradeOptions": null +} +``` + +### 2. 产品详情接口(C端) +**接口**: `GET /mp/product/{productId}` +**变更类型**: 返回字段新增 + +product 对象中新增相同的等级字段(productGrade, productGradeLabel, gradeGroupId, gradeOptions)。 + +**前端切换等级的方式**: 用 `gradeOptions` 中其他等级的 `productId` 重新调用此接口即可,无需新接口。 + +### 3. 推荐产品列表(C端) +**接口**: `GET /mp/product/recommend`(内部Feign) +同样新增等级字段,同等级分组去重。 + +### 4. 管理端产品列表 +**接口**: `GET /admin/product/item/list` +新增字段: `productGrade`, `productGradeLabel`, `gradeGroupId`(不含gradeOptions,管理端不去重) + +### 5. 管理端产品详情 +**接口**: `GET /admin/product/item/{productId}` +新增字段同C端详情。 + +### 6. [新增] 等级分组查询 +**接口**: `GET /admin/product/item/grade-groups` +**参数**: `lineId`(可选,按产品线筛选) +**用途**: 管理端创建等级产品时,查询已有等级分组列表用于关联 +**返回**: 分组列表,每组含 gradeGroupId、groupName、products[] + +### 7. 产品保存接口 +**接口**: `POST /admin/product/item/save` +**新增请求字段**: +| 字段 | 类型 | 说明 | +|------|------|------| +| `productGrade` | String | 产品等级,不传=无等级 | +| `gradeGroupId` | Long | 等级分组ID,关联已有组时传入 | +| `gradeSort` | Integer | 等级排序,越小越前 | + +**校验规则**: +- 同一等级分组内不能有重复等级 +- 同一等级分组内的产品必须在同一产品线 +- 同一产品线只能包含同一类型的产品 + +## 前端开发指引 + +### 列表页 +- 判断 `gradeOptions != null && gradeOptions.length > 1` 时,显示等级价格标签 +- 卡片示例: `[轻奢 ¥3,999起] [高档 ¥6,999起]` +- `gradeOptions == null` 的产品照常展示 + +### 详情页 +- 判断 `gradeOptions != null && gradeOptions.length > 1` 时,顶部显示等级切换tab +- 当前等级高亮(匹配 productId) +- 点击其他等级 → 用该等级的 productId 重新请求详情接口 +- 报价/下单用当前页面的 productId,不受影响 + +## DDL(已在测试/正式服务器执行) +```sql +ALTER TABLE product + ADD COLUMN product_grade VARCHAR(16) DEFAULT NULL COMMENT '产品等级' AFTER product_type, + ADD COLUMN grade_group_id BIGINT DEFAULT NULL COMMENT '等级分组ID' AFTER product_grade, + ADD COLUMN grade_sort INT DEFAULT 0 COMMENT '等级排序' AFTER grade_group_id, + ADD INDEX idx_grade_group_id (grade_group_id); +```