1 changed files with 65 additions and 0 deletions
@ -0,0 +1,65 @@ |
|||||
|
# 设计:全站 API 失败统一文案与 Toast 提示 |
||||
|
|
||||
|
**日期**:2026-04-18 |
||||
|
**状态**:已定稿(与产品对话一致) |
||||
|
|
||||
|
## 1. 背景与目标 |
||||
|
|
||||
|
当前 `app/utils/http/factory.ts` 中 `request` 为裸 `$fetch` 实例;`unwrapApiBody` 在 `code !== 0` 时抛出带 `message` 的 `Error`。HTTP 层失败表现为 ofetch 的 `FetchError`(常见字段含 `statusMessage`、`data`)。各页面仅在自行 `catch` 时才会 `useToast()`,导致部分操作失败时 **无可见反馈**,且多处重复手写 `extractError`,同一类失败文案不一致。 |
||||
|
|
||||
|
**目标**: |
||||
|
|
||||
|
- 用户触发的 API 调用失败时,**默认**有一条 **可读中文 toast**(Nuxt UI `useToast`)。 |
||||
|
- 文案优先来自服务端已有 **`statusMessage` / 业务 `message`**,避免空白或仅控制台错误。 |
||||
|
- **单一实现** 解析 `unknown` 错误为展示字符串,收敛重复代码。 |
||||
|
|
||||
|
## 2. 非目标 |
||||
|
|
||||
|
- 在 **SSR** 阶段弹出 toast(仅客户端展示)。 |
||||
|
- 替代表单字段级校验(组件内仍可保留短提示)。 |
||||
|
- 本次不强制改造 `_useHttpFetch` / 声明式 `useFetch` 用法(可作为后续增量)。 |
||||
|
- 不改变服务端错误契约(仍用 `createError` + `ApiResponse` 等现有形态)。 |
||||
|
|
||||
|
## 3. 方案结论(已选) |
||||
|
|
||||
|
采用 **薄封装 + 统一解析**(否决全局 `$fetch` 钩子自动 toast:难以区分静默场景、与 `useAuthSession` 等冲突)。 |
||||
|
|
||||
|
| 构件 | 职责 | |
||||
|
|------|------| |
||||
|
| `getApiErrorMessage(error: unknown): string` | 单一模块,统一从 `Error` / `FetchError` 等解析用户可见文案。 | |
||||
|
| `useClientApi()`(命名以实现为准) | 返回与 `request` 兼容的调用方法;**默认**在客户端失败时 `toast.add`(error)后 **原样 rethrow**,保证调用方 `finally`、状态回滚仍可用。 | |
||||
|
| 选项 `notify: false` | 关闭本次请求的 toast(会话刷新、401 静默路径、登录页内联错误、自定义多段流程等)。 | |
||||
|
|
||||
|
## 4. 文案解析规则 |
||||
|
|
||||
|
按顺序取值,命中即停: |
||||
|
|
||||
|
1. 若对象为 FetchError 风格且 **`data` 为对象且存在非空 `message` 字符串** → 使用该 `message`。 |
||||
|
2. 若非空 **`statusMessage` 字符串** → 使用。 |
||||
|
3. 若为 `Error` 且 **`message` 非空**(含 `unwrapApiBody` 抛出的业务 `message`)→ 使用。 |
||||
|
4. 若存在 **`statusCode`**:**401** → 「登录已失效,请重新登录」;**403** → 「没有权限执行此操作」;**404** → 「请求的资源不存在」;**5xx** → 「服务暂时不可用,请稍后重试」。 |
||||
|
5. 无状态码或网络类失败 → 「网络异常,请检查连接后重试」或等价短句。 |
||||
|
6. 兜底 → 「请求失败,请稍后重试」。 |
||||
|
|
||||
|
(具体措辞可在实现时微调,但优先级与分支须一致,避免再次出现多种解析逻辑。) |
||||
|
|
||||
|
## 5. 401 与认证路径(已确认策略) |
||||
|
|
||||
|
- **`useAuthSession.refresh` / 通过 `useRequestFetch` 拉 `/api/auth/me` 的路径**:保持现有行为 — **不**因 401 弹业务 toast(`notify: false` 或继续使用裸 `request` / 现有 `catch` 逻辑);401 时清会话等逻辑不变。 |
||||
|
- **其余用户触发的客户端 API**:默认 **`notify: true`**;若响应为 401,用户看到 **一条**与第 4 节一致的短文案(与「静默清会话」可并存:先 toast,路由或后续导航由现有布局/中间件处理)。 |
||||
|
- **登录 / 注册页**:失败提示以 **表单内联** 为主;对应请求使用 **`notify: false`**,避免与内联文案重复。 |
||||
|
|
||||
|
## 6. 迁移与重复 toast 防护 |
||||
|
|
||||
|
- 将 `app/pages/**`、`app/components/**` 中 **面向用户的** `request(...)` 逐步改为封装方法(默认 `notify: true`)。 |
||||
|
- 若调用方已有 `catch` 且 **`toast.add`**,应 **删除** 其中重复的错误 toast,仅保留特殊成功提示或额外上下文(否则会双弹)。 |
||||
|
- 保留 `unwrapApiBody` 与 `ApiResponse` 类型;封装内部仍调用现有 `request`。 |
||||
|
|
||||
|
## 7. 测试与验收 |
||||
|
|
||||
|
- **手工**:至少覆盖 — 登录失败(内联、无重复 toast)、管理端创建用户失败、评论失败、上传失败、任意 403/404 接口。 |
||||
|
- **自动化**:若有 e2e,可增一条「失败时出现全局通知」;否则以回归清单为准。 |
||||
|
|
||||
|
## 8. 后续 |
||||
|
|
||||
|
定稿并实现后,由 **`writing-plans`** 产出 `docs/superpowers/plans/2026-04-18-api-error-toast-implementation-plan.md`(或同日合并命名),再进入编码。 |
||||
Loading…
Reference in new issue