diff --git a/changelogs/2026-03/2026-03-19_custom_product_query.md b/changelogs/2026-03/2026-03-19_custom_product_query.md new file mode 100644 index 0000000..4ab850c --- /dev/null +++ b/changelogs/2026-03/2026-03-19_custom_product_query.md @@ -0,0 +1,397 @@ +# 小程序已完成定制产品查询 - 前端对接指南 + +> **日期**: 2026-03-19 +> **后端状态**: ✅ 已完成,测试服务器已部署 +> **涉及模块**: hl-mp-service(BFF层)、hl-product-service(产品详情)、hl-order-service(定制需求关联) +> **页面路由**: 定制需求详情页 → 查看定制产品 + +--- + +## 功能说明 + +用户提交定制需求后,定制师会设计一个定制产品(CUSTOM类型)。产品设计完成后状态变为 `COMPLETED`,用户下单后变为 `ORDERED`。 + +本次新增两个小程序端接口: +1. **产品详情** — 根据产品ID查询单个已完成的定制产品(定制需求详情页展示产品方案) +2. **产品列表** — 查询当前登录用户所有已完成的定制产品("我的定制"页展示列表) + +### 业务流程 + +``` +用户提交定制需求 → 定制师接单 → 设计产品(DRAFT) + → 完成设计(COMPLETED) → 用户查看产品方案 → 下单(ORDERED) + ↑ + 本次新增接口覆盖此环节 +``` + +### 聚合调用链 + +``` +小程序 → /mp/custom/products (BFF) + ├─ Feign → order-service: 获取用户定制需求关联的 productId 列表 + └─ Feign → product-service: 批量查询 CUSTOM + COMPLETED/ORDERED 产品 +``` + +--- + +## 接口清单 + +| # | 接口 | 方法 | 路径 | 认证 | 说明 | +|---|------|------|------|------|------| +| 1 | 已完成定制产品详情 | GET | `/mp/custom/product/{productId}` | ✅ 需登录 | 根据产品ID查询定制产品详情 | +| 2 | 我的已完成定制产品列表 | GET | `/mp/custom/products` | ✅ 需登录 | 当前用户的所有已完成定制产品 | + +--- + +## 接口 1:已完成定制产品详情 + +**使用场景**:定制需求详情页中,当定制师完成产品设计后(状态 QUOTED/COMPLETED),前端通过定制需求中的 `productId` 调用此接口展示完整产品方案。 + +``` +GET /mp/custom/product/{productId} +``` + +### 请求参数 + +| 参数 | 类型 | 位置 | 必填 | 说明 | +|------|------|------|------|------| +| productId | Long | 路径 | ✅ | 定制产品ID(来自定制需求详情中的 `productId` 字段) | + +### 请求头 + +| Header | 说明 | +|--------|------| +| Authorization | Bearer {token}(JWT令牌) | + +### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "productId": "2026279738275856385", + "productNo": "P20260319001", + "productType": "CUSTOM", + "name": "呼伦贝尔7日定制游-张三家庭", + "subtitle": "专属家庭定制方案", + "status": "COMPLETED", + "tripDays": 7, + "tripNights": 6, + "departureCity": "北京", + "destinationCity": "呼伦贝尔", + "coverImageUrl": "https://oss.example.com/cover.jpg", + "bannerImageUrls": ["https://oss.example.com/banner1.jpg"], + "tags": ["草原", "亲子", "定制"], + "seasons": ["SUMMER"], + "seasonLabels": ["夏季"], + "highlights": "...", + "description": "...", + "inclusions": "费用包含说明", + "exclusions": "费用不含说明", + "notes": "注意事项", + "routeMapUrl": "https://oss.example.com/route-map.jpg", + "itinerary": [ + { + "dayIndex": 1, + "title": "北京 → 海拉尔", + "description": "...", + "nodes": [...] + } + ], + "staffConfigs": [ + { + "staffType": "GUIDE", + "staffName": "李导", + "staffCount": 1, + "dailyCost": 500.00 + } + ], + "families": [ + { + "familyIndex": 1, + "familyName": "张三家庭", + "adultCount": 2, + "childCount": 1 + } + ], + "creatorAvatarUrl": "https://oss.example.com/designer-avatar.jpg", + "customizerId": "2026279738275856300", + "createTime": "2026-03-19T10:30:00", + "updateTime": "2026-03-19T15:00:00" + } +} +``` + +### 响应字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| productId | String | 产品ID(雪花ID,用String接收) | +| productNo | String | 产品编号(P开头) | +| productType | String | 产品类型,固定 `CUSTOM` | +| name | String | 产品名称 | +| subtitle | String | 副标题 | +| status | String | 产品状态:`COMPLETED`=已完成可下单、`ORDERED`=已有订单 | +| tripDays | Integer | 行程天数 | +| tripNights | Integer | 住宿晚数 | +| departureCity | String | 出发城市 | +| destinationCity | String | 目的地城市 | +| coverImageUrl | String | 封面图URL | +| bannerImageUrls | Array\ | 轮播图URL列表 | +| tags | Array\ | 产品标签 | +| seasons | Array\ | 适用季节编码 | +| seasonLabels | Array\ | 季节中文标签 | +| highlights | String | 产品亮点 | +| description | String | 详细描述 | +| inclusions | String | 费用包含 | +| exclusions | String | 费用不含 | +| notes | String | 注意事项 | +| routeMapUrl | String | 路线图URL | +| itinerary | Array | 行程安排(按天组织) | +| itinerary[].dayIndex | Integer | 第几天 | +| itinerary[].title | String | 当天标题 | +| itinerary[].description | String | 当天描述 | +| itinerary[].nodes | Array | 行程节点列表 | +| **staffConfigs** | Array | **人员配置(定制产品特有)** | +| staffConfigs[].staffType | String | 人员类型(GUIDE=导游等) | +| staffConfigs[].staffName | String | 人员姓名 | +| staffConfigs[].staffCount | Integer | 人数 | +| staffConfigs[].dailyCost | BigDecimal | 日费用 | +| **families** | Array | **家庭分组(定制产品特有)** | +| families[].familyIndex | Integer | 家庭序号 | +| families[].familyName | String | 家庭名称 | +| families[].adultCount | Integer | 成人数 | +| families[].childCount | Integer | 儿童数 | +| customizerId | String | 定制师ID | +| creatorAvatarUrl | String | 定制师头像URL | +| createTime | String | 创建时间 | +| updateTime | String | 更新时间 | + +### 错误响应 + +| code | message | 说明 | +|------|---------|------| +| 404 | 产品不存在 | productId 无效 | +| 404 | 产品不存在(非定制产品) | 不是 CUSTOM 类型 | +| 404 | 定制产品尚未完成 | 状态不是 COMPLETED/ORDERED | + +### 前端实现建议 +- 定制需求详情页中,当 `status` 为 `QUOTED` 或 `COMPLETED` 且 `productId` 不为空时,展示"查看定制方案"按钮 +- 点击按钮调用此接口,跳转到产品详情页展示完整方案 +- `staffConfigs` 和 `families` 是定制产品特有字段,需单独展示区域 + +--- + +## 接口 2:我的已完成定制产品列表 + +**使用场景**:"我的定制"页面展示用户所有已完成的定制产品列表。 + +``` +GET /mp/custom/products +``` + +### 请求参数 + +无(自动取当前登录用户) + +### 请求头 + +| Header | 说明 | +|--------|------| +| Authorization | Bearer {token}(JWT令牌) | + +### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": [ + { + "productId": "2026279738275856385", + "productType": "CUSTOM", + "name": "呼伦贝尔7日定制游-张三家庭", + "subtitle": "专属家庭定制方案", + "tripDays": 7, + "tripNights": 6, + "departureCity": "北京", + "destinationCity": "呼伦贝尔", + "coverImageUrl": "https://oss.example.com/cover.jpg", + "tags": ["草原", "亲子", "定制"], + "seasons": ["SUMMER"], + "seasonLabels": ["夏季"], + "routeMapUrl": "https://oss.example.com/route-map.jpg", + "creatorAvatarUrl": "https://oss.example.com/designer-avatar.jpg", + "startPrice": 29800.00, + "paymentMode": "DEPOSIT", + "lineName": null + } + ] +} +``` + +### 响应字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| productId | String | 产品ID | +| productType | String | 产品类型,固定 `CUSTOM` | +| name | String | 产品名称 | +| subtitle | String | 副标题 | +| tripDays | Integer | 行程天数 | +| tripNights | Integer | 住宿晚数 | +| departureCity | String | 出发城市 | +| destinationCity | String | 目的地城市 | +| coverImageUrl | String | 封面图URL | +| tags | Array\ | 产品标签 | +| seasons | Array\ | 适用季节编码 | +| seasonLabels | Array\ | 季节中文标签 | +| routeMapUrl | String | 路线图URL | +| creatorAvatarUrl | String | 定制师头像 | +| startPrice | BigDecimal | 起步价(整单售价),可能为null(未设置价格日历) | +| paymentMode | String | 支付方式:`FULL`-全额、`DEPOSIT`-定金 | +| lineName | String | 产品线名称,可能为null | + +### 前端实现建议 +- 用 `uni-list` 或自定义卡片列表展示 +- 每张卡片展示:封面图 + 名称 + 目的地 + 天数 + 起步价 +- 点击卡片跳转到接口1查看完整详情 +- 列表为空时展示空状态:"暂无定制产品,去提交定制需求吧" +- 定制产品为**整单计价**(perOrderStock),`startPrice` 是整单价格,不是每人价格 + +--- + +## 关联字典 + +| 字典类型 | 字典值 | 中文标签 | 说明 | +|----------|--------|----------|------| +| **product_type** | CUSTOM | 私人定制 | 定制产品类型 | +| **product_status** | COMPLETED | 已完成 | 定制师设计完成,可下单 | +| | ORDERED | 已下单 | 已有活跃订单 | +| **customize_request_status** | QUOTED | 已报价 | 定制需求中产品已关联 | +| | COMPLETED | 已完成 | 定制需求流程完结 | +| **season** | SPRING | 春季 | | +| | SUMMER | 夏季 | | +| | AUTUMN | 秋季 | | +| | WINTER | 冬季 | | +| | ALL_SEASON | 全年 | | + +--- + +## 校验规则 + +- **接口1**:productId 必须为 CUSTOM 类型且状态为 COMPLETED 或 ORDERED,否则返回404 +- **接口2**:自动关联当前用户的定制需求(status=QUOTED/COMPLETED 且 productId 不为空),无需传参 +- 定制产品**一单一产品**:同一个定制产品同时只能有一个有效订单 +- 定制产品为**整单计价**:价格日历中的 `adultSellPrice` 即整单售价,不按人头乘以 + +--- + +## 页面布局建议 + +### "我的定制"列表页 + +``` +┌─────────────────────────────────────────┐ +│ 我的定制产品 │ +├─────────────────────────────────────────┤ +│ ┌───────────────────────────────────┐ │ +│ │ [封面图] 呼伦贝尔7日定制游-张三家庭 │ │ +│ │ 北京 → 呼伦贝尔 · 7天6晚 │ │ +│ │ ¥29,800/整单 │ │ +│ │ 定制师: [头像] 李设计师 │ │ +│ └───────────────────────────────────┘ │ +│ ┌───────────────────────────────────┐ │ +│ │ [封面图] 三亚5日定制游-王先生 │ │ +│ │ 上海 → 三亚 · 5天4晚 │ │ +│ │ ¥18,600/整单 │ │ +│ └───────────────────────────────────┘ │ +│ │ +│ ── 没有更多了 ── │ +└─────────────────────────────────────────┘ +``` + +### 定制产品详情页(接口1展示) + +``` +┌─────────────────────────────────────────┐ +│ [轮播图 bannerImageUrls] │ +├─────────────────────────────────────────┤ +│ 呼伦贝尔7日定制游-张三家庭 │ +│ 专属家庭定制方案 │ +│ 北京 → 呼伦贝尔 · 7天6晚 │ +│ 标签: [草原] [亲子] [定制] │ +├─────────────────────────────────────────┤ +│ 👨‍👩‍👧 家庭分组 │ +│ 家庭1: 张三家庭(2大1小) │ +├─────────────────────────────────────────┤ +│ 👤 人员配置 │ +│ 导游: 李导 × 1人 │ +├─────────────────────────────────────────┤ +│ 📅 行程安排 │ +│ Day 1: 北京 → 海拉尔 │ +│ Day 2: 莫日格勒河 → 额尔古纳 │ +│ ... │ +├─────────────────────────────────────────┤ +│ [路线图 routeMapUrl] │ +├─────────────────────────────────────────┤ +│ 费用包含 / 费用不含 / 注意事项 │ +├─────────────────────────────────────────┤ +│ [立即预订 ¥29,800] │ +└─────────────────────────────────────────┘ +``` + +--- + +## 完整调用示例 + +### 示例1:从定制需求跳转查看产品 + +```javascript +// 1. 获取定制需求详情(已有接口) +const detail = await request.get('/mp/custom/detail', { params: { id: requestId } }) +const { productId, status } = detail.data + +// 2. 如果定制师已完成产品设计,跳转查看 +if (productId && ['QUOTED', 'COMPLETED'].includes(status)) { + uni.navigateTo({ + url: `/pages/custom/product-detail?productId=${productId}` + }) +} + +// 3. 在产品详情页加载数据 +const product = await request.get(`/mp/custom/product/${productId}`) +// product.data 包含完整产品信息 +``` + +### 示例2:展示"我的定制产品"列表 + +```javascript +// 1. 获取列表 +const res = await request.get('/mp/custom/products') +const products = res.data // Array + +// 2. 列表为空时展示空状态 +if (!products || products.length === 0) { + // 展示 "暂无定制产品" + return +} + +// 3. 渲染列表 +products.forEach(item => { + // item.name, item.coverImageUrl, item.tripDays, item.startPrice + // 注意: startPrice 是整单价格 +}) + +// 4. 点击跳转详情 +function onItemClick(productId) { + uni.navigateTo({ + url: `/pages/custom/product-detail?productId=${productId}` + }) +} +``` + +--- + +🤖 Generated with [Claude Code](https://claude.com/claude-code)