You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Quasar Vite Vue3 TS项目boot/axios.ts迁移与配置问询

迁移Quasar CLI到Vite:Axios与Vuex集成及拦截器配置方案

问题背景

从Quasar CLI迁移到Quasar Vite(版本^2.16.0,@quasar/app-vite ^2.0.0),在处理boot/axios.ts时遇到集成与拦截器逻辑适配问题,具体场景如下:

旧Quasar CLI环境的Axios启动文件

import { boot } from 'quasar/wrappers';
import axios, { AxiosInstance } from 'axios';
import { IRootState } from 'src/store';

const api = axios.create({
  baseURL: process.env.API_URL,
});

export default boot(({ app, store }) => {
  app.config.globalProperties.$axios = axios;
  app.config.globalProperties.$api = api;

  api.interceptors.request.use((config) => {
    const token = (store.state as IRootState).auth.currentUserData.accessToken;
    if (token) {
      config.headers.Authorization = token;
    }
    return config;
  });

  // Additional interceptors...
});

export { api };

新项目目录结构

src/
  boot/
    axios.ts
  store/
    index.ts
  router/
    index.ts

核心疑问

  1. 如何在Quasar Vite项目中正确配置boot/axios.ts以集成Vuex store?
  2. 在此新环境下如何处理Axios拦截器(含请求、响应拦截及token刷新逻辑)?
  3. 当前方案是否符合Quasar Vite的最佳实践?

已尝试操作

  • 显式导入Vuex store:
import { defineBoot } from '#q-app/wrappers';
import { useStore } from 'src/store';
  • 更新Axios启动文件,移除boot函数的store参数,直接访问Vuex状态:
export default defineBoot(({ app }) => {
  const store = useStore(); 
  app.config.globalProperties.$axios = axios;
  app.config.globalProperties.$api = api;

  api.interceptors.request.use((config) => {
    const token = store.state.auth.currentUserData.accessToken;
    if (token) {
      config.headers.Authorization = token;
    }
    return config;
  });
});
  • 尝试添加请求与响应拦截器处理API认证及token刷新逻辑,包括检测401错误、发起刷新token请求并重试原请求。

预期目标

  1. Axios与Store集成:实现无缝集成,确保API调用使用store中的access token;可在Vue组件全局使用this.$axios和this.$api。
  2. Token刷新逻辑生效:收到401响应时自动刷新token并重试原请求;仅当refresh token无效时跳转登录页。
  3. 符合Quasar最佳实践。

解决方案

1. Axios与Vuex Store的正确集成

Quasar Vite中defineBoot不再自动传入store,需直接导入store实例(useStore()是Vue组件钩子,在boot文件中直接调用会导致状态获取异常)。

假设你的store/index.ts导出了store实例:

// src/store/index.ts
import { createStore } from 'vuex';
import auth from './modules/auth';

export default createStore({
  modules: {
    auth,
  },
});

// 导出类型用于类型提示(可选)
export type IRootState = ReturnType<typeof store.state>;

修改boot/axios.ts的Store导入方式:

import { defineBoot } from '#q-app/wrappers';
import axios, { AxiosInstance } from 'axios';
import store from 'src/store';
import type { IRootState } from 'src/store';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL, // Vite环境变量需用import.meta.env
});

export default defineBoot(({ app }) => {
  // 挂载到全局属性,支持组件中使用this.$axios/this.$api
  app.config.globalProperties.$axios = axios;
  app.config.globalProperties.$api = api;

  // 请求拦截器:自动添加token
  api.interceptors.request.use((config) => {
    const rootState = store.state as IRootState;
    const token = rootState.auth.currentUserData.accessToken;
    if (token) {
      config.headers.Authorization = `Bearer ${token}`; // 通常需加Bearer前缀
    }
    return config;
  });
});

export { api };

2. 完整拦截器实现(含Token刷新逻辑)

处理401自动刷新token,需避免重复刷新请求,用锁标记+请求队列实现:

import { defineBoot } from '#q-app/wrappers';
import axios, { AxiosInstance, AxiosError } from 'axios';
import store from 'src/store';
import router from 'src/router';
import type { IRootState } from 'src/store';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
});

// 标记是否正在刷新token
let isRefreshing = false;
// 存储等待重试的请求队列
let refreshQueue: Array<(token: string) => void> = [];

export default defineBoot(({ app }) => {
  app.config.globalProperties.$axios = axios;
  app.config.globalProperties.$api = api;

  // 请求拦截器
  api.interceptors.request.use((config) => {
    const rootState = store.state as IRootState;
    const token = rootState.auth.currentUserData.accessToken;
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  }, (error) => {
    return Promise.reject(error);
  });

  // 响应拦截器
  api.interceptors.response.use((response) => {
    return response;
  }, async (error: AxiosError) => {
    const originalRequest = error.config as any;
    const rootState = store.state as IRootState;
    const refreshToken = rootState.auth.currentUserData.refreshToken;

    // 处理401未授权,且不是刷新token的请求本身
    if (error.response?.status === 401 && !originalRequest._retry && refreshToken) {
      if (isRefreshing) {
        // 正在刷新,加入队列等待
        return new Promise((resolve) => {
          refreshQueue.push((token: string) => {
            originalRequest.headers.Authorization = `Bearer ${token}`;
            resolve(api(originalRequest));
          });
        });
      }

      originalRequest._retry = true;
      isRefreshing = true;

      try {
        // 调用刷新token接口
        const refreshResponse = await axios.post(`${import.meta.env.VITE_API_URL}/auth/refresh`, {
          refreshToken,
        });
        const newAccessToken = refreshResponse.data.accessToken;
        const newRefreshToken = refreshResponse.data.refreshToken;

        // 更新store中的token
        await store.dispatch('auth/updateTokens', {
          accessToken: newAccessToken,
          refreshToken: newRefreshToken,
        });

        // 重发队列中的请求
        refreshQueue.forEach((cb) => cb(newAccessToken));
        refreshQueue = [];

        // 重试原请求
        originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
        return api(originalRequest);
      } catch (refreshError) {
        // 刷新失败,跳转登录页,清空store
        await store.dispatch('auth/logout');
        router.push('/login');
        return Promise.reject(refreshError);
      } finally {
        isRefreshing = false;
      }
    }

    return Promise.reject(error);
  });
});

export { api };

3. Quasar Vite项目Axios启动文件的最佳实践

  • 环境变量:Vite环境变量需以VITE_为前缀,使用import.meta.env.VITE_XXX访问,替代原process.env。
  • Store导入:直接导入store实例而非使用useStore(),boot文件属于应用初始化阶段,不处于Vue组件上下文,useStore()会失效。
  • 全局挂载:保留app.config.globalProperties挂载,同时推荐在组合式API中直接通过import { api } from 'src/boot/axios'使用。
  • 拦截器解耦:复杂的拦截逻辑可抽离为单独工具函数(如src/utils/auth.ts),避免boot文件过于臃肿。
  • 类型支持:为Vue全局属性添加类型声明,避免组件中使用this.$api时出现类型错误,可在src/typings/vue.d.ts中添加:
import type { AxiosInstance } from 'axios';

declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $axios: AxiosInstance;
    $api: AxiosInstance;
  }
}

内容的提问来源于stack exchange,提问作者Bhavini Chauhan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 04:27:33