- fix(product): 补充信息「费用包含」自动生成漏景区/活动/酒店 (PR #1568) - feat(product): 费用包含/费用不含项支持图标(SVG)字段 (PR #1573)
4.5 KiB
修复: 产品「补充信息」费用包含「根据行程自动生成」漏景区/活动/酒店
类型: 后端 BUG 修复 关联: 工单 #1567 / PR #1568 日期: 2026-04-30 前端处理者: yst / mmg 影响范围: 管理后台「产品编辑 → 补充信息 → 费用说明 →『根据行程自动生成』」按钮
现象
点击「根据行程自动生成」后,前端展示的「费用包含」列表项严重不全:
- 同类型节点只展示第一个:产品有 6 个景区只回 1 条「门票」
- 酒店完全不出现
测试样本:产品 id=2045018534152478721「游牧的森林·南线经典」(用户原图)
- 旧逻辑只返回 2 条:活动「← ATV 全地形越野穿越」 + 门票「← 白桦林景区」
- 修复后返回 35 条:6 门票 + 2 活动 + 27 住宿(全部景区 / 全部活动 / 全部酒店)
根因
hl-product-service-v2 ProductValidationService.feeDeductionPreview() 旧实现:
Set<String> deducted按feeLabel(门票/活动/住宿/餐饮/交通)去重 → 同 feeType 只保留第一条- 完全没查
product_day_hotel表(酒店数据已早期迁离product_itinerary_node,但 fee-deduction-preview 没同步更新)
修复
feeDeductionPreview() 重写为:
- 不再按 feeLabel 去重,每个
ProductItineraryNodeDO都生成 1 条FeeDeductionPreviewVO(前提getFeeLabel() ≠ null) - 追加查
dayHotelService.selectByProductId(productId),全部酒店并入结果,feeType="住宿",sourceNodeName=酒店名 - 节点按
dayId + sortOrder排序,酒店按dayNumber + sortOrder排序 - 整体按
门票 → 活动 → 住宿 → 餐饮 → 交通分组排序 - NPE 兜底:
allNodes / allHotelsnull → 空集合;hotelName / dayId / sortOrdernull → 排序用MAX_VALUE兜底
影响接口
| 方法 | 路径 | 改动 |
|---|---|---|
| GET | /admin/product/item/{id}/fee-deduction-preview |
返回项数从去重后的 N 条 → 全节点 + 全酒店,字段不变 |
请求签名完全不变,响应字段不变,只是 data 数组变长且含酒店。
响应示例
GET /admin/product/item/2045018534152478721/fee-deduction-preview
{
"code": 200,
"data": [
{ "feeType": "门票", "name": "门票", "source": "AUTO", "sourceNodeName": "黑山头", "icon": null },
{ "feeType": "门票", "name": "门票", "source": "AUTO", "sourceNodeName": "白桦林景区", "icon": null },
{ "feeType": "门票", "name": "门票", "source": "AUTO", "sourceNodeName": "莫日格勒河", "icon": null },
/* ... 共 6 条门票 */
{ "feeType": "活动", "name": "活动", "source": "AUTO", "sourceNodeName": "ATV 全地形越野车穿越", "icon": null },
{ "feeType": "活动", "name": "活动", "source": "AUTO", "sourceNodeName": "驯鹿拉车体验", "icon": null },
{ "feeType": "住宿", "name": "住宿", "source": "AUTO", "sourceNodeName": "海拉尔洲季酒店", "icon": null },
/* ... 共 27 条住宿 */
]
}
前端要做的改动
已是 v-for 列表渲染 — 0 改动
hl-ui/src/views/product/edit/components/supplement/CostDescPanel.vue 第 109-119 行已经是
v-for="(item, idx) in includedCosts" 网格布局,每张 card 显示 feeType + name + ← sourceNodeName,新方案完美适配:
- 6 个景区会渲染 6 张「门票」card,每张带不同景区名(如「门票 ← 黑山头」「门票 ← 白桦林景区」…)
- 27 个酒店会渲染 27 张「住宿」card,每张带不同酒店名
UI 上多张同 feeType card 紧邻成一组排开,符合用户期望「看到所有节点」。
如果想做「按 feeType 聚合显示」(可选)
如果前端反馈「列表太长,希望同 feeType 合并为一张 card,内部分行列出来源」,可以前端 Object.groupBy(includedCosts, c => c.feeType) 自行聚合,无需后端改动。
测试覆盖
mvn test -pl hl-product-service-v2:1026/1026 全绿。
新增/增强 6 条 @ExtendWith(MockitoExtension.class) 单测覆盖:多景区 / 多活动 / 含酒店 / 空产品 / 未知类型(FREE/NOTE/SERVICE/CUSTOM)/ 忽略类型。
测试服 round-trip 验证(产品 2045018534152478721):返回 35 条,包含 6 景区 + 2 活动 + 27 酒店,排序正确,用户原图问题修复。
不动
- 数据库表结构不变(只是查询逻辑扩展)
- VO 字段不变(只是
data数组长度) feeIgnoredTypes过滤逻辑保留,被忽略的 feeType 仍会从结果中剔除
部署状态
- ✅ Deploy Panel 已部署测试服 hl-product-service-v2(2026-04-30 16:35)
- 正式环境暂未发布(等 dev → main release PR)