# 产品模块v2 — 前端联调指南

> **日期**：2026-04-13
> **服务**：hl-product-service-v2（本地端口 8086 / 测试环境端口 8083）
> **在线文档（Knife4j）**：
> - **测试环境（局域网）**：http://192.168.100.236:8080/doc.html
> - **测试环境（公网）**：https://api.test.1814.love:9443/doc.html （需要登录认证）
> - 左侧下拉选择以下分组：
>   - `1. 管理端 - 产品管理` — 产品CRUD、5步保存、价格日历、班期、报价
>   - `2. 管理端 - 产品线管理` — 产品线CRUD
>   - `3. 管理端 - 模板管理` — 预订条款、温馨提示
>   - `4. 小程序端 - 产品查询` — C端产品列表、详情、价格日历、报价
>   - `5. 小程序端 - 产品线查询` — C端产品线列表
> - 本地直连：http://localhost:8086/doc.html
>
> **网关路由**：管理端 `/admin/product/**` → hl-product-service-v2，C端 `/mp/product/**` → hl-product-service-v2
> **重要**：本模块是全新服务，与旧 hl-product-service 并行运行。所有接口路径相同但路由已切换到v2。
> **本文档包含全部接口定义**，无需查看其他文件或在线文档。

---

## 一、整体架构变化

### 旧服务 vs 新服务

| 维度 | 旧 hl-product-service | 新 hl-product-service-v2 |
|------|----------------------|--------------------------|
| 产品类型 | CORE/GROUP/CUSTOM/ROUTE（4种） | CORE/GROUP/CUSTOM（3种，ROUTE废弃） |
| 保存方式 | 每个字段独立接口 | **5个业务域整体保存**（基础/行程/路线/定价/补充） |
| 定价模型 | 统一价格日历 | **双模型**：价格区间（CORE/CUSTOM）+ 班期（GROUP） |
| 住宿 | 统一 | 支持**多档位**（舒适/豪华/高端等） |
| 人群分档 | 成人/儿童 | **4档**：成人/儿童/小童/幼童 |
| 并发控制 | 无 | **乐观锁**（version字段） |

### 乐观锁机制（所有保存接口通用）

```
1. GET 详情 → 拿到 version 字段
2. PUT 保存 → 请求体带 version
3. 成功 → version+1（下次保存用新version）
4. 失败(409) → 提示"数据已被其他人修改，请刷新"，重新GET
```

> **已上架产品调价**：用 `priceVersion` 而非 `version`（调价不占主version，允许调价和内容编辑互不阻塞）

---

## 二、管理端页面接口对照

### 页面1：产品列表

```
┌──────────────────────────────────────────────────────────────┐
│  [搜索: 名称/编号]  [类型▼]  [产品线▼]  [状态▼]  [标签▼]    │
│                                                    [+ 新建产品] │
├──────────────────────────────────────────────────────────────┤
│  封面  │ 名称/编号  │ 类型  │ 产品线 │ 状态 │ 操作          │
│  ...   │ ...        │ ...   │ ...    │ ...  │ 编辑/复制/删除│
└──────────────────────────────────────────────────────────────┘
```

**接口**：

| 场景 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 产品列表 | GET | `/admin/product/item/list` | 分页，支持keyword/productType/lineId/status/tag筛选 |
| 产品线下拉 | GET | `/admin/product/line/simple-list` | 返回 `[{lineId, name, productType}]`，用于筛选和创建 |
| 创建产品 | POST | `/admin/product/item` | 传 name+productType+lineId+tripDays，返回productId |
| 删除产品 | DELETE | `/admin/product/item/{id}` | 需先下架 |
| 复制产品 | POST | `/admin/product/item/{id}/copy` | 返回新productId |
| 状态变更 | PUT | `/admin/product/item/{id}/action` | 传 action 枚举，见下方状态操作表 |
| 操作记录 | GET | `/admin/product/item/{id}/operation-logs` | 分页，查看历史操作 |

**产品列表请求参数**：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| keyword | String | 否 | 搜索关键词（匹配名称/编号） |
| productType | String | 否 | 产品类型筛选(CORE/GROUP/CUSTOM) |
| lineId | Long | 否 | 产品线ID筛选 |
| status | String | 否 | 状态筛选(DRAFT/PUBLISHED等) |
| tag | String | 否 | 标签筛选 |
| page | Integer | 否 | 页码(默认1) |
| pageSize | Integer | 否 | 每页条数(默认20) |

**产品列表响应字段**（`PageResult<ProductListRespVO>`中每条）：

| 字段 | 类型 | 说明 |
|------|------|------|
| productId | Long | 产品ID |
| productNo | String | 产品编号(如C260409001) |
| name | String | 产品名称 |
| productType | String | 产品类型 |
| status | String | 产品状态 |
| coverImageUrl | String | 封面图 |
| tripDays | Integer | 行程天数 |
| lineId | Long | 产品线ID |
| lineName | String | 产品线名称 |
| tags | List\<String\> | 产品标签 |
| startPrice | BigDecimal | 起步价 |
| createBy | Long | 创建人ID |
| createTime | LocalDateTime | 创建时间 |
| publishedAt | LocalDateTime | 上架时间 |

**创建产品请求**：

```json
{
  "name": "额吉的故乡·亲子版",
  "productType": "CORE",
  "lineId": 100,
  "tripDays": 6
}
```

**创建产品响应**：`Result<Long>` — 返回新产品ID

**状态变更请求**：

```json
{
  "productId": 123,
  "action": "SUBMIT_PUBLISH",
  "remark": "提交上架"
}
```

**状态操作枚举**（action字段）：

| action值 | 操作 | 前置状态 | 目标状态 |
|----------|------|---------|---------|
| SUBMIT_PUBLISH | 提交上架 | DRAFT | PENDING_REVIEW |
| WITHDRAW | 撤回审批 | PENDING_REVIEW | DRAFT |
| DIRECT_PUBLISH | 直接上架(超管) | DRAFT | PUBLISHED |
| UNPUBLISH | 申请下架 | PUBLISHED | PENDING_REVIEW(下架) |
| FORCE_UNPUBLISH | 强制下架(超管) | PUBLISHED | UNPUBLISHED |
| COMPLETE | 完成设计(定制) | DRAFT | COMPLETED |

---

### 页面2：产品编辑 — Step1 基础信息

```
┌──────────────────────────────────────────────────┐
│  Step1   Step2   Step3   Step4   Step5           │
│  ●基础   ○行程   ○路线   ○定价   ○补充           │
├──────────────────────────────────────────────────┤
│  产品名称: [____________]                         │
│  副标题:   [____________]                         │
│  产品类型: CORE (只读，创建时选定)                 │
│  产品线:   [下拉选择▼]                            │
│  行程天数: [6] 晚数: [5]                          │
│  住宿规格: ○单一 ○多档 → [舒适][豪华][+添加档位]  │
│  季节:     □春 ☑夏 ☑秋 □冬                       │
│  标签:     ☑亲子 ☑研学 □露营                      │
│  封面图:   [上传]                                  │
│  轮播图:   [上传1] [上传2] ... (≤10张)            │
│  年龄设置: 幼童≤[1]岁  小童≤[3]岁  儿童[4]-[12]岁│
│  幼童默认价: [0]元                                 │
│  支付方式:  ○全款 ○订金([30]%/固定[___]元)        │
│  产品简介:  [富文本编辑器]                         │
│                                    [保存]          │
└──────────────────────────────────────────────────┘
```

**接口**：

| 场景 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 获取详情(回显) | GET | `/admin/product/item/{id}` | 返回全部5步数据（`ProductDetailRespVO`），各步骤取对应部分回显 |
| 保存基础信息 | PUT | `/admin/product/item/{id}/basic` | `ProductBasicSaveReqVO`，带version |
| 产品线下拉 | GET | `/admin/product/line/simple-list` | 创建/修改时选产品线 |

**Step1 基础信息完整字段表**：

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| version | Integer | ✅ | 乐观锁版本号（从详情接口获取） |
| name | String | ✅ | 产品名称（≤30字） |
| subtitle | String | 否 | 副标题（≤128字） |
| introduction | String | 否 | 产品简介（富文本HTML） |
| lineId | Long | CORE/GROUP必填 | 产品线ID（私人定制可空） |
| tripDays | Integer | ✅ | 行程天数（≥1） |
| tripNights | Integer | 否 | 行程晚数（不传自动=天数-1） |
| tiers | List | 否 | 档位列表 `[{tierSeq:1, tierName:"舒适"}]` |
| seasons | List\<String\> | 否 | 适用季节(spring/summer/autumn/winter) |
| tags | List\<String\> | 否 | 产品标签(亲子/研学/露营等) |
| coverImageUrl | String | 上架必填 | 封面图URL |
| carouselImages | List\<String\> | 否 | 轮播图URL列表（≤10张） |
| infantAgeMax | Integer | 否 | 幼童年龄上限（默认1岁） |
| toddlerAgeMax | Integer | 否 | 小童年龄上限（默认3岁） |
| childAgeMin | Integer | 否 | 儿童年龄下限（默认4岁） |
| childAgeMax | Integer | 否 | 儿童年龄上限（默认12岁） |
| infantDefaultPrice | BigDecimal | 否 | 幼童默认价（元，0=免费） |
| isBooking | Boolean | 否 | 是否预约产品（默认false） |
| paymentType | String | 否 | 支付方式(FULL=全款/DEPOSIT=订金) |
| depositRatio | Integer | 否 | 订金比例%（DEPOSIT时用，与固定额二选一） |
| depositAmount | BigDecimal | 否 | 订金固定额（DEPOSIT时用） |
| balanceDueDays | Integer | 否 | 尾款期限（出发前N天） |
| defaultDailyStock | Integer | 否 | 每日库存默认值（NULL=不限量） |
| defaultRoomCount | Integer | 否 | 默认房间数（小蒙马用） |
| minGroupSize | Integer | 否 | 最低成团人数（小蒙马用） |
| showReview | Boolean | 否 | C端展示评价（默认true） |
| showChatGroup | Boolean | 否 | 展示群聊入口 |
| showTripTime | Boolean | 否 | 展示行程时间 |
| showTripDistance | Boolean | 否 | 展示行程距离 |
| creatorAvatarUrl | String | 否 | 创建者头像URL |
| creatorIntro | String | 否 | 创建者简介（≤512字） |
| customizerId | Long | 否 | 定制师ID（私人定制用） |
| customerName | String | 否 | 客户姓名（私人定制用） |
| contactPhone | String | 否 | 联系电话（私人定制用） |
| departureDate | LocalDate | 否 | 出发日期（私人定制用，格式yyyy-MM-dd） |
| safetyItems | List | 否 | 安全保障项 `[{icon,title,description}]` |
| photographyItems | List | 否 | 摄影跟拍项 `[{name,description}]` |
| diningHighlights | List | 否 | 餐饮亮点 `[{name,description}]` |

**请求示例**：

```json
{
  "version": 1,
  "name": "额吉的故乡·亲子版",
  "subtitle": "6天5晚草原环线",
  "tripDays": 6,
  "tripNights": 5,
  "lineId": 100,
  "tiers": [
    {"tierSeq": 1, "tierName": "舒适"},
    {"tierSeq": 2, "tierName": "豪华"}
  ],
  "seasons": ["summer", "autumn"],
  "tags": ["亲子", "研学"],
  "coverImageUrl": "https://...",
  "carouselImages": ["https://...", "https://..."],
  "infantAgeMax": 1,
  "toddlerAgeMax": 3,
  "childAgeMin": 4,
  "childAgeMax": 12,
  "infantDefaultPrice": 0,
  "paymentType": "FULL",
  "showReview": true
}
```

---

### 页面3：产品编辑 — Step2 行程编排

```
┌──────────────────────────────────────────────────┐
│  DAY1  DAY2  DAY3  DAY4  DAY5  DAY6  [+添加]    │
├──────┬───────────────────────────────────────────┤
│      │  标题: [海拉尔→额尔古纳]                   │
│ DAY1 │  金句: [让草原成为孩子的第一个课堂]         │
│      │  描述: [富文本]                             │
│      │  封面: [上传]    里程: [290]km              │
│      │                                             │
│      │  途经路线:                                   │
│      │  [海拉尔] → [莫日格勒河] → [额尔古纳]      │
│      │                                             │
│      │  活动安排:                                   │
│      │  ☐ 10:00 莫日格勒河观景台 (景区)  60分钟    │
│      │  ☐ 14:00 骑马体验 (活动)  120分钟           │
│      │  [+ 从资源库添加] [+ 自定义活动]            │
│      │                                             │
│      │  住宿安排: (多档时按档位Tab切换)             │
│      │  [舒适] 额尔古纳大酒店                      │
│      │  [豪华] 额尔古纳国际酒店                    │
│      │                                             │
│      │  餐饮: 早[酒店▼] 中[自理▼] 晚[特色餐▼]     │
│      │  照片: [上传1] [上传2]                      │
│      │                                             │
│      │                               [整体保存]    │
└──────┴───────────────────────────────────────────┘
```

**接口**：

| 场景 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 获取行程(回显) | GET | `/admin/product/item/{id}` | 从详情中取 `itinerary` 部分 |
| 整体保存行程 | PUT | `/admin/product/item/{id}/itinerary` | **整棵行程树一次提交**，后端用EntityDiffUtil对比增删改 |
| 增加一天 | POST | `/admin/product/item/{id}/itinerary/day` | 追加末尾天，返回dayId |
| 删除一天 | DELETE | `/admin/product/item/{id}/itinerary/day/{dayNumber}` | 级联删节点+住宿+路线点 |
| 费用推导预览 | GET | `/admin/product/item/{id}/fee-deduction-preview` | 根据行程自动推导费用包含，进入Step5前调用 |

**整体保存请求结构**：

```json
{
  "productId": 123,
  "version": 2,
  "days": [
    {
      "dayId": null,          // 新增不传，已有传ID
      "dayNumber": 1,
      "dayTitle": "海拉尔→额尔古纳",
      "quoteText": "让草原成为孩子的第一个课堂",
      "description": "...",
      "coverImageUrl": "https://...",
      "dailyMileage": 290,
      "breakfast": "HOTEL",   // HOTEL/CAMP/SPECIAL/SELF
      "lunch": "SELF",
      "dinner": "SPECIAL",
      "photoUrls": ["https://..."],
      "nodes": [
        {
          "nodeId": null,     // 新增不传
          "sortOrder": 1,
          "nodeType": "SCENIC",
          "nodeName": "莫日格勒河观景台",
          "resourceType": "SCENIC_SPOT",
          "resourceId": 456,
          "startTime": "10:00",
          "durationMinutes": 60,
          "description": "...",
          "images": ["https://..."]
        }
      ],
      "hotels": [
        {
          "id": null,
          "tierSeq": 1,       // 档位序号，单一档=1
          "hotelId": 789,
          "roomTypeId": null,  // 私人定制才选房型
          "isDefault": true,
          "sortOrder": 1
        }
      ],
      "routePoints": [
        {
          "resourceId": 456,
          "name": "莫日格勒河",
          "longitude": 119.123,
          "latitude": 49.456,
          "sortOrder": 1
        }
      ]
    }
  ]
}
```

> **增删改对比规则**：后端按ID判断——有ID且在提交中=更新，有ID但不在提交中=删除，无ID=新增。前端直接提交当前完整状态，不需要自己标记增删改。

---

### 页面4：产品编辑 — Step3 路线与备品

```
┌──────────────────────────────────────────────────┐
│  路线信息                                          │
│  路线名称: [草原环线体验]                          │
│  路线图:   [上传]                                  │
│  总里程:   [1200] km (自动计算，可手动改)           │
│  线路描述: [____________]                          │
│                                                    │
│  备品清单                                          │
│  名称         │ 涉及成本 │ 计费方式  │ 单价 │ 数量 │
│  防晒霜SPF50+ │ ☐        │ —        │ —    │ —    │
│  一次性雨衣   │ ☑        │ 按人头    │ 5    │ —    │
│  [+ 从资源库选] [+ 自定义]                         │
│                                                    │
│  全程成本项                                        │
│  实际用车: [车型下拉▼]                              │
│  服务人员: [选择▼]                                  │
│  额外费用: [名称] [单价] [数量] [按天☐]            │
│                                    [保存]          │
└──────────────────────────────────────────────────┘
```

**接口**：

| 场景 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 保存路线与备品 | PUT | `/admin/product/item/{id}/route` | `ProductRouteSaveReqVO`，一次提交全部 |
| 车型下拉 | — | 调资源服务 `/internal/vehicle-model/list` | 从资源模块获取车型列表 |
| 人员下拉 | — | 调资源服务 `/internal/staff/list` | 从资源模块获取人员列表 |
| 备品下拉 | — | 调资源服务 `/internal/supplies/list` | 从资源模块获取备品列表 |

**路线与备品请求示例**：

```json
{
  "productId": 123,
  "version": 3,
  "routeName": "草原环线体验",
  "routeMapUrl": "https://...",
  "totalMileage": 1200,
  "routeDescription": "海拉尔起止，途经莫日格勒河、额尔古纳...",
  "vehicleModelId": 10,
  "staffCostIds": [20, 21],
  "supplies": [
    {
      "id": null,
      "suppliesResourceId": 30,
      "suppliesName": "防晒霜SPF50+",
      "category": "防护用品",
      "hasCost": false,
      "sortOrder": 1
    },
    {
      "id": null,
      "suppliesResourceId": null,
      "suppliesName": "一次性雨衣",
      "category": "防护用品",
      "hasCost": true,
      "billingType": "PER_PERSON",
      "unitPrice": 5,
      "quantity": null,
      "sortOrder": 2
    }
  ],
  "extraCosts": [
    {
      "id": null,
      "name": "接机费",
      "unitPrice": 200,
      "quantity": 1,
      "daily": false,
      "sortOrder": 1
    }
  ]
}
```

---

### 页面5：产品编辑 — Step4 定价管理

#### 核心产品/私人定制 → 价格区间

```
┌──────────────────────────────────────────────────┐
│  价格区间列表                                      │
│  日期范围      │ 类型 │ 成人价 │ 儿童价 │ 库存    │
│  07.01 - 07.31 │ 旺季 │ 4980   │ 3980   │ 10     │
│  08.01 - 08.31 │ 旺季 │ 5280   │ 4280   │ 10     │
│  09.01 - 09.30 │ 平日 │ 3980   │ 2980   │ 不限   │
│  [+ 新增区间]                                      │
│                                                    │
│  (多档时每个区间按档位平铺多组价格)                 │
│                                    [保存]          │
└──────────────────────────────────────────────────┘
```

**接口**：

| 场景 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 查询价格日历 | GET | `/admin/product/item/{id}/price-calendar` | 参数 month(yyyy-MM)、tierSeq(档位) |
| 批量设置价格 | POST | `/admin/product/item/{id}/price-calendar/batch` | 按日期范围+档位UPSERT |
| 删除价格区间 | DELETE | `/admin/product/item/{id}/price-calendar` | 按日期范围+档位删除 |
| 报价测算 | POST | `/admin/product/item/{id}/quote` | 输入日期+人数，预览总价 |

**批量设置请求**：

```json
{
  "productId": 123,
  "version": 3,           // 草稿用version，已上架用priceVersion
  "startDate": "2026-07-01",
  "endDate": "2026-07-31",
  "tierSeq": 1,           // 档位序号，单一=1
  "priceType": "PEAK",    // NORMAL/PEAK/HOLIDAY/SPECIAL
  "adultSellPrice": 4980,
  "childSellPrice": 3980,
  "toddlerDiscount": -500, // 小童优惠额(负数)，小童价=儿童价-|优惠额|
  "infantPrice": 0,        // 幼童价，0=免费
  "dailyStock": 10         // NULL=不限量
}
```

#### 小蒙马 → 班期管理

```
┌──────────────────────────────────────────────────┐
│  班期列表                                          │
│  班期名   │ 出发日  │ 状态 │ 成人价 │ 已报/上限   │
│  第1期    │ 07.01  │ 报名中│ 4980  │ 8/20        │
│  第2期    │ 07.15  │ 报名中│ 4980  │ 3/20        │
│  [+ 新增] [批量创建]                               │
└──────────────────────────────────────────────────┘
```

**接口**：

| 场景 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 班期列表 | GET | `/admin/product/item/{id}/schedule/list` | 返回全部班期 |
| 创建班期 | POST | `/admin/product/item/{id}/schedule` | `ScheduleSaveReqVO`，不传batchId |
| 修改班期 | PUT | `/admin/product/item/{id}/schedule` | 传batchId |
| 删除班期 | DELETE | `/admin/product/item/{id}/schedule/{scheduleId}` | 有订单不可删 |
| 取消班期 | POST | `/admin/product/item/{id}/schedule/{scheduleId}/cancel` | 有订单触发退款 |
| 批量创建 | POST | `/admin/product/item/{id}/schedule/batch-create` | 按重复模式批量 |
| 团队查询 | GET | `/admin/product/item/{id}/schedule/team?batchId=xxx` | 班期团队成员 |
| 团队保存 | PUT | `/admin/product/item/{id}/schedule/team?batchId=xxx` | 全量替换 |

---

### 页面6：产品编辑 — Step5 补充信息

```
┌──────────────────────────────────────────────────┐
│  费用包含  (自动推导+手动编辑)                     │
│  ☑ 门票 (自动) │ ☑ 住宿 (自动) │ ☐ 旅拍 (手动)  │
│                                                    │
│  费用不含                                          │
│  ☑ 个人消费 │ ☑ 自费项目                           │
│                                                    │
│  人群优惠                                          │
│  儿童门票: [免票▼]  住宿: [不占床▼]  餐饮: [半额▼]│
│  老人门票: [半价▼]  住宿: [同成人▼]                │
│                                                    │
│  车辆展示: [选择车型▼] (纯展示)                    │
│  退改政策: [选择模板▼]                              │
│  预订条款: [选择模板▼]                              │
│  装备建议: [富文本]                                 │
│  保险告知: ○含 ○不含 ○可选升级                     │
│                                                    │
│  产品卖点                                          │
│  快速理解: [富文本+图片]                            │
│  孩子经历: [富文本+图片]                            │
│  成长收获: [探索力] [协作力] [+添加]                │
│                                    [保存]          │
└──────────────────────────────────────────────────┘
```

**接口**：

| 场景 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 保存补充信息 | PUT | `/admin/product/item/{id}/supplement` | `ProductSupplementSaveReqVO` |
| 费用推导预览 | GET | `/admin/product/item/{id}/fee-deduction-preview` | 根据行程自动计算费用包含 |
| 退改政策模板 | GET | `/admin/product/refund-policy/enabled` | 下拉选择，返回 `[{policyId, policyName}]` |
| 预订条款模板 | GET | `/admin/product/booking-terms/enabled` | 下拉选择，返回 `[{termsId, termsName}]` |
| 温馨提示模板 | GET | `/admin/product/warm-tips/enabled` | 下拉选择，返回 `[{tipsId, tipsName}]` |

**补充信息请求示例**：

```json
{
  "productId": 123,
  "version": 4,
  "includedFees": [
    {"id": null, "feeType": "TICKET", "name": "门票", "source": "AUTO", "description": "含所有景区门票", "sortOrder": 1},
    {"id": null, "feeType": "ACCOMMODATION", "name": "住宿", "source": "AUTO", "sortOrder": 2}
  ],
  "excludedFees": [
    {"id": null, "feeType": "PERSONAL", "name": "个人消费", "description": "如纪念品、零食等", "sortOrder": 1}
  ],
  "customFees": [],
  "feeIgnoredTypes": [],
  "childTicket": "FREE",
  "childAccommodation": "NO_BED",
  "childMeal": "HALF",
  "elderTicket": "HALF_PRICE",
  "elderAccommodation": "SAME_AS_ADULT",
  "crowdBenefitTips": "12岁以下儿童门票免费",
  "vehicleModelIds": [10, 11],
  "vehicleRuleText": "5人以下推荐SUV，6-9人推荐商务车",
  "bookingTermsId": 1,
  "warmTipsId": 1,
  "equipmentAdvice": "<p>必备：防晒霜、遮阳帽...</p>",
  "insuranceNotice": "INCLUDED",
  "insuranceSchemeId": 1,
  "contractSchemeId": 1,
  "quickUnderstand": {"text": "6天5晚，深入草原腹地...", "images": ["https://..."]},
  "childExperience": {"text": "骑马、射箭、挤牛奶...", "images": ["https://..."]},
  "growthGains": [
    {"dimension": "探索力", "description": "在辽阔草原培养探索精神"},
    {"dimension": "协作力", "description": "团队活动锻炼合作意识"}
  ]
}
```

**费用推导预览响应**（进入Step5前先调用）：

```json
[
  {"feeType": "TICKET", "name": "门票", "source": "AUTO", "sourceNodeName": "莫日格勒河观景台"},
  {"feeType": "ACCOMMODATION", "name": "住宿", "source": "AUTO", "sourceNodeName": "额尔古纳大酒店"}
]
```

---

### 页面7：产品线管理

```
┌──────────────────────────────────────────────────┐
│  [+ 新增产品线]                                    │
│  ┌──────┐ ┌──────┐ ┌──────┐                      │
│  │ 封面 │ │ 封面 │ │ 封面 │                      │
│  │ 名称 │ │ 名称 │ │ 名称 │                      │
│  │ 类型 │ │ 类型 │ │ 类型 │                      │
│  └──────┘ └──────┘ └──────┘                      │
└──────────────────────────────────────────────────┘
```

**接口**：

| 场景 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 产品线列表 | GET | `/admin/product/line/list` | 分页 |
| 创建产品线 | POST | `/admin/product/line` | 见下方请求 |
| 编辑产品线 | PUT | `/admin/product/line/{lineId}` | 同创建 |
| 删除产品线 | DELETE | `/admin/product/line/{lineId}` | 有产品时不可删 |
| 简单列表(下拉) | GET | `/admin/product/line/simple-list` | 返回 `[{lineId, name, productType}]` |

**产品线创建/编辑请求**：

```json
{
  "name": "额吉的故乡",
  "description": "呼伦贝尔夏季亲子游",
  "productType": "CORE",
  "coverImageUrl": "https://...",
  "seasons": ["summer", "autumn"],
  "tags": ["亲子游", "深度游"]
}
```

**产品线列表响应字段**：

| 字段 | 类型 | 说明 |
|------|------|------|
| lineId | Long | 产品线ID |
| name | String | 名称 |
| description | String | 描述 |
| productType | String | 绑定的产品类型 |
| coverImageUrl | String | 封面图 |
| seasons | List\<String\> | 适用季节 |
| tags | List\<String\> | 产品线标签 |
| productCount | Integer | 旗下产品数量 |
| startPrice | BigDecimal | 起步价（旗下已上架产品最低成人价） |
| createTime | LocalDateTime | 创建时间 |

---

## 三、C端（小程序）页面接口对照

### 页面1：主题精选页（产品线列表）

**接口**：`GET /mp/product/line/list`
- 参数：productType(可选)、season(可选)、page、pageSize
- 返回：产品线卡片列表（name, description, coverImageUrl, startPrice, productType, seasons）

### 页面2：产品列表（产品线下的产品）

**接口**：`GET /mp/product/list`
- 参数：lineId(产品线ID)、tag(产品标签Tab)、page、pageSize
- 返回：产品卡片列表

**产品卡片关键字段**：

```json
{
  "productId": 123,
  "name": "额吉的故乡·亲子版",
  "subtitle": "6天5晚草原环线",
  "coverImageUrl": "https://...",
  "tripDays": 6,
  "productType": "CORE",
  "tags": ["亲子", "研学"],
  "startPrice": 3980,         // 起步价(¥X起)
  "startPriceLabel": "¥3980起/人"
}
```

### 页面3：产品详情页

**接口**：`GET /mp/product/{productId}`
- 返回：`MpProductDetailRespVO`（聚合全部展示数据）

**返回结构**：

```json
{
  "productId": 123,
  "productType": "CORE",
  "name": "额吉的故乡·亲子版",
  "subtitle": "...",
  "coverImageUrl": "...",
  "carouselImages": ["...", "..."],
  "tripDays": 6,
  "tripNights": 5,
  "routeMapUrl": "...",
  "creatorAvatarUrl": "...",
  "creatorIntro": "...",
  
  "itinerary": [
    {
      "dayNumber": 1,
      "dayTitle": "海拉尔→额尔古纳",
      "description": "...",
      "coverImageUrl": "...",
      "breakfast": "HOTEL",
      "lunch": "SELF",
      "dinner": "SPECIAL",
      "nodes": [
        {
          "nodeType": "SCENIC",
          "nodeName": "莫日格勒河观景台",
          "startTime": "10:00",
          "durationMinutes": 60,
          "description": "..."
        }
      ]
    }
  ],
  
  "includedFees": [
    {"feeType": "TICKET", "name": "门票", "source": "AUTO"},
    {"feeType": "ACCOMMODATION", "name": "住宿", "source": "AUTO"}
  ],
  "excludedFees": [
    {"feeType": "PERSONAL", "name": "个人消费"}
  ],
  
  "crowdBenefit": {
    "childTicket": "FREE",
    "childAccommodation": "NO_BED",
    "childMeal": "HALF",
    "elderTicket": "HALF_PRICE",
    "tips": "..."
  }
}
```

### 页面4：价格日历（弹窗）

**接口**：`GET /mp/product/{productId}/price-calendar`
- 参数：month(yyyy-MM)
- 返回：

```json
{
  "days": [
    {
      "date": "2026-07-01",
      "priceType": "PEAK",
      "adultPrice": 4980,
      "childPrice": 3980,
      "isSelectable": true,     // 连续N天有价格且有库存
      "remainStock": 8          // null=不限量
    }
  ]
}
```

### 页面5：报价计算（选日期+人数后）

**接口**：`POST /mp/product/{productId}/quote`

**请求**：
```json
{
  "departureDate": "2026-07-01",
  "adultCount": 2,
  "childCount": 1,
  "youngChildCount": 0,
  "babyCount": 0,
  "batchId": null,          // 小蒙马传班期ID
  "tierSeq": 1              // 多档时传档位序号
}
```

**响应**：
```json
{
  "totalAdultPrice": 9960,
  "totalChildPrice": 3980,
  "grandTotal": 13940,
  "dailyDetails": [
    {"date": "2026-07-01", "adultPrice": 4980, "childPrice": 3980},
    {"date": "2026-07-02", "adultPrice": 4980, "childPrice": 3980}
  ],
  "paymentType": "FULL",
  "stock": 8,
  "available": true
}
```

### 页面6：小蒙马班期列表（弹窗）

**接口**：`GET /mp/product/{productId}/schedule/list`

**返回**：
```json
[
  {
    "batchId": 456,
    "batchName": "第1期",
    "departureDate": "2026-07-01",
    "endDate": "2026-07-06",
    "enrollmentDeadline": "2026-06-30",
    "batchStatus": "ENROLLING",
    "adultPrice": 4980,
    "childPrice": 3980,
    "maxParticipants": 20,
    "enrolledCount": 8,
    "maxRooms": 10,
    "bookedRooms": 4
  }
]
```

---

## 四、字典值速查

| 字典 | 值 | 中文 |
|------|-----|------|
| product_type | CORE | 核心产品 |
| product_type | GROUP | 小蒙马跟团游 |
| product_type | CUSTOM | 私人定制 |
| product_status | DRAFT | 草稿 |
| product_status | PENDING_REVIEW | 待审核 |
| product_status | PUBLISHED | 已上架 |
| product_status | UNPUBLISHED | 已下架 |
| product_status | REJECTED | 已驳回 |
| product_status | COMPLETED | 已完成(定制) |
| product_status | ORDERED | 已下单(定制) |
| payment_type | FULL | 全款支付 |
| payment_type | DEPOSIT | 订金+尾款 |
| price_type | NORMAL | 平日 |
| price_type | PEAK | 旺季 |
| price_type | HOLIDAY | 节假日 |
| price_type | SPECIAL | 特价 |
| meal_option | HOTEL | 含(酒店) |
| meal_option | CAMP | 含(营地) |
| meal_option | SPECIAL | 含(特色餐) |
| meal_option | SELF | 自理 |
| schedule_status | ENROLLING | 报名中 |
| schedule_status | NEARLY_FULL | 即将满员 |
| schedule_status | FULL | 已满 |
| schedule_status | FINISHED | 已结束 |
| schedule_status | CANCELLED | 已取消 |
| insurance_notice | INCLUDED | 含保险 |
| insurance_notice | EXCLUDED | 不含保险 |
| insurance_notice | OPTIONAL | 可选升级 |

---

## 五、与旧服务的切换说明

1. **管理端**：产品管理页面整体切换到v2接口，旧产品管理页面下线
2. **C端**：小程序的产品列表/详情/报价/下单全部切换到v2接口
3. **订单服务不改**：订单的Feign调用仍走旧接口（产品v2会兼容旧Feign路径）
4. **旧产品数据清空**：上线后旧产品数据全部删除，从零录入

---

## 六、联调注意事项

1. **所有保存接口都要传version**——忘传会返回400
2. **行程整体保存**——不是一个节点一个接口，是整棵行程树一次提交（`PUT /itinerary`）
3. **价格区间制vs班期制**——看productType：CORE/CUSTOM用价格日历接口，GROUP用班期接口
4. **多档位**——tierSeq从1开始递增，单一档位=1。价格日历和住宿都按tierSeq关联
5. **小童价格特殊**——前端传的是优惠额（负数），实际小童价=儿童价-|优惠额|
6. **C端只展示PUBLISHED产品**——其他状态的产品C端接口不会返回
7. **HTTP始终返回200**——业务错误通过 `Result.code` 区分，`code=0` 为成功，非0为失败，`msg` 为中文错误信息
8. **产品详情接口**——GET `/admin/product/item/{id}` 一次返回全部5步数据（基础+行程+路线+价格+补充），前端按当前Step取对应部分回显
