前端AI无法访问Knife4j,所有内容必须自包含在文档中。 补全:产品列表请求参数表+响应字段表、创建产品请求/响应示例、 Step1完整字段表(37字段)、Step3路线请求示例、Step5补充信息请求示例、 费用推导预览响应、产品线请求/响应字段、状态变更请求示例。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
34 KiB
产品模块v2 — 前端联调指南
日期:2026-04-13 服务:hl-product-service-v2(本地端口 8086 / 测试环境端口 8083) 在线文档(Knife4j):
- 测试环境(推荐):http://1.182.108.58:8080/doc.html → 左侧下拉选择以下分组:
1. 管理端 - 产品管理— 产品CRUD、5步保存、价格日历、班期、报价2. 管理端 - 产品线管理— 产品线CRUD3. 管理端 - 模板管理— 预订条款、温馨提示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 | 上架时间 |
创建产品请求:
{
"name": "额吉的故乡·亲子版",
"productType": "CORE",
"lineId": 100,
"tripDays": 6
}
创建产品响应:Result<Long> — 返回新产品ID
状态变更请求:
{
"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}] |
请求示例:
{
"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前调用 |
整体保存请求结构:
{
"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 |
从资源模块获取备品列表 |
路线与备品请求示例:
{
"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 |
输入日期+人数,预览总价 |
批量设置请求:
{
"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}] |
补充信息请求示例:
{
"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前先调用):
[
{"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}] |
产品线创建/编辑请求:
{
"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
- 返回:产品卡片列表
产品卡片关键字段:
{
"productId": 123,
"name": "额吉的故乡·亲子版",
"subtitle": "6天5晚草原环线",
"coverImageUrl": "https://...",
"tripDays": 6,
"productType": "CORE",
"tags": ["亲子", "研学"],
"startPrice": 3980, // 起步价(¥X起)
"startPriceLabel": "¥3980起/人"
}
页面3:产品详情页
接口:GET /mp/product/{productId}
- 返回:
MpProductDetailRespVO(聚合全部展示数据)
返回结构:
{
"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)
- 返回:
{
"days": [
{
"date": "2026-07-01",
"priceType": "PEAK",
"adultPrice": 4980,
"childPrice": 3980,
"isSelectable": true, // 连续N天有价格且有库存
"remainStock": 8 // null=不限量
}
]
}
页面5:报价计算(选日期+人数后)
接口:POST /mp/product/{productId}/quote
请求:
{
"departureDate": "2026-07-01",
"adultCount": 2,
"childCount": 1,
"youngChildCount": 0,
"babyCount": 0,
"batchId": null, // 小蒙马传班期ID
"tierSeq": 1 // 多档时传档位序号
}
响应:
{
"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
返回:
[
{
"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 | 可选升级 |
五、与旧服务的切换说明
- 管理端:产品管理页面整体切换到v2接口,旧产品管理页面下线
- C端:小程序的产品列表/详情/报价/下单全部切换到v2接口
- 订单服务不改:订单的Feign调用仍走旧接口(产品v2会兼容旧Feign路径)
- 旧产品数据清空:上线后旧产品数据全部删除,从零录入
六、联调注意事项
- 所有保存接口都要传version——忘传会返回400
- 行程整体保存——不是一个节点一个接口,是整棵行程树一次提交(
PUT /itinerary) - 价格区间制vs班期制——看productType:CORE/CUSTOM用价格日历接口,GROUP用班期接口
- 多档位——tierSeq从1开始递增,单一档位=1。价格日历和住宿都按tierSeq关联
- 小童价格特殊——前端传的是优惠额(负数),实际小童价=儿童价-|优惠额|
- C端只展示PUBLISHED产品——其他状态的产品C端接口不会返回
- HTTP始终返回200——业务错误通过
Result.code区分,code=0为成功,非0为失败,msg为中文错误信息 - 产品详情接口——GET
/admin/product/item/{id}一次返回全部5步数据(基础+行程+路线+价格+补充),前端按当前Step取对应部分回显