# 小程序已完成定制产品查询 - 前端对接指南 > **日期**: 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)