hl-api-changelog/changelogs/2026-05/12_frontend_bug_admin_price-interval-toddler-discount-locked-when-child-zero.md

7.0 KiB

date, type, module, priority, notify, status
date type module priority notify status
2026-05-12 frontend-bug admin-price-interval-editor high
@mmg
pending

价格区间编辑:儿童价=0 时小童优惠看起来「不可编辑」且回显为 0

现象(用户反馈)

产品编辑 → 第 4 步「定价管理」→ 点某价格区间「编辑」:

  1. 区间原本数据:儿童 ¥X、小童优惠 ¥10
  2. 用户把儿童价改成 0,保存
  3. 重新打开发现:小童优惠展示成 ¥0、看起来改不动(label 浅色像 disabled)

期望:儿童=0 是合法业务场景(儿童与成人同价/不收儿童费),小童优惠不应受儿童=0 影响,既要可编辑,也要保留原值 10。

文件:hl-ui/src/views/product/edit/components/pricing/PriceIntervalEditor.vue

根因分析(已查代码)

根因 1 - CSS 误导:label 颜色让用户误判 disabled

/* 第 562-565 行 */
.pie-price-label {
  font-size: 11px;
  color: var(--text-color-placeholder);   /* ❌ 浅灰 placeholder 色 */
}

成人/儿童/单房差用的是 NFormItem :label="..." (深色文字),小童优惠额/幼童用的是 <span class="pie-price-label">...</span> (浅灰),视觉上看起来一个深一个浅。再叠加 NInputNumber 当 value=0 时数字也是浅色,用户100% 会误判为 disabled

实际上第 358-361 行 NInputNumber 没有 disabled 属性:

<div class="pie-price-item">
  <span class="pie-price-label">小童优惠额</span>
  <NInputNumber v-model:value="formData.toddlerDiscount"
    placeholder="选填,负数" :show-button="false" style="width: 100%"
    @focus="handleNumberFocus">
    <template #prefix>¥</template>
  </NInputNumber>
</div>

→ 点输入框其实可以输入,只是看起来不能。

根因 2 - toddlerDiscount 字段语义跟后端公式相反

placeholder="选填,负数"     <!-- 前端引导用户输负数 -->

但后端 ProductPricingService.java 第 706 行计算公式:

BigDecimal toddlerPrice = childPrice.subtract(toddlerDiscount).max(BigDecimal.ZERO);
// 即 toddlerPrice = childPrice - toddlerDiscount

要让小童比儿童便宜(toddlerPrice < childPrice),需要 toddlerDiscount > 0 (正数)。

如果用户按 placeholder 提示输 -10:

  • toddlerPrice = childPrice - (-10) = childPrice + 10 → 小童反而贵 10 元
  • 完全相反语义,业务上错的

根因 3 - 后端会把 toddlerDiscount=0 归一化为 null

// ProductPricingService.java 第 367-372 行
// 小童优惠 0 语义歧义("0 优惠" vs "未设置"),统一归一化为 null
if (template.getToddlerDiscount() != null
        && BigDecimal.ZERO.compareTo(template.getToddlerDiscount()) == 0) {
    template.setToddlerDiscount(null);
}

本身合理。但如果前端:

  • 用户没改 toddlerDiscount(原值 10) → handleConfirm 第 249 行 body.toddlerDiscount = f.toddlerDiscount 应该传 10 → DB 存 10 ✓
  • 用户改成 0 → 后端归一化为 null → DB 存 null ✓ (符合"未设置")
  • 但用户没动这个字段重开发现是 0 → 说明在某个保存路径里 toddlerDiscount 被意外清成了 null/0

根因 4 - 怀疑 CalendarDayModal 单日编辑会覆盖整区间

hl-ui/src/views/product/edit/components/CalendarDayModal.vue 单日改时,如果用户只改儿童=0、没动小童优惠 → formData.toddlerDiscount = info?.toddlerDiscount ?? null 第 256 行,如果传入的 info 没带 toddlerDiscount,默认 null,保存时把这天的小童优惠清掉了。

如果用户最初是从这个弹框改的,那就解释了"回显 0":单日清空导致那天 toddlerDiscount=null,聚合后展示 null → formatPrice(null) 显示 "-" 或某种 fallback。

需要 mmg 核实 CalendarDayModal 的 info 传入是否完整带 toddlerDiscount。

修复建议(优先级排序)

1 最紧急 - 修 CSS 误导

.pie-price-label {
  font-size: 14px;                              /* 从 11 改回正常字号 */
  color: var(--text-color);                     /* 从 placeholder 改深色 */
  font-weight: normal;
}

或者统一改用 NFormItem,所有 4 个价格字段(成人/儿童/小童优惠/幼童)风格一致。只此一项就解决「看起来 disabled」的误判

2 修字段语义 - toddlerDiscount placeholder 与文档统一

确认业务真实意图后,统一改:

  • 方案 A(推荐): toddlerDiscount 改成正数语义(折扣额)
    • placeholder 改 "选填,正数=折扣额"
    • NInputNumber 加 :min="0"
    • 后端公式 childPrice - toddlerDiscount 保持不变 ✓
  • 方案 B: 改语义为"小童价 = 儿童价 + toddlerDiscount"(可正可负),placeholder 改 "可负数=便宜,正数=加价",后端公式改成 childPrice + toddlerDiscount

两个方案选一,当前"placeholder 写负数 + 后端减号公式"是矛盾的

3 修单日编辑覆盖问题

确认 CalendarDayModal.vue 在保存单日时,如果用户没动 toddlerDiscount,应当保留原值,不要传 null 给后端。

具体看 handleSubmit 函数(第 319-320 行):

childSellPrice: formData.childSellPrice,
toddlerDiscount: formData.toddlerDiscount,    // ← 如果是 null 也会覆盖

如果调用方 fillFormData(info) 传入的 info.toddlerDiscount 是 null/undefined,formData.toddlerDiscount = null,保存时把这天的优惠清了。

修法:fillFormData从聚合区间数据中带上 toddlerDiscount(确认调用 fillFormData 的地方传入的 info 已包含此字段)。

4 NInputNumber value=0 显示样式

NInputNumber 在 value=0 且 disabled=false 时数字应该是深色。如果看起来浅,可能跟 naive-ui 主题变量有关,确认 --input-text-color 没被覆盖。这是小问题,根因 1 解决后视觉上就不再误导

后端是否需要改

是否需要
toddlerDiscount=0 归一化为 null 不改,语义正确
toddlerDiscount 字段语义/公式 待与前端对齐后,如果选方案 B 需改公式;选方案 A 不改
其他 后端不参与 UI 问题

验证清单(改完自测)

  • 价格区间「小童优惠额」label 颜色与「成人」「儿童」「单房差」一致
  • toddlerDiscount 输入框点击后能输入(无 disabled),输入 100 能保存
  • placeholder 文案与后端公式语义一致(正数/负数)
  • 区间原本 toddlerDiscount=10、儿童改 0 后保存,重开仍是 10(单日和批量两种入口都测)
  • 单日编辑(CalendarDayModal)只改其中一天的儿童价,其他天的 toddlerDiscount 不被影响

复现路径(给 mmg 测试)

  1. 产品 2045345825172639746(游牧的森林-短途版)第 4 步「定价管理」
  2. 「轻奢」档点某价格区间「编辑」
  3. 把儿童价从 X 改成 0,点确认
  4. 弹框关闭后再点同一区间「编辑」
  5. 观察小童优惠额:值是否变 0?label 是否浅色看似 disabled?

@mmg 请按根因 1(CSS)+ 根因 2(语义)+ 根因 4(单日覆盖)三处一起改。根因 3(后端归一化)保持不变。