# 前端 Bug 通知 — Step4 定价管理 新增价格区间不回显（前端只刷当前月）

- **日期**: 2026-04-18
- **类型**: 前端 Bug 修复指南（**不涉及后端代码改动，后端无需重启**）
- **涉及端**: 管理后台 hl-ui（产品编辑页 Step4 定价管理）
- **后端服务**: hl-product-service-v2（**后端已核实数据完全正确，无需改动**）
- **优先级**: P2
- **关联任务**: backend 仓库 `docs/tasks/20260418_修复_产品定价新增价格区间不回显.md`

---

## 一、问题描述

在产品编辑页 Step4「定价管理」tab 中：

- **预期**：在已有 2026-04 平日价区间的情况下，新增一段不同月份（如 2026-05-07 → 2026-05-17）的价格区间，点"确认添加"后列表应立即回显新增区间
- **实际**：点"确认添加"后**列表只显示原来 4 月那一条**，新增的 5 月区间**没有出现**，看起来"加不上"

**复现路径**：
- 环境：测试服 `192.168.100.160`
- 产品 ID：`2045345825172639746`（游牧的森林-短途版，CORE 草稿）
- 页面：产品编辑 → Step4 定价管理 → 选"轻奢"档位 tab
- 前置：已有 2026-04-01 → 2026-04-30 平日价（成人 ¥4105 / 儿童 ¥1865）
- 操作：点 +添加区间 → 填 2026-05-07 → 2026-05-17 / 平日 / 轻奢 / 成人 ¥4725 / 儿童 ¥1725 → 确认添加
- 结果：列表里看不到新加的 5 月区间

---

## 二、根因分析（后端已实证核实）

### 1. 后端 POST 已成功写入 DB（实证 1：服务日志）

测试环境 `hl-product-service-v2` 日志（`/opt/hulalv/logs/services/hl-product-service-v2/hl-product-service-v2-8083.log`）：

```
12:40:40 价格日历批量设置成功, productId=2045345825172639746, 日期范围=2026-05-02-2026-05-22, 记录数=21
12:41:17 价格日历批量设置成功, productId=2045345825172639746, 日期范围=2026-05-09-2026-05-10, 记录数=2
```

后端**全程零 ERROR / 零 WARN**，所有 batch 写入返回 200 成功。

### 2. DB 真实有 5 月数据（实证 2：数据库）

`hl_product_service.product_price_calendar` 表实际记录：

| range_id | 月份 | tier_seq | 日期范围 | 成人/儿童 | 条数 | deleted_at |
|----------|------|----------|----------|-----------|------|------------|
| 2045361705738719233 | 2026-04 | 1 | 4/1-4/30 | 4105/1865 | 30 | NULL |
| 2045361802451058689 | 2026-04 | 2 | 4/1-4/30 | 7777/1000 | 30 | NULL |
| 2045361847678160898 | 2026-05 | 2 | 5/2-5/22 | 4865/1865 | 21 | NULL |
| 2045362006457733121 | 2026-05 | 1 | 5/9-5/10 | 4695/1695 | 2  | NULL |

5 月数据真实存在、未被软删。

### 3. 前端只 GET 当前月份（实证 3：网关访问日志）

`hl-gateway-8080.log` 关键序列（保存 5 月区间前后）：

```
12:40:40.125 POST .../price-calendar/batch         ← 写入 5/2-5/22  ✓
12:40:40.379 GET  .../price-calendar?month=2026-04 ← 刷新只查 4 月 ❌
12:41:17.981 POST .../price-calendar/batch         ← 写入 5/9-5/10  ✓
12:41:18.239 GET  .../price-calendar?month=2026-04 ← 刷新只查 4 月 ❌
```

**整个会话前端从未发起 `month=2026-05` 的 GET 请求** → 后端有数据但前端从不拉取。

### 真因

前端 Step4 定价管理「保存价格区间」的成功回调里，只刷新当前选中月份的数据。当用户新增的区间落在**当前选中月之外的月份**（包括跨月、纯异月）时：
- POST batch 已正确入库
- 但 GET 只请求当前月，新月份的数据不在响应里
- 列表自然回显不出来

非后端 bug，**纯前端刷新策略问题**。

---

## 三、涉及接口契约（不变，仅供前端 AI 自包含参考）

### 1. 批量保存价格区间

| 项 | 值 |
|----|----|
| 方法 | POST |
| 路径 | `/admin/product/item/{productId}/price-calendar/batch` |
| 鉴权 | 管理后台 token |

**Request Body**（关键字段）：

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `startDate` | string `yyyy-MM-dd` | 是 | 区间起始日 |
| `endDate`   | string `yyyy-MM-dd` | 是 | 区间结束日 |
| `tierSeq`   | int    | 是 | 档位序号（1=经济 / 2=舒适 / 3=轻奢 / 4=高端 / 5=豪华） |
| `priceType` | string | 是 | `WEEKDAY` 平日 / `WEEKEND` 周末 / `HOLIDAY` 节假日 |
| `adultSellPrice` | decimal(12,2) | 是 | 成人售价 |
| `childSellPrice` | decimal(12,2) | 是 | 儿童售价 |
| `subChildDiscount` | decimal(12,2) | 否 | 小童优惠额 |
| `infantPrice` | decimal(12,2) | 否 | 幼童价 |
| `dailyStock` | int | 否 | 每日库存 |

**Response**：
```json
{
  "code": 200,
  "data": {
    "rangeId": 2045361847678160898,
    "recordCount": 21
  }
}
```

> 关键：响应里返回的 `rangeId` 可用于精确识别本次落库的区间，便于前端定位需要刷新哪些月。

### 2. 按月查询价格日历

| 项 | 值 |
|----|----|
| 方法 | GET |
| 路径 | `/admin/product/item/{productId}/price-calendar?month=yyyy-MM` |

按月返回该月**全部档位**所有日期的明细（不按 tierSeq 过滤）。

**Response**：
```json
{
  "code": 200,
  "data": [
    {
      "date": "2026-05-07",
      "tierSeq": 3,
      "rangeId": 2045361847678160898,
      "priceType": "WEEKDAY",
      "adultSellPrice": 4725.00,
      "childSellPrice": 1725.00,
      "subChildDiscount": null,
      "infantPrice": null,
      "dailyStock": null
    }
  ]
}
```

---

## 四、前端修复建议（任选其一）

### 方案 A（最小改动，推荐）

保存成功后，根据**本次保存的区间起止日期**，计算覆盖的月份集合（同月 1 个，跨月 N 个），逐月 GET 并 merge 到本地状态。

伪代码：
```js
async function onBatchSaved(payload) {
  // payload 是刚提交的请求体：{ startDate, endDate, tierSeq, ... }
  const startMonth = payload.startDate.slice(0, 7); // "2026-05"
  const endMonth   = payload.endDate.slice(0, 7);   // "2026-05"
  const months = enumerateMonths(startMonth, endMonth); // ["2026-05"] 或跨月 ["2026-05","2026-06"]

  // 同时 fetch 当前选中月（保留原有视图刷新）
  if (!months.includes(currentMonth.value)) months.push(currentMonth.value);

  const results = await Promise.all(
    months.map(m => api.getPriceCalendar(productId, m))
  );
  // 把所有月份的明细合并到本地状态，按区间聚合显示
  mergeIntoLocalState(results.flatMap(r => r.data));
}
```

并且，**保存成功后建议自动把月份选择器切换到新区间的起始月**（`startDate.slice(0,7)`），这样用户能立即看到刚加的区间，不用手动切月。

### 方案 B（粗暴但稳）

保存成功后，无脑刷新「当前月 ± 6 个月」共 13 个月（或业务可见范围全部月份），用 `Promise.all` 批量 GET 后聚合。请求多但实现简单，适合日历视图用户经常切月的场景。

### 方案 C（最稳妥）

保存成功后：
1. 清空本地所有缓存
2. 把月份选择器切到 `payload.startDate.slice(0, 7)`
3. 触发该月份的 GET 拉取

用户视觉上"跳到新区间所在月"，能立即看到新数据。请求最少（只 1 次），但前端要做一次月份切换动画。

### 三方案对比

| 方案 | 请求数 | UX | 实现复杂度 | 推荐度 |
|------|--------|----|------------|--------|
| A | 1~N（按跨月数） | 留在原月 + 数据合并 | 低 | ★★★★★ |
| B | ~13 | 留在原月 + 全量刷新 | 极低 | ★★★ |
| C | 1 | 跳到新月 | 低 | ★★★★ |

---

## 五、回归测试点（前端修完后自测）

- [ ] 当前选中 4 月，新增 5 月区间，确认添加后能立即在列表/日历中看到新区间
- [ ] 当前选中 5 月，新增 4 月区间，同上能立即看到
- [ ] 跨月新增（如 5/28 → 6/3），确认添加后两个月的数据都能看到
- [ ] 同月新增（4 月内 4/15 → 4/20），确认添加后能立即看到
- [ ] 多次连续新增不同月的区间，每次都能立即回显
- [ ] 新增成功后，月份选择器切换到任意月，数据都正确

---

## 六、后端是否有配合改动

**无**。

后端代码、接口路径、字段定义、返回结构、写入读取行为**完全不变**。**后端不会发版本，测试环境无需重启服务**。

---

## 七、可选优化（前端讨论后再决定，本次不做）

如果前端觉得"逐月 GET 合并"太繁琐，后端可以增加一个新接口（不影响现有接口）：

- `GET /admin/product/item/{productId}/price-calendar/ranges` —— 按 `range_id` 聚合返回区间列表（含 startDate/endDate/tierSeq/priceType/adultSellPrice/childSellPrice），不分月，一次拉完所有区间
- 或者给现有 `GET .../price-calendar` 增加 `startDate` / `endDate` 可选参数支持任意日期范围查询

如有需求请前端在本 changelog 评论或单独提工单，后端再评估排期。**本次不强制做，前端先按方案 A/B/C 之一修即可解决问题**。
