# 小程序已完成定制产品查询 - 前端对接指南

> **日期**: 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\<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令牌） |

### 响应示例

```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\<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：从定制需求跳转查看产品

```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)
