hl-api-changelog/changelogs/2026-04/2026-04-17_frontend-bug_step2-hotel-tiers.md

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
  • 产品 ID2045018534152478721
  • 页面:产品编辑 → Step2 行程编排 → 住宿安排

二、根因分析(后端已核实)

1. 后端返回的数据是完整正确的

调用 GET /admin/product/item/{id} 返回 data.tiers 完整包含 5 档,每档都有 tierSeq / tierName / tierDescription 字段,数据无问题。

2. "经奢" 不是后端字典

在整个后端项目(包括 schema.sql、字典表、常量、VO、代码注释grep "经奢" 零匹配。该字符串必然是前端代码内写死的

3. itinerary 内 hotels 只有部分档位是业务正常

后端 data.itinerary[*].hotelstierSeq 只出现 12,是用户在 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


五、排查步骤建议

  1. 全前端代码 grep 经奢 → 应能定位到写死的字符串来源,删除或修正为从 tiers 取值
  2. 定位 Step2 住宿 tab 的数据源:检查是否从 itinerary[*].hotels 聚合 tierSeq 推导档位 —— 如是,改为从 data.tiers
  3. 检查档位名枚举数组:搜 ['经济' / ['经济',' 等关键词,排查是否有写死的档位名数组写反
  4. 检查字典调用Step2 档位名是否误用了其他字典(如把酒店星级字典当档位用)

六、验收标准(前端修完后自测)

  • 产品 2045018534152478721 Step2「住宿安排」tab 严格显示 5 个档位,名称依次等于 Step1经济 / 舒适 / 轻奢 / 高端 / 豪华
  • 档位 3轻奢、4高端、5豪华下未录酒店时,tab 仍然显示,内容区呈现空状态而非 tab 消失
  • 档位 1经济、2舒适下能正确看到已录入的酒店
  • 全前端代码 grep 不到「经奢」这个字符串
  • 其他已发布产品随机抽查 2 个,Step2 住宿 tab 档位数与 Step1 配置完全一致

七、影响范围

  • 后端:无变更,不需要重启服务
  • 前端:仅 Step2「住宿安排」tab 的档位渲染逻辑修复
  • 数据:无需订正,线上数据是正确的,仅前端展示错误