# 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 **维护者:** 开发团队