新增2个C端接口:
- GET /mp/custom/product/{productId} 查询单个定制产品详情
- GET /mp/custom/products 我的已完成定制产品列表
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
15 KiB
15 KiB
小程序已完成定制产品查询 - 前端对接指南
日期: 2026-03-19 后端状态: ✅ 已完成,测试服务器已部署 涉及模块: hl-mp-service(BFF层)、hl-product-service(产品详情)、hl-order-service(定制需求关联) 页面路由: 定制需求详情页 → 查看定制产品
功能说明
用户提交定制需求后,定制师会设计一个定制产品(CUSTOM类型)。产品设计完成后状态变为 COMPLETED,用户下单后变为 ORDERED。
本次新增两个小程序端接口:
- 产品详情 — 根据产品ID查询单个已完成的定制产品(定制需求详情页展示产品方案)
- 产品列表 — 查询当前登录用户所有已完成的定制产品("我的定制"页展示列表)
业务流程
用户提交定制需求 → 定制师接单 → 设计产品(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令牌) |
响应示例
{
"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<String> | 轮播图URL列表 |
| tags | Array<String> | 产品标签 |
| seasons | Array<String> | 适用季节编码 |
| seasonLabels | Array<String> | 季节中文标签 |
| 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令牌) |
响应示例
{
"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<String> | 产品标签 |
| seasons | Array<String> | 适用季节编码 |
| seasonLabels | Array<String> | 季节中文标签 |
| 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:从定制需求跳转查看产品
// 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:展示"我的定制产品"列表
// 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