From 5449ace86e5b602a95ce1d35d5ba33b21de121e3 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Tue, 12 May 2026 15:51:10 +0800 Subject: [PATCH] =?UTF-8?q?frontend-bug(price-interval):=20=E5=84=BF?= =?UTF-8?q?=E7=AB=A5=3D0=20=E6=97=B6=E5=B0=8F=E7=AB=A5=E4=BC=98=E6=83=A0?= =?UTF-8?q?=E7=9C=8B=E8=B5=B7=E6=9D=A5=20disabled=20=E4=B8=94=E5=9B=9E?= =?UTF-8?q?=E6=98=BE=200=20=E2=80=94=20CSS+=E8=AF=AD=E4=B9=89+=E5=8D=95?= =?UTF-8?q?=E6=97=A5=E8=A6=86=E7=9B=96=E4=B8=89=E4=B8=AA=E6=A0=B9=E5=9B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...toddler-discount-locked-when-child-zero.md | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 changelogs/2026-05/12_frontend_bug_admin_price-interval-toddler-discount-locked-when-child-zero.md diff --git a/changelogs/2026-05/12_frontend_bug_admin_price-interval-toddler-discount-locked-when-child-zero.md b/changelogs/2026-05/12_frontend_bug_admin_price-interval-toddler-discount-locked-when-child-zero.md new file mode 100644 index 0000000..1db7633 --- /dev/null +++ b/changelogs/2026-05/12_frontend_bug_admin_price-interval-toddler-discount-locked-when-child-zero.md @@ -0,0 +1,164 @@ +--- +date: 2026-05-12 +type: frontend-bug +module: admin-price-interval-editor +priority: high +notify: ["@mmg"] +status: 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** + +```css +/* 第 562-565 行 */ +.pie-price-label { + font-size: 11px; + color: var(--text-color-placeholder); /* ❌ 浅灰 placeholder 色 */ +} +``` + +成人/儿童/单房差用的是 `NFormItem :label="..."` (深色文字),小童优惠额/幼童用的是 `...` (浅灰),**视觉上看起来一个深一个浅**。再叠加 NInputNumber 当 value=0 时数字也是浅色,用户**100% 会误判为 disabled**。 + +实际上第 358-361 行 NInputNumber **没有 disabled 属性**: + +```vue +
+ 小童优惠额 + + + +
+``` + +→ 点输入框其实**可以输入**,只是看起来不能。 + +### 根因 2 - **toddlerDiscount 字段语义跟后端公式相反** + +```vue +placeholder="选填,负数" +``` + +但后端 `ProductPricingService.java` 第 706 行计算公式: + +```java +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** + +```java +// 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 误导 + +```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 行): + +```js +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(后端归一化)保持不变。