# 前端 BUG 通知 — 主题列表筛选触发并发 3 请求互相 cancel (DevTools 实测纠正 07 文件根因)

**日期**: 2026-05-09
**类型**: 前端 BUG (admin 后台 hl-ui)
**模块**: 主题管理 (产品线 / Product Line)
**通知**: @mmg
**后端是否需改动**: 否(本日已 PR #1908 做防御性加固)
**关联**: `07_frontend_notice_topic-filter-empty.md` (07 文件的根因推断 A/B 已被本次 DevTools 证据**否定**)

---

## 现象

用户 2026-05-09 复现, DevTools Network 截图证实:

打开「主题列表」页 → 选「主题类型 = 小蒙马」点搜索 → 列表显示「暂无主题数据」, 但顶部仍显示「共 15 条」

**Network 关键发现**:
- 同一时刻并发发出 **3 个请求**, productType 分别是 `CORE` / `CUSTOM` / `GROUP`
- 所有 3 个请求 status 全部 `(canceled)` (size 0.0kB / time 8-27ms)
- 因为请求都被 cancel, 列表 store 收不到任何 data → 列表空
- 「共 15 条」是上一次请求成功的缓存值, 没刷

```
list?keyword=&productType=CORE&page=1&pageSize...    (canceled)  27 ms
list?keyword=&productType=CUSTOM&page=1&pageSize...  (canceled)  10 ms
list?keyword=&productType=GROUP&page=1&pageSize...   (canceled)   8 ms
```

---

## 根因 (推翻 07 文件的 A/B 推断)

07 文件原推断:
- (A) 字典 store localStorage 老数据 → ❌ 不成立, 请求里 productType 字段是正确的字典 value
- (B) 前端把 label 当 value 传给后端 → ❌ 不成立, 请求里清楚是 `GROUP` 而非 `小蒙马`

**真实根因 (推断)**:

页面进入时 URL 带 `?productType=GROUP`, 触发了**多个 watch/computed 同时联动**, 各自独立调 `getProductLinePage()` 拼接不同的 productType:
- 一个 watch 监听 URL query → 发 `productType=GROUP`
- 另一个 watch 监听筛选下拉 model → 看到 dict store 拉好后默认 option 又触发一次, 可能是 CORE
- 又一个 watch 监听某个状态切换 → 发 `productType=CUSTOM`

axios 默认会把同一接口的并发请求按 `cancelToken` 互相 cancel (项目 axios 拦截器很可能有 dedupe / cancel-on-new 逻辑), 结果**3 个全被 cancel** (互相 cancel 死锁).

---

## 复现取证

请 mmg 在 DevTools 里:
1. Network 抓页面进入时刻所有 `GET /admin/product/line/list` 请求, 看是否真的有 3 个并发
2. Sources 里搜 `getProductLinePage` 找所有调用点, 看是否多个 watch/onMounted/init 各自触发了一次
3. axios 拦截器 (`hl-ui/src/utils/request.js` 或 `hl-ui/src/api/index.js`) 是否有 cancel-on-duplicate 逻辑

---

## 建议修复方向

**优先级 1**: 把 `getProductLinePage` 调用收敛到**单一入口**:
- 页面 onMounted 触发一次 fetch
- 用户点搜索 / reset / 改筛选 → 只有点搜索按钮才 fetch (不要 watch 字段直接 fetch)
- URL query 同步只更新 store / 表单 model, 不直接 fetch

**优先级 2**: 检查 axios 拦截器 cancel 逻辑:
- 如果有 dedupe by url, `productType=GROUP` 与 `productType=CORE` URL 不同应当不算 dup, 但仍被 cancel → 拦截器逻辑 bug
- 临时缓解: 关掉这个 dedupe / cancel-on-new

**优先级 3**: 后端兼容性 (本次 PR #1908 已加固)
- 即使前端真传了 `productType=GROUP,CORE,CUSTOM` 拼接形式, 后端也能 setter 兼容
- 即使前端某次传了字典里没有的 status, 后端不再 500

---

## 涉及文件 (前端排查参考)

- 页面: `hl-ui/src/views/product/line/index.vue`
- API: `hl-ui/src/api/product/core.js:49-51` `getProductLinePage`
- axios 配置: `hl-ui/src/utils/request.js` 或 `hl-ui/src/api/index.js`

---

## 后端关联

后端今日 PR #1908 已落地防御性加固 (详见 `09_refactor_admin_list_filter_governance.md`):
- `ProductLinePageReqVO.productType` 改 `List<String>` + 自定义 setter, 兼容 4 种入参
- 即使前端按重复 key / 逗号分隔 / 单值任一方式发送, 后端能正确接住

后端**不能**修复"3 个请求互相 cancel"问题——这是前端调用频次问题, 必须前端修.
