docs(2026-04-20): 3 个 BUG 修复 (mp/review/target 改可选+班期不限名额+产品列表加档位)
这个提交包含在:
父节点
95cb39a4f1
当前提交
bd28d3bd3e
@ -0,0 +1,207 @@
|
||||
# feat: 管理端产品列表 `/admin/product/item/list` 响应新增 `tierPrices` 档位价格摘要
|
||||
|
||||
- **日期**: 2026-04-20
|
||||
- **PR**: [#955](https://git.1814.love:8443/wx/HL/pulls/955) (Closes #954)
|
||||
- **类型**: FEATURE(响应新增字段,无破坏性变更)
|
||||
- **服务**: hl-product-service-v2
|
||||
- **前端是否需要改动**: **建议改动**(管理端产品列表新增"档位摘要"列展示)
|
||||
|
||||
---
|
||||
|
||||
## 一、为什么加这个字段
|
||||
|
||||
管理端产品管理列表此前每行只能看到:
|
||||
- `tierCount`(档位总数)
|
||||
- `startPrice`(产品起步价 = 所有档位最低)
|
||||
|
||||
但运营在列表里需要快速判断**每个档位是否已定价**、**哪个档位起步价多少**,否则要点进详情页一个个看,效率低。
|
||||
|
||||
本次新增 `tierPrices` 字段:**列表页保留无价档位**(startPrice=null),让前端可以一眼看出"档位待定价"的状态。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 方法 | 路径 | 影响 |
|
||||
|---|------|------|------|
|
||||
| 1 | GET | `/admin/product/item/list` | 响应 `records[]` 每项新增 `tierPrices` 字段(数组) |
|
||||
|
||||
**其他接口不变**:
|
||||
- `GET /admin/product/{id}`(产品详情):**行为零变化**。详情接口的 `tierPrices` **仍剔除无价档位**,与本次列表接口语义不同(见第五节)。
|
||||
- 既有的 `tierCount` / `startPrice` 字段保留,不受影响。
|
||||
|
||||
---
|
||||
|
||||
## 三、字段定义
|
||||
|
||||
### 新增 `tierPrices`(数组)
|
||||
|
||||
每个产品记录新增 `tierPrices: TierPriceItem[]`,结构:
|
||||
|
||||
| 字段 | 类型 | 说明 | 可能为 null |
|
||||
|------|------|------|-------------|
|
||||
| `tierSeq` | Integer | 档位序号(从 1 起) | 否 |
|
||||
| `tierName` | String | 档位名称(如"经济档"/"舒适档"/"豪华档") | 否 |
|
||||
| `tierDescription` | String | 档位描述(产品配置时填写) | 是(未填即 null) |
|
||||
| `startPrice` | BigDecimal | **该档位**未来日期的最低售价;**无价档位返回 null** | **是** ⚠️ |
|
||||
|
||||
### 既有字段(保留,不变)
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `tierCount` | 档位总数(包含无价档位) |
|
||||
| `startPrice` | 产品起步价(所有档位中最低;CORE/CUSTOM 取自 `product_price_calendar`,GROUP 同详情口径) |
|
||||
|
||||
---
|
||||
|
||||
## 四、响应示例对比
|
||||
|
||||
### 前
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"total": 25,
|
||||
"records": [
|
||||
{
|
||||
"id": 1001,
|
||||
"name": "小蒙马·呼伦贝尔5日",
|
||||
"productType": "GROUP",
|
||||
"tierCount": 3,
|
||||
"startPrice": 4580.00
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 后
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"total": 25,
|
||||
"records": [
|
||||
{
|
||||
"id": 1001,
|
||||
"name": "小蒙马·呼伦贝尔5日",
|
||||
"productType": "GROUP",
|
||||
"tierCount": 3,
|
||||
"startPrice": 4580.00,
|
||||
"tierPrices": [
|
||||
{
|
||||
"tierSeq": 1,
|
||||
"tierName": "经济档",
|
||||
"tierDescription": "标准住宿+常规交通",
|
||||
"startPrice": 4580.00
|
||||
},
|
||||
{
|
||||
"tierSeq": 2,
|
||||
"tierName": "舒适档",
|
||||
"tierDescription": "四星住宿+商务车",
|
||||
"startPrice": 5160.00
|
||||
},
|
||||
{
|
||||
"tierSeq": 3,
|
||||
"tierName": "豪华档",
|
||||
"tierDescription": "五星住宿+豪华车",
|
||||
"startPrice": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 第 3 个档位 "豪华档" 未配价格 → `startPrice: null`,前端可展示为"档位待定价"。
|
||||
|
||||
---
|
||||
|
||||
## 五、列表 vs 详情语义差异(重要)
|
||||
|
||||
| 接口 | 无价档位(startPrice=null)处理 |
|
||||
|---|---|
|
||||
| **列表 `/admin/product/item/list`**(本次) | **保留**,`startPrice=null`,便于运营在列表里识别 |
|
||||
| **详情 `/admin/product/{id}`**(不变) | **剔除**,`tierPrices` 中只保留有价档位 |
|
||||
|
||||
> 这个差异是设计意图:详情页面向"已成型可售产品",列表页面向"运营治理"。
|
||||
|
||||
---
|
||||
|
||||
## 六、GROUP 类型的特别说明
|
||||
|
||||
| 产品类型 | 列表 `tierPrices[].startPrice` 数据源 |
|
||||
|---|---|
|
||||
| `CORE` / `CUSTOM` | `product_price_calendar` 表(按档位 + 日期取未来最低) |
|
||||
| `GROUP`(小蒙马) | 仍走 `product_price_calendar`,**与详情接口口径一致** |
|
||||
|
||||
⚠️ **已知偏差**:GROUP 真实成交价存在 `group_tour_batch.adult_price`(班期表),与日历价可能存在差异。这是已知问题,本次不修复,保持与详情接口同口径以避免列表/详情数字不一致。
|
||||
|
||||
---
|
||||
|
||||
## 七、前端使用建议
|
||||
|
||||
### 推荐:列表新增"档位摘要"列
|
||||
|
||||
```vue
|
||||
<el-table-column label="档位摘要" min-width="240">
|
||||
<template #default="{ row }">
|
||||
<div v-for="t in row.tierPrices" :key="t.tierSeq" class="tier-line">
|
||||
<span class="tier-name">{{ t.tierName }}</span>
|
||||
<span v-if="t.startPrice != null" class="tier-price">
|
||||
起 ¥{{ t.startPrice }}
|
||||
</span>
|
||||
<el-tag v-else type="warning" size="small">待定价</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
### ⚠️ 容错要求
|
||||
|
||||
- `tierPrices[].startPrice` **可能为 null**(无价档位),必须做 null 判断后再格式化金额,避免 `undefined.toFixed()` 报错
|
||||
- `tierDescription` 可能为 null,渲染时做空值兜底
|
||||
- `tierPrices` 数组本身不会为 null(无档位时为空数组 `[]`)
|
||||
|
||||
---
|
||||
|
||||
## 八、不兼容变更
|
||||
|
||||
**无**。纯新增字段,前端不读不受影响;既有 `tierCount` / `startPrice` 字段保留,含义与之前完全一致。
|
||||
|
||||
---
|
||||
|
||||
## 九、回归验证
|
||||
|
||||
测试环境部署完成后,用管理端 token 调用:
|
||||
|
||||
```bash
|
||||
# 1. 列表接口检查 tierPrices 字段存在
|
||||
curl "https://api.test.1814.love/admin/product/item/list?pageNo=1&pageSize=10" \
|
||||
-H "Authorization: Bearer {admin-token}" \
|
||||
| jq '.data.records[0] | {id, name, tierCount, startPrice, tierPrices}'
|
||||
|
||||
# 预期:tierPrices 字段存在;数组长度 == tierCount;含无价档位时 startPrice=null
|
||||
|
||||
# 2. 详情接口对比(应仍剔除无价档位)
|
||||
curl "https://api.test.1814.love/admin/product/{id}" \
|
||||
-H "Authorization: Bearer {admin-token}" \
|
||||
| jq '.data.tierPrices'
|
||||
|
||||
# 预期:详情接口的 tierPrices 长度 ≤ 列表接口(剔除了无价档位)
|
||||
|
||||
# 3. GROUP 产品(小蒙马)口径核对
|
||||
curl "https://api.test.1814.love/admin/product/item/list?productType=GROUP&pageNo=1&pageSize=5" \
|
||||
-H "Authorization: Bearer {admin-token}" \
|
||||
| jq '.data.records[] | {id, productType, tierPrices}'
|
||||
|
||||
# 预期:GROUP 产品的 tierPrices[].startPrice 与详情接口一致(同走 product_price_calendar)
|
||||
```
|
||||
|
||||
**预期**:
|
||||
- 列表新字段 `tierPrices` 必出现
|
||||
- 无价档位 `startPrice: null` 而非被剔除
|
||||
- 与详情接口语义差异符合第五节说明
|
||||
@ -0,0 +1,137 @@
|
||||
# 小程序评价 - `/mp/review/target` 接口 targetId 改为可选(支持"全部评论"页)
|
||||
|
||||
- **日期**: 2026-04-20
|
||||
- **PR**: [#951](https://git.1814.love:8443/wx/HL/pulls/951) (Closes #949)
|
||||
- **类型**: FEATURE(兼容性增强,请求参数收紧 → 放宽)
|
||||
- **服务**: hl-user-service(review 模块合并在 user 库 / user 服务,路由 `/mp/review/**`)
|
||||
- **前端是否需要改动**: **无强制改动**(旧调用方式继续可用),新页面"全部评论"可直接复用本接口
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
小程序新增"全部评论"列表页:在某个 `targetType`(产品 / 攻略 / 酒店 / 景点 / ...)下展示**全部已通过审核的评价**,不限定具体的目标对象 ID。
|
||||
|
||||
原接口 `GET /mp/review/target` 此前要求 `targetId` 必填,前端无法用同一个接口实现"全部评论"页,只能等后端再开一个新接口或自己拼。
|
||||
|
||||
本次后端把 `targetId` 由必填改为可选,**同接口同时支持两种语义**,前端不用再要新接口。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口
|
||||
|
||||
| # | 方法 | 路径 | 变更类型 |
|
||||
|---|------|------|---------|
|
||||
| 1 | GET | `/mp/review/target` | 请求参数 `targetId` 由必填改为可选;响应结构不变 |
|
||||
|
||||
---
|
||||
|
||||
## 三、请求参数变化
|
||||
|
||||
| 参数 | 类型 | 之前 | 现在 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `targetType` | String | **必填** | **必填**(不变) | 评价目标类型,字典 `review_target_type`(如 `PRODUCT` / `STRATEGY` / `HOTEL` / `SCENIC` / ...) |
|
||||
| `targetId` | Long | **必填** | **可选** | 不传 → 返回该 targetType 下所有已通过评价;传 → 按该 ID 过滤(行为不变) |
|
||||
| `pageNo` / `pageSize` | Integer | 可选(分页默认值) | 同左 | 不变 |
|
||||
|
||||
---
|
||||
|
||||
## 四、行为对照
|
||||
|
||||
| 调用方式 | 返回内容 |
|
||||
|---|---|
|
||||
| `GET /mp/review/target?targetType=PRODUCT&targetId=1001` | 商品 1001 的全部已通过评价(**行为完全不变**) |
|
||||
| `GET /mp/review/target?targetType=PRODUCT` | 全部商品的所有已通过评价(**新支持**) |
|
||||
| `GET /mp/review/target`(缺 targetType) | 仍返回参数校验错误(targetType 仍必填) |
|
||||
|
||||
---
|
||||
|
||||
## 五、响应结构(不变)
|
||||
|
||||
`PageResult<MpReviewVO>`,字段沿用既有结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"total": 128,
|
||||
"records": [
|
||||
{
|
||||
"id": 9001,
|
||||
"targetType": "PRODUCT",
|
||||
"targetId": 1001,
|
||||
"userId": 88,
|
||||
"nickname": "***",
|
||||
"avatar": "https://...",
|
||||
"rating": 5,
|
||||
"content": "服务很好,下次还来",
|
||||
"images": ["https://..."],
|
||||
"createTime": "2026-04-15 12:30:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 字段名、类型、嵌套结构与原接口**完全一致**,前端拿到的 records 结构无变化。
|
||||
|
||||
---
|
||||
|
||||
## 六、前端使用建议
|
||||
|
||||
### 场景 A:商品/攻略详情页"评价"模块(旧场景)
|
||||
|
||||
**不用改**。原本就传 `targetType + targetId`,行为不变。
|
||||
|
||||
### 场景 B:小程序"全部评论"列表页(新场景)
|
||||
|
||||
```js
|
||||
// 全部商品评论(按 targetType 过滤)
|
||||
const res = await request({
|
||||
url: '/mp/review/target',
|
||||
method: 'GET',
|
||||
data: {
|
||||
targetType: 'PRODUCT', // 必填
|
||||
pageNo: 1,
|
||||
pageSize: 20
|
||||
// targetId 不传
|
||||
}
|
||||
});
|
||||
// res.data.records 即整个 targetType 下的所有评价
|
||||
```
|
||||
|
||||
### ⚠️ 注意
|
||||
|
||||
- `targetType` 仍**必填**,不传会被参数校验拦截
|
||||
- 不传 `targetId` 时,返回的 records 里每条都有自己的 `targetId`,前端可据此跳到对应详情
|
||||
- 评价审核状态过滤(已通过 / 待审核)逻辑后端处理,前端无感
|
||||
|
||||
---
|
||||
|
||||
## 七、不兼容变更
|
||||
|
||||
**无**。仅放宽请求参数约束(必填 → 可选),既有调用方式继续按原语义工作。
|
||||
|
||||
---
|
||||
|
||||
## 八、回归验证
|
||||
|
||||
测试环境部署完成后,用 mp token 调用:
|
||||
|
||||
```bash
|
||||
# 1. 旧用法(带 targetId)行为不变
|
||||
curl "https://api.test.1814.love/mp/review/target?targetType=PRODUCT&targetId=1001&pageNo=1&pageSize=10" \
|
||||
-H "Authorization: Bearer {mp-token}" \
|
||||
| jq '.data | {total, sample: .records[0]}'
|
||||
|
||||
# 2. 新用法(不带 targetId,全部评论)
|
||||
curl "https://api.test.1814.love/mp/review/target?targetType=PRODUCT&pageNo=1&pageSize=10" \
|
||||
-H "Authorization: Bearer {mp-token}" \
|
||||
| jq '.data | {total, sample: .records[0]}'
|
||||
|
||||
# 3. 缺 targetType(应报参数错误)
|
||||
curl "https://api.test.1814.love/mp/review/target?pageNo=1&pageSize=10" \
|
||||
-H "Authorization: Bearer {mp-token}"
|
||||
```
|
||||
|
||||
**预期**:场景 1 与改动前一致;场景 2 返回的 total 应 ≥ 场景 1;场景 3 返回参数校验错误。
|
||||
@ -0,0 +1,149 @@
|
||||
# fix: 小蒙马(GROUP)班期 maxParticipants 改回可选(0/不传 = 不限名额)
|
||||
|
||||
- **日期**: 2026-04-20
|
||||
- **PR**: [#953](https://git.1814.love:8443/wx/HL/pulls/953) (Closes #952)
|
||||
- **类型**: FIX(请求参数约束放宽)
|
||||
- **服务**: hl-product-service-v2
|
||||
- **前端是否需要改动**: **建议改动**(去掉前端必填校验,新增"不限名额"选项)
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
PR #927(2026-04-19)将班期保存接口的 `maxParticipants` 改为 **必填**,目的是把"约束模糊报错"前置到字段级。
|
||||
|
||||
但实际业务反馈:**小蒙马(GROUP)部分团允许"不限名额"**(按需开班,不卡上限),强制必填导致这类班期无法创建。
|
||||
|
||||
本次回退到**可选**,并明确"0 / 不传 = 不限名额"的语义;前端可据此提供"不限名额"开关或留空。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 方法 | 路径 | 影响 |
|
||||
|---|------|------|------|
|
||||
| 1 | POST | `/admin/product/v2/schedule/save` | 请求体字段 `maxParticipants` 由必填改回**可选** |
|
||||
| 2 | POST | `/admin/product/v2/schedule/batch-create` | 同上 |
|
||||
|
||||
---
|
||||
|
||||
## 三、字段约束变化
|
||||
|
||||
| 字段 | 之前 (PR #927) | 现在 (本次) | 允许值 |
|
||||
|------|---------------|-------------|--------|
|
||||
| `maxParticipants` | **必填**(null 报"最大参与人数不能为空") | **可选** | 不传 / 0 / null = **不限名额**;>0 = 上限人数 |
|
||||
|
||||
DB 侧:不传或传 0 均落库为 `0`,业务校验报名人数时按"0 视为不限"处理。
|
||||
|
||||
---
|
||||
|
||||
## 四、请求示例
|
||||
|
||||
### 不限名额班期(新支持)
|
||||
|
||||
```json
|
||||
POST /admin/product/v2/schedule/save
|
||||
{
|
||||
"productId": 123,
|
||||
"batchName": "五一不限人数团",
|
||||
"departureDate": "2026-05-01",
|
||||
"adultPrice": 5160,
|
||||
"childPrice": 4580
|
||||
// 不传 maxParticipants → 0 → 不限
|
||||
}
|
||||
```
|
||||
|
||||
### 显式限名额班期(行为不变)
|
||||
|
||||
```json
|
||||
POST /admin/product/v2/schedule/save
|
||||
{
|
||||
"productId": 123,
|
||||
"batchName": "五一限 30 人精品团",
|
||||
"departureDate": "2026-05-01",
|
||||
"adultPrice": 5160,
|
||||
"childPrice": 4580,
|
||||
"maxParticipants": 30
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、报名上限校验逻辑
|
||||
|
||||
| `maxParticipants` 落库值 | 报名时校验 |
|
||||
|---|---|
|
||||
| `0`(不传 / 显式传 0) | **不校验**,可无限报名 |
|
||||
| `> 0` | 报名累计人数不得超过该值;超出报"班期已满"错误 |
|
||||
|
||||
> 现有"按 maxParticipants 校验"代码路径完整保留,仅在 `maxParticipants > 0` 时生效。
|
||||
|
||||
---
|
||||
|
||||
## 六、前端改动建议
|
||||
|
||||
### 班期创建/编辑表单
|
||||
|
||||
1. **去掉"最大人数必填"前端校验**
|
||||
2. 在最大人数输入框旁加 **"不限名额"开关 / 复选框**:
|
||||
- 勾选 → 提交时**不传 `maxParticipants`**(或显式传 `0`)
|
||||
- 不勾 → 输入框可填具体数字(建议前端做 `>0` 校验,避免误填负数)
|
||||
3. **回显逻辑**:编辑场景拉到 `maxParticipants=0` 时,开关默认勾选,输入框置灰
|
||||
|
||||
### 批量创建表单
|
||||
|
||||
同上,"不限名额"开关对批量生成的所有班期统一生效。
|
||||
|
||||
### 列表展示
|
||||
|
||||
| 后端字段值 | 建议展示 |
|
||||
|---|---|
|
||||
| `0` | "不限" / "不限名额" |
|
||||
| `> 0` | "上限 N 人" / 进度条(已报 / N) |
|
||||
|
||||
---
|
||||
|
||||
## 七、不兼容变更
|
||||
|
||||
| 调用方 | 影响 |
|
||||
|---|---|
|
||||
| 旧前端版本(**必传 `maxParticipants`**) | **不受影响**,传具体数字时行为完全一致 |
|
||||
| 旧前端版本(**显式传 0** 想表达"不限") | 之前会因必填校验直接 400;本次起正常落库为"不限" |
|
||||
| 新前端版本(**不传 `maxParticipants`**) | 落库为 0 = 不限,符合预期 |
|
||||
|
||||
---
|
||||
|
||||
## 八、回归验证
|
||||
|
||||
测试环境部署完成后,用管理端 token 调用:
|
||||
|
||||
```bash
|
||||
# 1. 不限名额班期(不传字段)
|
||||
curl -X POST "https://api.test.1814.love/admin/product/v2/schedule/save" \
|
||||
-H "Authorization: Bearer {admin-token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"productId": 123,
|
||||
"batchName": "回归-不限名额",
|
||||
"departureDate": "2026-06-01",
|
||||
"adultPrice": 5160,
|
||||
"childPrice": 4580
|
||||
}'
|
||||
|
||||
# 预期:code=200,落库 maxParticipants=0
|
||||
|
||||
# 2. 显式限名额班期(传具体数字)
|
||||
curl -X POST "https://api.test.1814.love/admin/product/v2/schedule/save" \
|
||||
-H "Authorization: Bearer {admin-token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"productId": 123,
|
||||
"batchName": "回归-限 30 人",
|
||||
"departureDate": "2026-06-02",
|
||||
"adultPrice": 5160,
|
||||
"childPrice": 4580,
|
||||
"maxParticipants": 30
|
||||
}'
|
||||
|
||||
# 预期:code=200,落库 maxParticipants=30
|
||||
```
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户