717 lines
15 KiB
Markdown
717 lines
15 KiB
Markdown
|
|
# API 错误处理规范
|
|||
|
|
|
|||
|
|
> 本文档定义了项目中 API 请求的错误处理标准,确保错误提示的一致性和用户体验。
|
|||
|
|
|
|||
|
|
## 📋 目录
|
|||
|
|
|
|||
|
|
- [核心原则](#核心原则)
|
|||
|
|
- [全局拦截器机制](#全局拦截器机制)
|
|||
|
|
- [API 层规范](#api-层规范)
|
|||
|
|
- [页面层规范](#页面层规范)
|
|||
|
|
- [完整示例](#完整示例)
|
|||
|
|
- [常见问题](#常见问题)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 核心原则
|
|||
|
|
|
|||
|
|
### ✅ 避免重复提示
|
|||
|
|
- **全局拦截器**已经处理了大部分错误提示
|
|||
|
|
- **页面层**不应再显示固定的错误提示
|
|||
|
|
- 只在需要自定义错误处理时使用 `errorMode: 'page'`
|
|||
|
|
|
|||
|
|
### ✅ 优先使用后端 message
|
|||
|
|
- 全局拦截器会自动提取后端返回的 `message` 字段
|
|||
|
|
- 页面层使用 `getApiErrorMessage` 工具函数提取错误信息
|
|||
|
|
- 避免写死前端错误文案
|
|||
|
|
|
|||
|
|
### ✅ 业务逻辑与错误提示分离
|
|||
|
|
- `catch` 块只处理必要的业务逻辑(数据清空、状态重置等)
|
|||
|
|
- 错误提示交给全局拦截器或使用 `getApiErrorMessage`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 全局拦截器机制
|
|||
|
|
|
|||
|
|
### 位置
|
|||
|
|
`src/utils/request.ts`
|
|||
|
|
|
|||
|
|
### 错误处理流程
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 响应拦截器
|
|||
|
|
service.interceptors.response.use(
|
|||
|
|
(response) => {
|
|||
|
|
const res = response.data;
|
|||
|
|
const code = res.code;
|
|||
|
|
|
|||
|
|
// 业务成功
|
|||
|
|
if (code === 200 || code === 0) {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 业务失败
|
|||
|
|
const errorMode = response.config.requestOptions?.errorMode || 'global';
|
|||
|
|
|
|||
|
|
if (errorMode === 'global') {
|
|||
|
|
// 全局模式:自动显示后端 message
|
|||
|
|
ElMessage.error(res.message || res.msg || '操作失败');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 抛出错误供页面 catch
|
|||
|
|
return Promise.reject(new Error(res.message || res.msg));
|
|||
|
|
},
|
|||
|
|
(error) => {
|
|||
|
|
// 网络错误、超时等
|
|||
|
|
ElMessage.error('网络请求失败,请稍后重试');
|
|||
|
|
return Promise.reject(error);
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 错误模式
|
|||
|
|
|
|||
|
|
| 模式 | 说明 | 使用场景 |
|
|||
|
|
|------|------|----------|
|
|||
|
|
| `global`(默认) | 全局拦截器自动显示错误 | 大部分接口(推荐) |
|
|||
|
|
| `page` | 页面自己处理错误 | 需要自定义错误处理时 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## API 层规范
|
|||
|
|
|
|||
|
|
### 1. 默认配置(推荐 95% 的场景)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// src/api/xxx/index.ts
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取列表
|
|||
|
|
* 使用默认配置,全局拦截器自动处理错误
|
|||
|
|
*/
|
|||
|
|
export function getList(params: ListParams) {
|
|||
|
|
return request({
|
|||
|
|
url: '/api/xxx/list',
|
|||
|
|
method: 'get',
|
|||
|
|
params
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建数据
|
|||
|
|
* 使用默认配置
|
|||
|
|
*/
|
|||
|
|
export function createItem(data: CreateParams) {
|
|||
|
|
return request({
|
|||
|
|
url: '/api/xxx/create',
|
|||
|
|
method: 'post',
|
|||
|
|
data
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新数据
|
|||
|
|
* 使用默认配置
|
|||
|
|
*/
|
|||
|
|
export function updateItem(data: UpdateParams) {
|
|||
|
|
return request({
|
|||
|
|
url: '/api/xxx/update',
|
|||
|
|
method: 'put',
|
|||
|
|
data
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除数据
|
|||
|
|
* 使用默认配置
|
|||
|
|
*/
|
|||
|
|
export function deleteItem(id: string) {
|
|||
|
|
return request({
|
|||
|
|
url: `/api/xxx/delete/${id}`,
|
|||
|
|
method: 'delete'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 页面自定义错误处理(特殊场景)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// src/api/xxx/index.ts
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 批量导入
|
|||
|
|
* 需要页面自定义错误处理(显示详细的导入结果)
|
|||
|
|
*/
|
|||
|
|
export function batchImport(data: ImportParams) {
|
|||
|
|
return request({
|
|||
|
|
url: '/api/xxx/import',
|
|||
|
|
method: 'post',
|
|||
|
|
data,
|
|||
|
|
requestOptions: {
|
|||
|
|
errorMode: 'page' // 页面自己处理错误
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 复杂表单提交
|
|||
|
|
* 需要根据不同错误类型做不同处理
|
|||
|
|
*/
|
|||
|
|
export function submitComplexForm(data: FormData) {
|
|||
|
|
return request({
|
|||
|
|
url: '/api/xxx/submit',
|
|||
|
|
method: 'post',
|
|||
|
|
data,
|
|||
|
|
requestOptions: {
|
|||
|
|
errorMode: 'page'
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 何时使用 `errorMode: 'page'`?
|
|||
|
|
|
|||
|
|
仅在以下情况使用:
|
|||
|
|
|
|||
|
|
- ✅ 需要根据不同错误码做不同处理
|
|||
|
|
- ✅ 需要自定义错误提示格式
|
|||
|
|
- ✅ 需要在错误后执行特殊业务逻辑
|
|||
|
|
- ✅ 需要显示详细的错误信息(如批量操作结果)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 页面层规范
|
|||
|
|
|
|||
|
|
### 1. 默认场景 - 全局错误处理
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// ✅ 推荐写法
|
|||
|
|
const getList = async () => {
|
|||
|
|
loading.value = true;
|
|||
|
|
try {
|
|||
|
|
const res = await listApi(params);
|
|||
|
|
tableData.value = res.data?.list || [];
|
|||
|
|
total.value = res.data?.total || 0;
|
|||
|
|
} catch (error) {
|
|||
|
|
// 错误已由全局拦截器处理
|
|||
|
|
// 这里只处理必要的业务逻辑
|
|||
|
|
tableData.value = [];
|
|||
|
|
total.value = 0;
|
|||
|
|
} finally {
|
|||
|
|
loading.value = false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ✅ 简单场景可以不写 catch
|
|||
|
|
const deleteItem = async (id: string) => {
|
|||
|
|
try {
|
|||
|
|
await deleteApi(id);
|
|||
|
|
ElMessage.success('删除成功');
|
|||
|
|
getList(); // 刷新列表
|
|||
|
|
} catch (error) {
|
|||
|
|
// 错误已由全局拦截器处理
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ✅ 更简洁的写法(如果不需要处理错误)
|
|||
|
|
const deleteItem = async (id: string) => {
|
|||
|
|
await deleteApi(id);
|
|||
|
|
ElMessage.success('删除成功');
|
|||
|
|
getList();
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 页面自定义错误处理
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { getApiErrorMessage } from '/@/utils/request';
|
|||
|
|
|
|||
|
|
// ⚠️ 仅在 API 设置了 errorMode: 'page' 时使用
|
|||
|
|
const saveData = async () => {
|
|||
|
|
loading.value = true;
|
|||
|
|
try {
|
|||
|
|
await saveApi(formData);
|
|||
|
|
ElMessage.success('保存成功');
|
|||
|
|
closeDialog();
|
|||
|
|
} catch (error) {
|
|||
|
|
// 使用 getApiErrorMessage 提取后端错误信息
|
|||
|
|
ElMessage.error(getApiErrorMessage(error, '保存失败'));
|
|||
|
|
} finally {
|
|||
|
|
loading.value = false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 根据不同错误做不同处理
|
|||
|
|
const deleteItem = async (id: string) => {
|
|||
|
|
try {
|
|||
|
|
await deleteApi(id);
|
|||
|
|
ElMessage.success('删除成功');
|
|||
|
|
getList();
|
|||
|
|
} catch (error) {
|
|||
|
|
const msg = getApiErrorMessage(error, '删除失败');
|
|||
|
|
|
|||
|
|
// 根据错误信息做不同处理
|
|||
|
|
if (msg.includes('被引用')) {
|
|||
|
|
ElMessage.warning('该数据已被其他数据引用,无法删除');
|
|||
|
|
showRelatedData(id);
|
|||
|
|
} else if (msg.includes('权限')) {
|
|||
|
|
ElMessage.error('您没有删除权限');
|
|||
|
|
} else {
|
|||
|
|
ElMessage.error(msg);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 批量操作显示详细结果
|
|||
|
|
const batchImport = async (file: File) => {
|
|||
|
|
try {
|
|||
|
|
const res = await importApi(file);
|
|||
|
|
ElMessage.success(`导入成功 ${res.data.successCount} 条,失败 ${res.data.failCount} 条`);
|
|||
|
|
if (res.data.failCount > 0) {
|
|||
|
|
showFailDetails(res.data.failList);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
ElMessage.error(getApiErrorMessage(error, '导入失败'));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. getApiErrorMessage 工具函数
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
/**
|
|||
|
|
* 从错误对象中提取错误信息
|
|||
|
|
* @param error - 错误对象
|
|||
|
|
* @param fallback - 默认错误信息
|
|||
|
|
* @returns 错误信息字符串
|
|||
|
|
*/
|
|||
|
|
export function getApiErrorMessage(error: any, fallback: string = '操作失败'): string {
|
|||
|
|
// 优先从 response.data 中获取
|
|||
|
|
if (error?.response?.data?.message) {
|
|||
|
|
return error.response.data.message;
|
|||
|
|
}
|
|||
|
|
if (error?.response?.data?.msg) {
|
|||
|
|
return error.response.data.msg;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从 Error.message 中获取
|
|||
|
|
if (error?.message && error.message !== 'Network Error') {
|
|||
|
|
return error.message;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 返回默认值
|
|||
|
|
return fallback;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**使用示例:**
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { getApiErrorMessage } from '/@/utils/request';
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await someApi();
|
|||
|
|
} catch (error) {
|
|||
|
|
// 使用后端返回的 message,如果没有则显示 '操作失败'
|
|||
|
|
ElMessage.error(getApiErrorMessage(error, '操作失败'));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 完整示例
|
|||
|
|
|
|||
|
|
### 示例 1:标准 CRUD 操作
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// ==================== API 层 ====================
|
|||
|
|
// src/api/user/index.ts
|
|||
|
|
|
|||
|
|
export function getUserList(params: ListParams) {
|
|||
|
|
return request({
|
|||
|
|
url: '/api/user/list',
|
|||
|
|
method: 'get',
|
|||
|
|
params
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function createUser(data: UserForm) {
|
|||
|
|
return request({
|
|||
|
|
url: '/api/user/create',
|
|||
|
|
method: 'post',
|
|||
|
|
data
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function updateUser(data: UserForm) {
|
|||
|
|
return request({
|
|||
|
|
url: '/api/user/update',
|
|||
|
|
method: 'put',
|
|||
|
|
data
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function deleteUser(id: string) {
|
|||
|
|
return request({
|
|||
|
|
url: `/api/user/delete/${id}`,
|
|||
|
|
method: 'delete'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 页面层 ====================
|
|||
|
|
// src/views/user/index.vue
|
|||
|
|
|
|||
|
|
import { getUserList, createUser, updateUser, deleteUser } from '/@/api/user';
|
|||
|
|
|
|||
|
|
// 获取列表
|
|||
|
|
const getList = async () => {
|
|||
|
|
loading.value = true;
|
|||
|
|
try {
|
|||
|
|
const res = await getUserList(queryParams);
|
|||
|
|
tableData.value = res.data?.list || [];
|
|||
|
|
total.value = res.data?.total || 0;
|
|||
|
|
} catch (error) {
|
|||
|
|
// 错误已由全局拦截器处理
|
|||
|
|
tableData.value = [];
|
|||
|
|
total.value = 0;
|
|||
|
|
} finally {
|
|||
|
|
loading.value = false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 新增/编辑
|
|||
|
|
const onSubmit = async () => {
|
|||
|
|
try {
|
|||
|
|
await formRef.value?.validate();
|
|||
|
|
|
|||
|
|
if (isEdit.value) {
|
|||
|
|
await updateUser(formData);
|
|||
|
|
ElMessage.success('修改成功');
|
|||
|
|
} else {
|
|||
|
|
await createUser(formData);
|
|||
|
|
ElMessage.success('添加成功');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
dialogVisible.value = false;
|
|||
|
|
getList();
|
|||
|
|
} catch (error) {
|
|||
|
|
// 错误已由全局拦截器处理
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 删除
|
|||
|
|
const onDelete = async (row: User) => {
|
|||
|
|
try {
|
|||
|
|
await ElMessageBox.confirm(`确定要删除用户"${row.name}"吗?`, '提示', {
|
|||
|
|
type: 'warning'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await deleteUser(row.id);
|
|||
|
|
ElMessage.success('删除成功');
|
|||
|
|
getList();
|
|||
|
|
} catch (error) {
|
|||
|
|
if (error === 'cancel') {
|
|||
|
|
// 用户取消操作
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
// 错误已由全局拦截器处理
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 示例 2:需要自定义错误处理
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// ==================== API 层 ====================
|
|||
|
|
// src/api/model/index.ts
|
|||
|
|
|
|||
|
|
export function listModel(params: ListParams) {
|
|||
|
|
return request({
|
|||
|
|
url: '/api/model/list',
|
|||
|
|
method: 'get',
|
|||
|
|
params,
|
|||
|
|
requestOptions: { errorMode: 'page' } // 页面自己处理错误
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function createModel(data: ModelForm) {
|
|||
|
|
return request({
|
|||
|
|
url: '/api/model/create',
|
|||
|
|
method: 'post',
|
|||
|
|
data,
|
|||
|
|
requestOptions: { errorMode: 'page' }
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 页面层 ====================
|
|||
|
|
// src/views/model/index.vue
|
|||
|
|
|
|||
|
|
import { getApiErrorMessage } from '/@/utils/request';
|
|||
|
|
import { listModel, createModel } from '/@/api/model';
|
|||
|
|
|
|||
|
|
// 获取列表
|
|||
|
|
const getList = async () => {
|
|||
|
|
loading.value = true;
|
|||
|
|
try {
|
|||
|
|
const res = await listModel(queryParams);
|
|||
|
|
tableData.value = res.data?.list || [];
|
|||
|
|
total.value = res.data?.total || 0;
|
|||
|
|
} catch (error) {
|
|||
|
|
// 使用 getApiErrorMessage 提取后端错误
|
|||
|
|
ElMessage.error(getApiErrorMessage(error, '获取列表失败'));
|
|||
|
|
tableData.value = [];
|
|||
|
|
total.value = 0;
|
|||
|
|
} finally {
|
|||
|
|
loading.value = false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 创建
|
|||
|
|
const onCreate = async () => {
|
|||
|
|
try {
|
|||
|
|
await createModel(formData);
|
|||
|
|
ElMessage.success('创建成功');
|
|||
|
|
dialogVisible.value = false;
|
|||
|
|
getList();
|
|||
|
|
} catch (error) {
|
|||
|
|
// 使用 getApiErrorMessage 提取后端错误
|
|||
|
|
ElMessage.error(getApiErrorMessage(error, '创建失败'));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 常见问题
|
|||
|
|
|
|||
|
|
### Q1: 什么时候使用 `errorMode: 'page'`?
|
|||
|
|
|
|||
|
|
**A:** 仅在以下情况使用:
|
|||
|
|
- 需要根据不同错误码做不同处理
|
|||
|
|
- 需要自定义错误提示格式
|
|||
|
|
- 需要在错误后执行特殊业务逻辑
|
|||
|
|
- 需要显示详细的错误信息
|
|||
|
|
|
|||
|
|
**大部分情况(95%)使用默认的全局错误处理即可。**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Q2: 为什么不能在页面写固定的错误提示?
|
|||
|
|
|
|||
|
|
**A:** 因为全局拦截器已经显示了错误,页面再显示会导致**重复提示**:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// ❌ 错误写法 - 会重复提示
|
|||
|
|
try {
|
|||
|
|
await getList();
|
|||
|
|
} catch (error) {
|
|||
|
|
ElMessage.error('获取列表失败'); // 全局拦截器已经显示过了
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 正确写法
|
|||
|
|
try {
|
|||
|
|
await getList();
|
|||
|
|
} catch (error) {
|
|||
|
|
// 错误已由全局拦截器处理
|
|||
|
|
tableData.value = [];
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Q3: 如何显示后端返回的错误信息?
|
|||
|
|
|
|||
|
|
**A:** 有两种方式:
|
|||
|
|
|
|||
|
|
1. **使用默认全局处理(推荐)**
|
|||
|
|
```typescript
|
|||
|
|
// API 层不设置 errorMode
|
|||
|
|
export function getList() {
|
|||
|
|
return request({ url: '/api/list', method: 'get' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 页面层不写错误提示
|
|||
|
|
try {
|
|||
|
|
await getList();
|
|||
|
|
} catch (error) {
|
|||
|
|
// 全局拦截器会自动显示后端的 message
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **使用 getApiErrorMessage**
|
|||
|
|
```typescript
|
|||
|
|
// API 层设置 errorMode: 'page'
|
|||
|
|
export function getList() {
|
|||
|
|
return request({
|
|||
|
|
url: '/api/list',
|
|||
|
|
method: 'get',
|
|||
|
|
requestOptions: { errorMode: 'page' }
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 页面层使用 getApiErrorMessage
|
|||
|
|
import { getApiErrorMessage } from '/@/utils/request';
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await getList();
|
|||
|
|
} catch (error) {
|
|||
|
|
ElMessage.error(getApiErrorMessage(error, '获取列表失败'));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Q4: catch 块应该写什么?
|
|||
|
|
|
|||
|
|
**A:** 根据场景决定:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 场景 1:只需要清空数据
|
|||
|
|
try {
|
|||
|
|
const res = await getList();
|
|||
|
|
tableData.value = res.data?.list || [];
|
|||
|
|
} catch (error) {
|
|||
|
|
// 错误已由全局拦截器处理
|
|||
|
|
tableData.value = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 场景 2:需要重置状态
|
|||
|
|
try {
|
|||
|
|
await uploadFile(file);
|
|||
|
|
} catch (error) {
|
|||
|
|
// 错误已由全局拦截器处理
|
|||
|
|
resetUploadState();
|
|||
|
|
fileList.value = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 场景 3:不需要任何处理
|
|||
|
|
try {
|
|||
|
|
await deleteItem(id);
|
|||
|
|
ElMessage.success('删除成功');
|
|||
|
|
getList();
|
|||
|
|
} catch (error) {
|
|||
|
|
// 错误已由全局拦截器处理
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 场景 4:需要自定义错误处理(API 设置了 errorMode: 'page')
|
|||
|
|
try {
|
|||
|
|
await saveData();
|
|||
|
|
ElMessage.success('保存成功');
|
|||
|
|
} catch (error) {
|
|||
|
|
ElMessage.error(getApiErrorMessage(error, '保存失败'));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Q5: 如何处理用户取消操作?
|
|||
|
|
|
|||
|
|
**A:** 使用 `if (error === 'cancel')` 判断:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const onDelete = async (row: any) => {
|
|||
|
|
try {
|
|||
|
|
await ElMessageBox.confirm('确定要删除吗?', '提示', {
|
|||
|
|
type: 'warning'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await deleteApi(row.id);
|
|||
|
|
ElMessage.success('删除成功');
|
|||
|
|
getList();
|
|||
|
|
} catch (error) {
|
|||
|
|
if (error === 'cancel') {
|
|||
|
|
// 用户取消操作,不显示错误
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
// 其他错误已由全局拦截器处理
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Q6: 如何处理表单验证失败?
|
|||
|
|
|
|||
|
|
**A:** 表单验证失败不会进入 catch,无需特殊处理:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const onSubmit = async () => {
|
|||
|
|
try {
|
|||
|
|
// 表单验证失败会直接 return,不会进入 catch
|
|||
|
|
await formRef.value?.validate();
|
|||
|
|
|
|||
|
|
await saveApi(formData);
|
|||
|
|
ElMessage.success('保存成功');
|
|||
|
|
} catch (error) {
|
|||
|
|
// 这里只会捕获 API 请求错误
|
|||
|
|
// 错误已由全局拦截器处理
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 快速检查清单
|
|||
|
|
|
|||
|
|
写新接口时,检查以下几点:
|
|||
|
|
|
|||
|
|
- [ ] **API 层**:是否需要设置 `errorMode: 'page'`?
|
|||
|
|
- 大部分情况不需要
|
|||
|
|
- 只在需要自定义错误处理时设置
|
|||
|
|
|
|||
|
|
- [ ] **页面层**:catch 块是否正确?
|
|||
|
|
- ✅ 只处理业务逻辑(数据清空、状态重置)
|
|||
|
|
- ❌ 不写固定的 `ElMessage.error('xxx失败')`
|
|||
|
|
- ✅ 如果 API 设置了 `errorMode: 'page'`,使用 `getApiErrorMessage`
|
|||
|
|
|
|||
|
|
- [ ] **是否避免了重复提示?**
|
|||
|
|
- ✅ 全局拦截器 OR 页面 getApiErrorMessage
|
|||
|
|
- ❌ 全局拦截器 + 页面固定提示
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 工具函数导入
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 导入错误提取工具
|
|||
|
|
import { getApiErrorMessage } from '/@/utils/request';
|
|||
|
|
|
|||
|
|
// 使用示例
|
|||
|
|
try {
|
|||
|
|
await someApi();
|
|||
|
|
} catch (error) {
|
|||
|
|
ElMessage.error(getApiErrorMessage(error, '操作失败'));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
### 核心规则
|
|||
|
|
|
|||
|
|
1. **默认使用全局错误处理**(95% 的场景)
|
|||
|
|
- API 层不设置 `errorMode`
|
|||
|
|
- 页面层 catch 不写固定错误提示
|
|||
|
|
|
|||
|
|
2. **特殊场景使用页面自定义处理**(5% 的场景)
|
|||
|
|
- API 层设置 `errorMode: 'page'`
|
|||
|
|
- 页面层使用 `getApiErrorMessage`
|
|||
|
|
|
|||
|
|
3. **避免重复提示**
|
|||
|
|
- 全局拦截器已经处理了错误
|
|||
|
|
- 页面不要再显示固定错误
|
|||
|
|
|
|||
|
|
### 记住这个公式
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
全局错误处理(默认) = 不设置 errorMode + catch 不写错误提示
|
|||
|
|
页面自定义处理(特殊) = errorMode: 'page' + getApiErrorMessage
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**文档版本:** v1.0
|
|||
|
|
**最后更新:** 2026-05-11
|
|||
|
|
**维护者:** 开发团队
|