# 产品模块v2 — 前端联调指南 > **日期**:2026-04-13 > **服务**:hl-product-service-v2(本地端口 8086 / 测试环境端口 8083) > **在线文档(Knife4j)**: > - **测试环境(推荐)**:http://1.182.108.58:8080/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`中每条): | 字段 | 类型 | 说明 | |------|------|------| | productId | Long | 产品ID | | productNo | String | 产品编号(如C260409001) | | name | String | 产品名称 | | productType | String | 产品类型 | | status | String | 产品状态 | | coverImageUrl | String | 封面图 | | tripDays | Integer | 行程天数 | | lineId | Long | 产品线ID | | lineName | String | 产品线名称 | | tags | List\ | 产品标签 | | startPrice | BigDecimal | 起步价 | | createBy | Long | 创建人ID | | createTime | LocalDateTime | 创建时间 | | publishedAt | LocalDateTime | 上架时间 | **创建产品请求**: ```json { "name": "额吉的故乡·亲子版", "productType": "CORE", "lineId": 100, "tripDays": 6 } ``` **创建产品响应**:`Result` — 返回新产品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\ | 否 | 适用季节(spring/summer/autumn/winter) | | tags | List\ | 否 | 产品标签(亲子/研学/露营等) | | coverImageUrl | String | 上架必填 | 封面图URL | | carouselImages | List\ | 否 | 轮播图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": "

必备:防晒霜、遮阳帽...

", "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\ | 适用季节 | | tags | List\ | 产品线标签 | | 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取对应部分回显