5.8 KiB
5.8 KiB
前端 Bug 通知 — Step2 行程编排「住宿安排」档位显示错乱
- 日期: 2026-04-17
- 类型: 前端 Bug 修复指南(不涉及后端代码改动,后端无需重启)
- 涉及端: 管理后台 hl-ui(产品编辑页 Step2)
- 后端服务: hl-product-service-v2(仅作接口说明,代码不变)
一、问题描述
在产品编辑页 Step2「行程编排」→「住宿安排」tab 中:
- 预期:显示 5 个档位 tab,名称依次为
经济 / 舒适 / 轻奢 / 高端 / 豪华(与 Step1 基础信息配置的 5 档一致) - 实际:只显示 2 个档位 tab,且名称为
经奢 / 高端—— 其中 "经奢" 这个词在后端任何地方都不存在
复现路径:
- 环境:测试服
192.168.100.236 - 产品 ID:
2045018534152478721 - 页面:产品编辑 → Step2 行程编排 → 住宿安排
二、根因分析(后端已核实)
1. 后端返回的数据是完整正确的
调用 GET /admin/product/item/{id} 返回 data.tiers 完整包含 5 档,每档都有 tierSeq / tierName / tierDescription 字段,数据无问题。
2. "经奢" 不是后端字典
在整个后端项目(包括 schema.sql、字典表、常量、VO、代码注释)grep "经奢" 零匹配。该字符串必然是前端代码内写死的。
3. itinerary 内 hotels 只有部分档位是业务正常
后端 data.itinerary[*].hotels 里 tierSeq 只出现 1 和 2,是用户在 Step2 只给档位 1、2 录入了酒店的真实业务状态(业务允许不录满全部档位,不是 bug)。
前端不应该从 itinerary[*].hotels 反推档位 tab 列表 —— 这会导致:未录入酒店的档位 tab 直接消失;而一旦索引/字段错位,还会出现 "经奢" 这种幻觉档位名。
三、后端接口数据结构(示例)
接口:GET /admin/product/item/{id}
{
"code": 0,
"data": {
"id": 2045018534152478721,
"name": "示例产品",
"tiers": [
{"tierSeq": 1, "tierName": "经济", "tierDescription": "快捷酒店/双床标间"},
{"tierSeq": 2, "tierName": "舒适", "tierDescription": "四星酒店/河景房"},
{"tierSeq": 3, "tierName": "轻奢", "tierDescription": "五星酒店/高级套房"},
{"tierSeq": 4, "tierName": "高端", "tierDescription": "精品度假酒店/江景套房"},
{"tierSeq": 5, "tierName": "豪华", "tierDescription": "奢华度假庄园/别墅"}
],
"itinerary": [
{
"dayNumber": 1,
"hotels": [
{"tierSeq": 1, "hotelId": 111, "hotelName": "某快捷酒店"},
{"tierSeq": 2, "hotelId": 222, "hotelName": "某四星酒店"}
]
},
{
"dayNumber": 2,
"hotels": []
}
]
}
}
字段说明:
| 字段 | 含义 | 说明 |
|---|---|---|
data.tiers |
档位定义列表(权威来源) | Step1 基础信息配置,档位 tab 列表必须用它 |
data.tiers[*].tierSeq |
档位序号 | 1/2/3/4/5 |
data.tiers[*].tierName |
档位中文名 | 经济/舒适/轻奢/高端/豪华 |
data.tiers[*].tierDescription |
档位描述 | 档位下方的说明文字 |
data.itinerary[*].hotels |
某一天的酒店安排 | 按档位可能部分录入,允许为空数组 |
data.itinerary[*].hotels[*].tierSeq |
该酒店归属的档位 | 用于过滤到对应档位 tab 下展示 |
四、前端正确渲染逻辑
1. 档位 tab 列表
严格使用 data.tiers:
// 正确
const tierTabs = data.tiers; // 5 档齐全,顺序按 tierSeq
// 错误 —— 会导致未录酒店的档位消失
const tierTabs = uniqBy(flatMap(data.itinerary, d => d.hotels), 'tierSeq');
2. 档位名称
一律用 tier.tierName 字段,禁止前端写死档位名数组:
// 正确
<Tab label={tier.tierName} />
// 错误 —— 极易出现 "经奢" 这种幻觉
const TIER_NAMES = ['经济', '经奢', '轻奢', '高端', '豪华']; // 第 2 项就是错的
<Tab label={TIER_NAMES[tier.tierSeq - 1]} />
3. 每档下的酒店
按当前选中档位的 tierSeq 过滤:
const hotelsOfCurrentTier = day.hotels.filter(h => h.tierSeq === currentTierSeq);
4. 空档位保留 tab,内容区显示空状态
即使某档位下 hotels 全部为空,tab 仍然要显示,内容区渲染空状态提示(如"该档位暂未配置酒店,点击添加"),禁止隐藏 tab。
五、排查步骤建议
- 全前端代码 grep
经奢→ 应能定位到写死的字符串来源,删除或修正为从tiers取值 - 定位 Step2 住宿 tab 的数据源:检查是否从
itinerary[*].hotels聚合tierSeq推导档位 —— 如是,改为从data.tiers取 - 检查档位名枚举数组:搜
['经济'/['经济','等关键词,排查是否有写死的档位名数组写反 - 检查字典调用:Step2 档位名是否误用了其他字典(如把酒店星级字典当档位用)
六、验收标准(前端修完后自测)
- 产品
2045018534152478721Step2「住宿安排」tab 严格显示 5 个档位,名称依次等于 Step1:经济 / 舒适 / 轻奢 / 高端 / 豪华 - 档位 3(轻奢)、4(高端)、5(豪华)下未录酒店时,tab 仍然显示,内容区呈现空状态而非 tab 消失
- 档位 1(经济)、2(舒适)下能正确看到已录入的酒店
- 全前端代码 grep 不到「经奢」这个字符串
- 其他已发布产品随机抽查 2 个,Step2 住宿 tab 档位数与 Step1 配置完全一致
七、影响范围
- 后端:无变更,不需要重启服务
- 前端:仅 Step2「住宿安排」tab 的档位渲染逻辑修复
- 数据:无需订正,线上数据是正确的,仅前端展示错误