Files
vue3-element-admin/src/utils/request.ts
2025-12-20 21:56:48 +08:00

118 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from "axios";
import qs from "qs";
import { ApiCodeEnum } from "@/enums/api";
import { AuthStorage, redirectToLogin } from "@/utils/auth";
import { STORAGE_KEYS } from "@/constants";
import { useTokenRefresh } from "@/composables/auth/useTokenRefresh";
import { authConfig } from "@/settings";
// 初始化token刷新组合式函数
const { refreshTokenAndRetry } = useTokenRefresh();
/**
* 创建 HTTP 请求实例
*/
const httpRequest = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000,
headers: { "Content-Type": "application/json;charset=utf-8" },
paramsSerializer: (params) => qs.stringify(params),
});
/**
* 请求拦截器 - 添加 Authorization 头
*/
httpRequest.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const accessToken = AuthStorage.getAccessToken();
// 如果 Authorization 设置为 no-auth则不携带 Token
if (config.headers.Authorization !== "no-auth" && accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
const tenantId = localStorage.getItem(STORAGE_KEYS.TENANT_ID);
if (tenantId) {
config.headers["tenant-id"] = tenantId;
}
} else {
delete config.headers.Authorization;
delete config.headers["tenant-id"];
}
return config;
},
(error) => {
console.error("Request interceptor error:", error);
return Promise.reject(error);
}
);
/**
* 响应拦截器 - 统一处理响应和错误
*/
httpRequest.interceptors.response.use(
(response: AxiosResponse<ApiResponse>) => {
// 如果响应是二进制数据则直接返回response对象用于文件下载、Excel导出、图片显示等
if (response.config.responseType === "blob" || response.config.responseType === "arraybuffer") {
return response;
}
const { code, data, msg } = response.data;
// 请求成功
if (code === ApiCodeEnum.SUCCESS) {
return data;
}
// 特殊处理:需要选择租户(不显示错误提示,返回特殊对象供业务层处理)
if (code === ApiCodeEnum.CHOOSE_TENANT) {
return Promise.reject({
code: ApiCodeEnum.CHOOSE_TENANT,
data,
msg,
});
}
// 业务错误
ElMessage.error(msg || "系统出错");
return Promise.reject(new Error(msg || "Business Error"));
},
async (error) => {
console.error("Response interceptor error:", error);
const { config, response } = error;
// 网络错误或服务器无响应
if (!response) {
ElMessage.error("网络连接失败,请检查网络设置");
return Promise.reject(error);
}
const { code, msg } = response.data as ApiResponse;
switch (code) {
case ApiCodeEnum.ACCESS_TOKEN_INVALID:
// Access Token 过期
if (authConfig.enableTokenRefresh) {
// 启用了token刷新尝试刷新
return refreshTokenAndRetry(config, httpRequest);
} else {
// 未启用token刷新直接跳转登录页
await redirectToLogin("登录已过期,请重新登录");
return Promise.reject(new Error(msg || "Access Token Invalid"));
}
case ApiCodeEnum.REFRESH_TOKEN_INVALID:
// Refresh Token 过期,跳转登录页
await redirectToLogin("登录已过期,请重新登录");
return Promise.reject(new Error(msg || "Refresh Token Invalid"));
default:
ElMessage.error(msg || "请求失败");
return Promise.reject(new Error(msg || "Request Error"));
}
}
);
export default httpRequest;