查看: 320|回复: 3

Vue3 Axios拦截器封装与全局错误处理实践

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
Vue 3 项目里的拦截器通常指 Axios 请求/响应拦截器,它在请求发出前和响应进入业务代码前做统一处理,相当于前端与后端之间的中间件层。原文将作用归纳为请求拦截、响应拦截、全局错误处理和可维护性提升,并补充 Vue Router 导航守卫用于页面级权限控制。把这些逻辑集中到实例和入口文件后,业务组件不必重复写鉴权、错误提示和数据解包代码。

一、请求拦截器
请求拦截器在发送前统一处理:从 localStorage 或 Pinia 读取 Token 写入 Header,设置超时和 Content-Type,发出请求时触发 Loading,对敏感参数做预处理。原文以 Pinia 的 useUserStore 管理 token,在 config.headers.Authorization 中拼接 Bearer。若 data 为 FormData,则将 Content-Type 设为 multipart/form-data。示例还保留了全局 Loading 计数器的注释位置,原文说明可配合 ref 管理 Loading,代码未展开计数器实现。
  1. // src/utils/request.js
  2. import axios from 'axios';
  3. import { useUserStore } from '@/stores/user'; // Pinia 用户状态
  4. import { ElMessage } from 'element-plus'; // UI 框架提示
  5. // 1. 创建 axios 实例
  6. const service = axios.create({
  7.   baseURL: import.meta.env.VITE_BASE_API, // 环境变量
  8.   timeout: 10000,
  9. });
  10. // 2. 请求拦截器
  11. service.interceptors.request.use(
  12.   (config) => {
  13.     // 开启 Loading(配合 Element Plus 的 v-loading 指令全局控制)
  14.     // 这里可以添加一个全局 loading 计数器
  15.     // 自动添加 Token
  16.     const userStore = useUserStore();
  17.     if (userStore.token) {
  18.       config.headers.Authorization = `Bearer ${userStore.token}`;
  19.     }
  20.     // 处理特殊请求头(如 FormData)
  21.     if (config.data instanceof FormData) {
  22.       config.headers['Content-Type'] = 'multipart/form-data';
  23.     }
  24.     return config;
  25.   },
  26.   (error) => {
  27.     // 请求配置出错(如 URL 格式错误)
  28.     return Promise.reject(error);
  29.   }
  30. );
复制代码

二、响应拦截器
响应拦截器在服务器返回后、业务组件处理前统一解包。原文约定 code===200 表示成功,直接返回 data;否则 ElMessage.error(message || '请求失败') 并 reject。网络错误按 error.response.status 分支:401 清 token 并 window.location.href='/login';403 提示没有权限;其他提示 error.response.data?.message 或系统错误。timeout 提示请求超时,其他提示网络异常。原文注释中有关闭 Loading 的位置,未展开实现。
  1. // 3. 响应拦截器
  2. service.interceptors.response.use(
  3.   (response) => {
  4.     // 关闭 Loading
  5.     // 后端约定 code===200 为成功
  6.     const { code, message, data } = response.data;
  7.     if (code === 200) {
  8.       return data; // 直接返回业务数据,简化调用
  9.     } else {
  10.       // 业务错误统一处理
  11.       ElMessage.error(message || '请求失败');
  12.       return Promise.reject(new Error(message));
  13.     }
  14.   },
  15.   (error) => {
  16.     // 网络超时、服务器 500 等错误
  17.     if (error.response) {
  18.       const { status } = error.response;
  19.       if (status === 401) {
  20.         // Token 失效:清除状态并跳转登录
  21.         const userStore = useUserStore();
  22.         userStore.logout();
  23.         window.location.href = '/login';
  24.       } else if (status === 403) {
  25.         ElMessage.error('没有权限访问');
  26.       } else {
  27.         ElMessage.error(error.response.data?.message || '系统错误');
  28.       }
  29.     } else if (error.message.includes('timeout')) {
  30.       ElMessage.error('请求超时,请稍后重试');
  31.     } else {
  32.       ElMessage.error('网络异常,请检查连接');
  33.     }
  34.     return Promise.reject(error);
  35.   }
  36. );
  37. export default service;
复制代码

三、组件级渲染错误拦截
Vue 的 onErrorCaptured 可在父组件捕获后代组件异常,防止局部错误导致白屏。原文示例返回 false,阻止错误继续向上传播,并用 ElMessage 提示页面渲染异常。
  1. <template>
  2.   <ChildComponent />
  3. </template>
  4. <script setup>
  5. import { onErrorCaptured } from 'vue';
  6. import { ElMessage } from 'element-plus';
  7. // 捕获后代组件的错误
  8. onErrorCaptured((err, instance, info) => {
  9.   console.error('子组件错误:', err, info);
  10.   ElMessage.error('页面渲染出现异常');
  11.   // 返回 false 阻止错误继续向上传播
  12.   return false;
  13. });
  14. </script>
复制代码

四、应用全局错误拦截
main.js 中 app.config.errorHandler 捕获未被组件处理的错误,原文示例打印错误并预留上报 Sentry 或日志平台的注释。
  1. // main.js
  2. import { createApp } from 'vue';
  3. import App from './App.vue';
  4. const app = createApp(App);
  5. app.config.errorHandler = (err, vm, info) => {
  6.   console.error('全局捕获错误:', err, info);
  7.   // 上报到 Sentry 或自己的日志平台
  8.   // reportErrorToServer(err);
  9. };
  10. app.mount('#app');
复制代码

五、路由导航守卫
Axios 拦截器管接口请求,Vue Router 的导航守卫管页面跳转。原文在 router/index.js 的 beforeEach 中根据 userStore.token 判断登录状态,to.meta.requiresAuth 为真时未登录跳登录页并带 redirect,已登录访问 /login 则跳首页。
  1. // src/router/index.js
  2. import { createRouter, createWebHistory } from 'vue-router';
  3. import { useUserStore } from '@/stores/user';
  4. const router = createRouter({
  5.   history: createWebHistory(),
  6.   routes: [
  7.     { path: '/login', component: () => import('@/views/Login.vue') },
  8.     { path: '/dashboard', component: () => import('@/views/Dashboard.vue'), meta: { requiresAuth: true } },
  9.   ],
  10. });
  11. // 全局前置守卫
  12. router.beforeEach((to, from, next) => {
  13.   const userStore = useUserStore();
  14.   const isLoggedIn = !!userStore.token;
  15.   if (to.meta.requiresAuth) {
  16.     if (isLoggedIn) {
  17.       next(); // 已登录,放行
  18.     } else {
  19.       next({ path: '/login', query: { redirect: to.fullPath } }); // 未登录,跳转登录
  20.     }
  21.   } else {
  22.     // 如果已登录且访问登录页,直接跳转首页
  23.     if (to.path === '/login' && isLoggedIn) {
  24.       next({ path: '/' });
  25.     } else {
  26.       next();
  27.     }
  28.   }
  29. });
  30. export default router;
复制代码

六、组件中调用封装请求
业务组件引入 request 后,响应拦截器已返回 data,所以 request.get('/user/info') 得到的就是业务数据,不用再解构 response.data。catch 中原文只打印请求失败,因为错误提示已由拦截器完成,组件可专注特殊逻辑。
  1. <script setup>
  2. import request from '@/utils/request';
  3. const fetchData = async () => {
  4.   try {
  5.     // 直接拿到 data,无需再解构
  6.     const res = await request.get('/user/info');
  7.     console.log(res);
  8.   } catch (error) {
  9.     // 拦截器已经提示了错误,这里只需处理特殊逻辑
  10.     console.log('请求失败');
  11.   }
  12. };
  13. </script>
复制代码

关键点
- Axios 请求拦截器:request.js,加 Token、处理 FormData、预留 Loading 控制。
- Axios 响应拦截器:request.js,按 code 解包 data,统一错误提示,401 清状态并跳转。
- 组件错误拦截:父组件 onErrorCaptured,捕获子组件渲染异常,返回 false 阻止传播。
- 全局错误拦截:main.js 的 app.config.errorHandler,兜底未捕获错误并可上报。
- 路由拦截:router/index.js 的 beforeEach,做登录鉴权和页面权限控制。
从原文可推导,拦截器的价值在于把鉴权、错误提示、数据格式化从业务组件抽离,一次配置让整个项目请求生效,减少重复代码;同时 Axios 拦截器与路由守卫结合,可分别覆盖接口层和页面层的权限与交互处理。原文示例使用 Pinia 的 useUserStore 读取 token;Loading 计数器与日志上报以注释预留,未展开具体实现,实际项目需按这些位置补齐。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Vue3 Axios拦截器封装与全局错误处理实践

感谢分享,整体思路很清楚,把请求拦截、响应拦截、业务错误和网络错误分层处理,再配合组件级 onErrorCaptured 防白屏,确实能减少业务组件里的重复代码。一个小建议是 Loading 计数器最好在请求成功、业务报错和网络异常三条路径都能关闭,避免异常时一直卡着;另外 FormData 那段手动设置 Content-Type 为 multipart/form-data 有时会缺少 boundary,交给浏览器自动处理可能更稳妥。401 跳转登录前也可以判断一下当前是否已在登录页,减少重复跳转。组件级错误拦截那段好像贴到一半,期待后续补全。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Vue3 Axios拦截器封装与全局错误处理实践

这套封装思路很清晰,把鉴权、Loading、错误提示和数据解包都收到请求层,业务组件确实会干净很多,尤其是 401 统一清状态跳登录、403 单独提示权限,这些细节很实用。 有几个小地方可以再打磨一下:请求拦截器里手动给 FormData 设置 Content-Type 为 multipart/form-data 时,最好确认不会覆盖浏览器自动生成的 boundary,通常让浏览器自己带更稳妥;在拦截器里调用 useUserStore 要确保 Pinia 已经安装,避免模块初始化阶段提前调用;超时判断用 error.message.includes('timeout') 有点依赖文案,可以结合 error.code 判断更稳。响应拦截器直接返回 data 很方便,但配合 TypeScript 时建议把 request 方法封装成泛型,调用处类型会更清楚。 组件级 onErrorCaptured 返回 false 能阻止错误继续向上传播,适合局部兜底,不过生产环境最好同时把错误上报,避免异常被完全吞掉。首帖最后的示例似乎截断了,期待补全。整体来说这套方案可维护性不错,适合中后台项目直接参考。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Vue3 Axios拦截器封装与全局错误处理实践

这篇整理得挺清晰的,把鉴权、错误提示和数据解包集中到 Axios 实例里,确实能少写很多重复代码,业务组件也会干净不少。几个实践点想补充一下:请求拦截器里用 useUserStore 时,要确保 Pinia 已经安装并激活,不然在模块初始化或拦截器执行时机上可能拿不到 store;FormData 那块其实不建议手动设置 Content-Type,让浏览器自动带 boundary 更稳妥,手动写 multipart/form-data 有时会出问题。响应拦截器直接返回 data 很方便,但 TS 项目里最好把 service 的返回类型和调用方预期统一一下,避免类型对不上。401 用 window.location.href 跳登录简单直接,不过会整页刷新,如果项目里路由状态比较重要,也可以考虑走 router。还有 Loading 计数器既然留了位置,记得在成功和失败分支都做减计数,最好用 finally 兜底,不然异常时容易卡住。onErrorCaptured 返回 false 阻止继续传播是合理的,但生产环境里建议同时做错误上报或日志记录,方便排查。整体思路很实用,适合作为项目里的基础请求层模板。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-31 06:48 , Processed in 0.032799 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部