# 【前端 BUG·管理后台】任务看板打开弹「获取看板详情/任务/成员失败」红 toast = 前端竞态重复发请求 + 取消被误报错 > **关联**: 无工单(前端 bug 走 changelog 通知 mmg) > **服务**: 前端 hl-ui(**后端零改动**)| **提报时间**: 2026-06-05 > > **存放目录**: `changelogs-v2/2026-06/` > **影响范围**: 管理后台「任务看板」`/task/boards`(看板列表 / 看板详情视图) > **类型**: 前端竞态 bug(mmg 修),**后端实测全 200 健康无需改动** --- ## ⚠️ 关键说明 打开任务看板时弹出红色错误 toast「**获取看板详情失败 / 获取看板任务失败 / 获取看板成员失败**」,**但看板数据其实正常加载出来了**(公司看板、各状态列、任务卡片都在)。 **根因 = 前端竞态,不是后端故障。** 看板组件挂载时对**同一个数据接口重复发请求**(`tasks` 发 3 次、`members` 发 2 次、`detail` 视时序也会重发),多余的在飞请求被前端 `AbortController` **主动取消**(浏览器 `net::ERR_ABORTED`)。前端 `.catch()` **没有把「请求被取消」和「请求真失败」区分开**,于是把取消也当成失败弹了红 toast。 后端侧:凡是被允许跑完的请求**全部返回 200**(看板列表/详情/任务/成员/状态 + token refresh,curl 80+ 次 + 真浏览器多轮复现,零服务端失败)。 --- ## 1. 实证(真浏览器 DevTools Network,刚登录 token 全新,硬刷新复现) 打开 `/task/boards`(公司看板 `2022226454376366081`)后的请求序列: | # | 请求 | 结果 | |---|---|---| | 1 | `GET /admin/task/boards`(看板列表) | **200** ✅ | | 2 | `GET /admin/task/board/{id}`(看板详情,含 statuses) | **200** ✅ | | 3 | `GET /admin/task/board/{id}/tasks` | **FAILED `net::ERR_ABORTED`** ❌ | | 4 | `GET /admin/task/board/{id}/tasks` | **200** ✅(同一接口重发,成功)| | 5 | `GET /admin/task/board/{id}/members` | **200** ✅ | | 6 | `GET /admin/task/board/{id}/tasks` | **FAILED `net::ERR_ABORTED`** ❌ | | 7 | `GET /admin/task/board/{id}/members` | **200** ✅(同一接口重发)| 要点: - `tasks` 接口被触发 **3 次**(2 次被取消、1 次成功);`members` 触发 **2 次**;`detail` 偶发重发被取消时就弹「获取看板详情失败」。 - `net::ERR_ABORTED` 是**客户端主动取消**(不是 4xx/5xx,服务端从没收到失败结果),典型来自 axios cancel / `AbortController.abort()` / 组件重渲染或重复触发同一 fetch。 - 浏览器 console **0 个 error**(说明取消已被 catch 捕获),但 catch 里仍调用了错误 toast。 后端实测对照(均 200,有数据): ``` GET /admin/task/boards -> 200, 看板列表 GET /admin/task/board/2022226454376366081 -> 200, 详情含 4 个 statuses GET /admin/task/board/2022226454376366081/tasks -> 200, 任务分组 GET /admin/task/board/2022226454376366081/members -> 200, [] GET /admin/task/board/2022226454376366081/statuses-> 200, 4 条 POST /admin/auth/refresh (真 refreshToken) -> 200(连打 30 次全 200) ``` --- ## 2. 前端修法(mmg) ### 2.1 必修:catch 里放过「被取消」的请求,别弹错误 toast 错误处理里先判断是否为取消,是则**静默 return**,不弹 toast: ```js import axios from 'axios' // ... .catch((e) => { // 请求被取消(组件重渲染/重复触发抢占)是预期行为,不是错误 if (axios.isCancel(e) || e?.code === 'ERR_CANCELED' || e?.name === 'CanceledError') return ElMessage.error('获取看板任务失败') // 只有真失败才提示 }) ``` 三处都要加:获取看板详情 / 获取看板任务 / 获取看板成员的 catch。 ### 2.2 根治:去掉对同一接口的重复触发 `tasks` 发 3 次、`members` 发 2 次,说明同一 fetch 被多个来源同时触发(常见:`onMounted` 触发一次 + `watch(boardId)` 触发一次 + 默认「优先级/状态」筛选 radio 又触发一次)。建议: - 数据加载**只保留单一触发源**(推荐由 `watch(boardId, { immediate: true })` 统一触发,去掉 `onMounted` 里的重复拉取); - 或对每类请求维护一个 `AbortController`,重发前先 `abort()` 旧的——**但这种「主动取消」必须配合 §2.1,把取消视为正常、不报错**。 ### 2.3 注意(与本 bug 无关、但同页常见) 看板有实时通道(`/ws/task/info` + `/admin/task/board/ws-doc`)。若 WS 推送后又触发整页重新拉取,也会放大上面的「重复请求 + 取消」现象,一并按 §2.1/§2.2 处理。 --- ## 3. 后端状态(无需改动) - 任务看板全部读接口(list / detail / tasks / members / statuses)实测 200,数据正确。 - `POST /admin/auth/refresh`:网关已在 `JwtAuthFilter.SKIP_URLS` 白名单放行,带过期 access token + 真 refreshToken 实测 200,20 并发全 200。 ### 3.1 附注:你截图里 `refresh` 的 502 是「另一桩、瞬时」 你之前截图 Network 里 `refresh` 显示 **502**,那是**独立的瞬时事件**:当时会话 access token 已过期 → 前端自动调 `/admin/auth/refresh` 续签 → 恰好撞上测试服 user-service(双实例 8081/8181)部署/重启抖动,网关 LB 轮到正在重启的实例返 502。**当前 30+ 次 refresh 全 200,复现不出,不是看板代码缺陷**,无需前端改动;若再遇到,刷新页面即恢复。 --- ## 4. 关联 - 后端负责人:@wx(已实测确认后端健康,无改动) - 前端对接(本 bug 修复):**mmg** - 复现路径:登录管理后台 → 任务看板(`/task/boards`)→ 打开「公司看板」即弹失败 toast,但数据正常。