feat: 新增角色管理模块,优化API与交互体验
1. 新增角色管理后台页面、路由与国际化文案 2. 重构API请求错误处理逻辑,统一拦截业务与HTTP错误 3. 新增确认弹窗组合式函数,区分取消与请求错误场景 4. 完善表格按钮权限与显示控制逻辑 5. 更新API参数规范与文档说明 6. 修复部分页面分页数据解析问题
This commit is contained in:
213
docs/表格组件使用说明.md
213
docs/表格组件使用说明.md
@@ -17,7 +17,8 @@
|
||||
7. [路由配置](#7-路由配置)
|
||||
8. [API 文件写法](#8-api-文件写法)
|
||||
9. [旧代码迁移对照](#9-旧代码迁移对照)
|
||||
10. [常见问题排查](#10-常见问题排查)
|
||||
10. [接口请求错误处理规范](#10-接口请求错误处理规范)
|
||||
11. [常见问题排查](#11-常见问题排查)
|
||||
|
||||
---
|
||||
|
||||
@@ -963,7 +964,215 @@ export function deleteFactoryArea (data) {
|
||||
|
||||
---
|
||||
|
||||
## 10. 常见问题排查
|
||||
## 10. 接口请求错误处理规范
|
||||
|
||||
> 错误处理采用 **三层架构**:拦截器全局兜底 → confirmMixin 区分取消/错误 → 页面标准模式。
|
||||
|
||||
### 10.1 错误处理总流程
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ 页面发起 API 请求 │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
┌──────────▼──────────┐
|
||||
│ 是否需 confirm ? │
|
||||
└─────┬────────┬──────┘
|
||||
│ 否 │ 是
|
||||
│ │
|
||||
│ ┌────▼───────────┐
|
||||
│ │ $confirmAction() │
|
||||
│ │ (confirmMixin) │
|
||||
│ └────┬──────┬─────┘
|
||||
│ 取消 │ │ 确认
|
||||
│ ┌────▼──┐ │
|
||||
│ │return │ │
|
||||
│ │ true │ │
|
||||
│ └───────┘ │
|
||||
│ │
|
||||
┌──────────▼────────────────▼──┐
|
||||
│ axios 请求 │
|
||||
└──────────┬───────────────────┘
|
||||
│
|
||||
┌───────────▼────────────┐
|
||||
│ HTTP 状态码是什么? │
|
||||
└──┬──────────────┬──────┘
|
||||
200 │ │ 非 200
|
||||
│ │
|
||||
┌───────────▼──────┐ ┌───▼─────────────┐
|
||||
│ response.data. │ │ HTTP 拦截器 │
|
||||
│ code 是什么? │ │ error.message = │
|
||||
└──┬───────────┬───┘ │ '未授权/超时/...' │
|
||||
0 │ │≠0 └───┬──────────────┘
|
||||
│ │ │
|
||||
┌────▼──┐ ┌─────▼──────┐ │
|
||||
│resolve│ │handleError()│◄─┘
|
||||
│ data │ │Message.error│
|
||||
└───────┘ └─────┬──────┘
|
||||
│
|
||||
┌────▼────┐
|
||||
│ throw │
|
||||
│ error │
|
||||
└────┬────┘
|
||||
│
|
||||
┌─────────▼─────────┐
|
||||
│ 页面层 catch 到了吗?│
|
||||
└────┬──────────┬───┘
|
||||
没写 │ │ 写了
|
||||
│ │
|
||||
┌─────────▼──┐ ┌────▼─────────────┐
|
||||
│ 什么都不发生 │ │ finally 关锁 │
|
||||
│(依赖拦截器 │ │ loading/submitting│
|
||||
│ Message) │ │ = false │
|
||||
└────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
### 10.2 三层架构详图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ 第一层:全局拦截器 │
|
||||
│ src/api/_service.js │
|
||||
│ │
|
||||
│ 拦截所有 axios 响应,统一处理两类错误: │
|
||||
│ │
|
||||
│ HTTP 错误 (4xx/5xx) │
|
||||
│ ├─ 400 → error.message = '请求错误' │
|
||||
│ ├─ 401 → error.message = '未授权,请登录' │
|
||||
│ ├─ 403 → error.message = '拒绝访问' │
|
||||
│ ├─ 404 → error.message = '请求地址出错' │
|
||||
│ ├─ 408 → error.message = '请求超时' │
|
||||
│ ├─ 500 → error.message = '服务器内部错误' │
|
||||
│ ├─ 502 → error.message = '网关错误' │
|
||||
│ ├─ ... 其它状态码 │
|
||||
│ │ │
|
||||
│ └─ handleError(error) │
|
||||
│ ├─ store.dispatch('d2admin/log/push', ...) │
|
||||
│ ├─ console.log(error) (dev only) │
|
||||
│ └─ Message.error(error.message) ← 弹红色提示 │
|
||||
│ │
|
||||
│ 业务错误 (code ≠ 0, 如 code=500 "参数错误") │
|
||||
│ ├─ 取出 response.data.msg 或 '请求失败' │
|
||||
│ ├─ const err = new Error(`${msg}: ${url}`) │
|
||||
│ ├─ handleError(err) │
|
||||
│ └─ throw err ← 继续向上抛给页面层 │
|
||||
└──────────────────────┬──────────────────────────┘
|
||||
│
|
||||
┌──────────────────────▼──────────────────────────┐
|
||||
│ 第二层:confirmMixin │
|
||||
│ src/composables/useConfirmHandle.js │
|
||||
│ │
|
||||
│ $confirmAction(confirmOpts, action) │
|
||||
│ ├─ 第一层 try/catch │
|
||||
│ │ await this.$confirm(...) │
|
||||
│ │ ├─ 用户点"确定" → 继续 │
|
||||
│ │ └─ 用户点"取消" → catch → return true │
|
||||
│ │ │
|
||||
│ └─ 第二层 try/catch │
|
||||
│ await action() ← 执行 API 调用 │
|
||||
│ ├─ 成功 → return false │
|
||||
│ └─ 失败 → catch → return false │
|
||||
│ (拦截器已弹出 Message,此处静默吞掉) │
|
||||
└──────────────────────┬──────────────────────────┘
|
||||
│
|
||||
┌──────────────────────▼──────────────────────────┐
|
||||
│ 第三层:页面标准模式 │
|
||||
│ │
|
||||
│ 场景A — 查询 (fetchData) │
|
||||
│ ┌─────────────────────────────────────────────┐ │
|
||||
│ │ async fetchData () { │ │
|
||||
│ │ this.loading = true │ │
|
||||
│ │ try { │ │
|
||||
│ │ const res = await getList(...) │ │
|
||||
│ │ this.tableData = res.data │ │
|
||||
│ │ } finally { this.loading = false } │ │
|
||||
│ │ } │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ • try 内无 catch:拦截器已弹错误提示 │
|
||||
│ • finally 始终执行:loading 无论成败都关闭 │
|
||||
│ │
|
||||
│ 场景B — 提交 (onDialogSubmit) │
|
||||
│ ┌─────────────────────────────────────────────┐ │
|
||||
│ │ async onDialogSubmit () { │ │
|
||||
│ │ this.submitting = true │ │
|
||||
│ │ try { │ │
|
||||
│ │ await createApi(this.formData) │ │
|
||||
│ │ this.$message.success(...) ← 成功才执行 │ │
|
||||
│ │ this.dialogVisible = false │ │
|
||||
│ │ this.fetchData() │ │
|
||||
│ │ } finally { this.submitting = false } │ │
|
||||
│ │ } │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ • 出错时弹窗不关,用户可修改后重试 │
|
||||
│ │
|
||||
│ 场景C — 删除 (handleDelete) │
|
||||
│ ┌─────────────────────────────────────────────┐ │
|
||||
│ │ async handleDelete (row) { │ │
|
||||
│ │ const cancelled = await this.$confirmAction(│ │
|
||||
│ │ { message: this.key('confirm_delete'), │ │
|
||||
│ │ title: this.key('tip') }, │ │
|
||||
│ │ () => deleteApi({ id: row.id }) │ │
|
||||
│ │ ) │ │
|
||||
│ │ if (cancelled) return ← 用户取消,静默 │ │
|
||||
│ │ this.$message.success(...) ← 成功才执行 │ │
|
||||
│ │ this.fetchData() │ │
|
||||
│ │ } │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ • $confirmAction 内已处理错误 Message,不额外 catch│
|
||||
│ • 没有 try/catch 包裹 → 代码更简洁直观 │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 10.3 关键原则
|
||||
|
||||
| 原则 | 说明 |
|
||||
|------|------|
|
||||
| **拦截器兜底** | 所有 API 错误(HTTP + 业务)统一在 `_service.js` 中弹出 `Message.error` |
|
||||
| **finally 关锁** | `loading` / `submitting` 用 `try { ... } finally { lock = false }`,不论成败都关闭 |
|
||||
| **只用 catch 不动** | 成功后的 `$message.success` / `dialogVisible = false` / `fetchData` 不放在 `finally`,只有成功才执行 |
|
||||
| **$confirmAction 包装** | 删除/批量操作类的 confirm + API 用 `$confirmAction()` 区分取消和错误 |
|
||||
| **不吞业务错误** | `fetchData` / `onDialogSubmit` 的 try 只写 `finally`,不写 `catch`,错误由拦截器处理 |
|
||||
|
||||
### 10.4 confirmMixin 使用
|
||||
|
||||
页面中引入:
|
||||
|
||||
```js
|
||||
import { confirmMixin } from '@/composables/useConfirmHandle'
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
i18nMixin('page.xxx.xxx'),
|
||||
confirmMixin // ← 新增
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
API:
|
||||
|
||||
```js
|
||||
// $confirmAction(confirmOpts, actionFn) → Promise<boolean>
|
||||
// 返回 true = 用户点了取消
|
||||
// 返回 false = action 已执行完成(无论成功失败)
|
||||
|
||||
const cancelled = await this.$confirmAction(
|
||||
{
|
||||
message: this.key('confirm_delete'), // confirm 正文
|
||||
title: this.key('tip'), // confirm 标题
|
||||
// type: 'warning', // 可选,默认 'warning'
|
||||
// confirmButtonText / cancelButtonText 可选,默认用 page.common 的
|
||||
},
|
||||
() => deleteApi({ id: row.id }) // 确认后执行的异步函数
|
||||
)
|
||||
if (cancelled) return
|
||||
// 以下只在成功时执行
|
||||
this.$message.success(...)
|
||||
this.fetchData()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 常见问题排查
|
||||
|
||||
### Q1:弹框打开后不显示内容?
|
||||
|
||||
|
||||
@@ -41,6 +41,18 @@
|
||||
```
|
||||
- 后续统一修改路由后再批量替换
|
||||
|
||||
#### 4.1 API 参数名必须对齐旧项目(重要)
|
||||
- **API 函数入参的 key 名称必须与旧项目保持一致**,不能自行发明参数名
|
||||
- 例如:旧项目角色菜单接口参数是 `role_id`,不能写成 `id`
|
||||
```js
|
||||
// ❌ 错误 — 自行发明参数名
|
||||
getRoleMenu({ id: this.roleId })
|
||||
// ✅ 正确 — 与旧项目参数名一致
|
||||
getRoleMenu({ role_id: this.roleId })
|
||||
```
|
||||
- **迁移 API 时必须仔细阅读旧项目接口代码**,逐个核对每个接口的入参 key 名称
|
||||
- 如果后端报「xxx 不能为空」,首先检查参数 key 是否与旧项目一致
|
||||
|
||||
#### 5. 旧 key → 新 key 映射
|
||||
如果用户提供的是旧页面代码,需要根据对照表做 key 映射。已知映射如下(持续补充):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user