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,代码未展开计数器实现。
- // src/utils/request.js
- import axios from 'axios';
- import { useUserStore } from '@/stores/user'; // Pinia 用户状态
- import { ElMessage } from 'element-plus'; // UI 框架提示
- // 1. 创建 axios 实例
- const service = axios.create({
- baseURL: import.meta.env.VITE_BASE_API, // 环境变量
- timeout: 10000,
- });
- // 2. 请求拦截器
- service.interceptors.request.use(
- (config) => {
- // 开启 Loading(配合 Element Plus 的 v-loading 指令全局控制)
- // 这里可以添加一个全局 loading 计数器
- // 自动添加 Token
- const userStore = useUserStore();
- if (userStore.token) {
- config.headers.Authorization = `Bearer ${userStore.token}`;
- }
- // 处理特殊请求头(如 FormData)
- if (config.data instanceof FormData) {
- config.headers['Content-Type'] = 'multipart/form-data';
- }
- return config;
- },
- (error) => {
- // 请求配置出错(如 URL 格式错误)
- return Promise.reject(error);
- }
- );
复制代码
二、响应拦截器
响应拦截器在服务器返回后、业务组件处理前统一解包。原文约定 code===200 表示成功,直接返回 data;否则 ElMessage.error(message || '请求失败') 并 reject。网络错误按 error.response.status 分支:401 清 token 并 window.location.href='/login';403 提示没有权限;其他提示 error.response.data?.message 或系统错误。timeout 提示请求超时,其他提示网络异常。原文注释中有关闭 Loading 的位置,未展开实现。
- // 3. 响应拦截器
- service.interceptors.response.use(
- (response) => {
- // 关闭 Loading
- // 后端约定 code===200 为成功
- const { code, message, data } = response.data;
- if (code === 200) {
- return data; // 直接返回业务数据,简化调用
- } else {
- // 业务错误统一处理
- ElMessage.error(message || '请求失败');
- return Promise.reject(new Error(message));
- }
- },
- (error) => {
- // 网络超时、服务器 500 等错误
- if (error.response) {
- const { status } = error.response;
- if (status === 401) {
- // Token 失效:清除状态并跳转登录
- const userStore = useUserStore();
- userStore.logout();
- window.location.href = '/login';
- } else if (status === 403) {
- ElMessage.error('没有权限访问');
- } else {
- ElMessage.error(error.response.data?.message || '系统错误');
- }
- } else if (error.message.includes('timeout')) {
- ElMessage.error('请求超时,请稍后重试');
- } else {
- ElMessage.error('网络异常,请检查连接');
- }
- return Promise.reject(error);
- }
- );
- export default service;
复制代码
三、组件级渲染错误拦截
Vue 的 onErrorCaptured 可在父组件捕获后代组件异常,防止局部错误导致白屏。原文示例返回 false,阻止错误继续向上传播,并用 ElMessage 提示页面渲染异常。
- <template>
- <ChildComponent />
- </template>
- <script setup>
- import { onErrorCaptured } from 'vue';
- import { ElMessage } from 'element-plus';
- // 捕获后代组件的错误
- onErrorCaptured((err, instance, info) => {
- console.error('子组件错误:', err, info);
- ElMessage.error('页面渲染出现异常');
- // 返回 false 阻止错误继续向上传播
- return false;
- });
- </script>
复制代码
四、应用全局错误拦截
main.js 中 app.config.errorHandler 捕获未被组件处理的错误,原文示例打印错误并预留上报 Sentry 或日志平台的注释。
- // main.js
- import { createApp } from 'vue';
- import App from './App.vue';
- const app = createApp(App);
- app.config.errorHandler = (err, vm, info) => {
- console.error('全局捕获错误:', err, info);
- // 上报到 Sentry 或自己的日志平台
- // reportErrorToServer(err);
- };
- app.mount('#app');
复制代码
五、路由导航守卫
Axios 拦截器管接口请求,Vue Router 的导航守卫管页面跳转。原文在 router/index.js 的 beforeEach 中根据 userStore.token 判断登录状态,to.meta.requiresAuth 为真时未登录跳登录页并带 redirect,已登录访问 /login 则跳首页。
- // src/router/index.js
- import { createRouter, createWebHistory } from 'vue-router';
- import { useUserStore } from '@/stores/user';
- const router = createRouter({
- history: createWebHistory(),
- routes: [
- { path: '/login', component: () => import('@/views/Login.vue') },
- { path: '/dashboard', component: () => import('@/views/Dashboard.vue'), meta: { requiresAuth: true } },
- ],
- });
- // 全局前置守卫
- router.beforeEach((to, from, next) => {
- const userStore = useUserStore();
- const isLoggedIn = !!userStore.token;
- if (to.meta.requiresAuth) {
- if (isLoggedIn) {
- next(); // 已登录,放行
- } else {
- next({ path: '/login', query: { redirect: to.fullPath } }); // 未登录,跳转登录
- }
- } else {
- // 如果已登录且访问登录页,直接跳转首页
- if (to.path === '/login' && isLoggedIn) {
- next({ path: '/' });
- } else {
- next();
- }
- }
- });
- export default router;
复制代码
六、组件中调用封装请求
业务组件引入 request 后,响应拦截器已返回 data,所以 request.get('/user/info') 得到的就是业务数据,不用再解构 response.data。catch 中原文只打印请求失败,因为错误提示已由拦截器完成,组件可专注特殊逻辑。
- <script setup>
- import request from '@/utils/request';
- const fetchData = async () => {
- try {
- // 直接拿到 data,无需再解构
- const res = await request.get('/user/info');
- console.log(res);
- } catch (error) {
- // 拦截器已经提示了错误,这里只需处理特殊逻辑
- console.log('请求失败');
- }
- };
- </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 计数器与日志上报以注释预留,未展开具体实现,实际项目需按这些位置补齐。 |