Skip to content

使用

获取请求的基础路径

ts
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';

const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);

isHttpProxy 用于判断是否使用代理,baseURL 表示环境文件中 VITE_SERVICE_BASE_URL 的值,otherBaseURL 用于其他请求,通过 VITE_OTHER_SERVICE_BASE_URL 配置。

getServiceBaseURL 方法用于获取请求的基础路径,根据环境变量 import.meta.envisHttpProxy 判断是否使用代理。

导入请求实例创建函数

可以选择 createRequest 或者 createFlatRequest 创建请求实例。

ts
import { createFlatRequest, createRequest } from '@sa/axios';

具体见下方的案例

确认创建请求实例函数的范型参数

  • 请求结果的数据类型: App.Service.Response,默认请求用该类型,请根据自己的后端返回数据类型进行修改

    因为不同的类型会影响到 RequestOption 中的 isBackendSuccesstransformBackendResponse 以及错误信息的字段的参数类型

    其他请求实例的数据类型请自行再定义新的类型声明

  • 请求实例的状态类型: InstanceState,用于存储请求实例的一些状态,例如:表示是否正在刷新 token, 是否有错误弹窗展示等,根据自己的业务需求自行定义状态类型

创建请求实例 request

示例

ts
import type { AxiosResponse } from 'axios';
import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios';
import { useAuthStore } from '@/store/modules/auth';
import { localStg } from '@/utils/storage';
import { getServiceBaseURL } from '@/utils/service';
import { $t } from '@/locales';
import { handleRefreshToken } from './shared';

const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);

interface InstanceState {
  /** 是否有请求正在执行刷新token */
  isRefreshingToken: boolean;
}

export const request = createFlatRequest<App.Service.Response, InstanceState>(
  {
    baseURL,
    headers: {
      apifoxToken: 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2'
    }
  },
  {
    async onRequest(config) {
      const { headers } = config;

      // 添加 token 到请求头
      const token = localStg.get('token');
      const Authorization = token ? `Bearer ${token}` : null;
      Object.assign(headers, { Authorization });

      return config;
    },
    isBackendSuccess(response) {
      // 当后端返回的 code 为 "0000"(默认) 时,表示请求成功
      // 如果需要修改这个逻辑,可以在 `.env` 文件中修改 `VITE_SERVICE_SUCCESS_CODE`
      return response.data.code === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
    },
    async onBackendFail(response, instance) {
      const authStore = useAuthStore();

      function handleLogout() {
        authStore.resetStore();
      }

      function logoutAndCleanup() {
        handleLogout();
        window.removeEventListener('beforeunload', handleLogout);
      }

      // 当后端返回的 code 在 `logoutCodes` 中时,表示用户需要退出登录
      const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
      if (logoutCodes.includes(response.data.code)) {
        handleLogout();
        return null;
      }

      // 当后端返回的 code 在 `modalLogoutCodes` 中时,表示用户需要退出登录,通过弹窗形式提醒
      const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
      if (modalLogoutCodes.includes(response.data.code)) {
        // 防止用户刷新页面
        window.addEventListener('beforeunload', handleLogout);

        window.$dialog?.error({
          title: 'Error',
          content: response.data.msg,
          positiveText: $t('common.confirm'),
          maskClosable: false,
          onPositiveClick() {
            logoutAndCleanup();
          },
          onClose() {
            logoutAndCleanup();
          }
        });

        return null;
      }

      // 当后端返回的 code 在 `expiredTokenCodes` 中时,表示 token 过期,需要刷新 token
      // `refreshToken` 接口不能返回 `expiredTokenCodes` 中的错误码,否则会死循环,应该返回 `logoutCodes` 或 `modalLogoutCodes`
      const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
      if (expiredTokenCodes.includes(response.data.code) && !request.state.isRefreshingToken) {
        request.state.isRefreshingToken = true;

        const refreshConfig = await handleRefreshToken(response.config);

        request.state.isRefreshingToken = false;

        if (refreshConfig) {
          return instance.request(refreshConfig) as Promise<AxiosResponse>;
        }
      }

      return null;
    },
    transformBackendResponse(response) {
      return response.data.data;
    },
    onError(error) {
      // 当请求失败时,可以在这里处理显示错误信息的逻辑

      let message = error.message;
      let backendErrorCode = '';

      // 获取后端返回的错误信息和错误码
      if (error.code === BACKEND_ERROR_CODE) {
        message = error.response?.data?.msg || message;
        backendErrorCode = error.response?.data?.code || '';
      }

      // 错误信息通过弹窗形式显示
      const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
      if (modalLogoutCodes.includes(backendErrorCode)) {
        return;
      }

      // 当 token 过期时,刷新 token 并重试请求,所以不需要显示错误信息
      const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
      if (expiredTokenCodes.includes(backendErrorCode)) {
        return;
      }

      window.$message?.error?.(message);
    }
  }
);

使用请求实例

ts
/**
 * 登录
 *
 * @param loginRes 登录参数
 */
export function fetchLogin(loginRes: Api.Auth.LoginReq) {
  return request<Api.Auth.LoginToken>({
    url: '/auth/accounts/login',
    method: 'post',
    data: loginRes
  });
}

需要定义请求成功后的数据类型,例如:Api.Auth.LoginToken,并传入 request 函数中。

  • 如果 request 函数是通过 createFlatRequest 创建的,请求成功后的数据类型会被包装在一个对象中,可以通过 data 字段获取。
ts
async function login() {
  const { error, data } = await fetchLogin({ username: 'admin', password: 'admin' });

  if (!error) {
    // 请求成功
  }
}
  • 如果 request 函数是通过 createRequest 创建的,请求成功后的数据类型会直接返回,不会被包装在对象中。
ts
async function login() {
  const data = await fetchLogin({ username: 'admin', password: 'admin' });

  if (data) {
    // 请求成功
  }
}

根据 MIT 许可证发布