import axios, { type InternalAxiosRequestConfig, type AxiosResponse, type AxiosInstance, type AxiosError, } from "axios"; import qs from "qs"; import { ResultEnum } from "@/enums/api/result.enum"; import { Auth } from "@/utils/auth"; import { redirectToLogin } from "@/utils/authRedirect"; declare module "axios" { export interface AxiosRequestConfig { /** 静默请求:失败不弹 toast(后台自动刷新用)。401 / TOKEN_EXPIRED 的跳登录行为不变 */ silent?: boolean; } } /** * 创建 HTTP 请求实例 */ const httpRequest: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, timeout: import.meta.env.VITE_TIMEOUT, headers: { "Content-Type": "application/json;charset=utf-8" }, paramsSerializer: (params) => qs.stringify(params, { indices: false }), }); /** * 请求拦截器 - 添加 Authorization 头 */ httpRequest.interceptors.request.use( (config: InternalAxiosRequestConfig) => { const accessToken = Auth.getAccessToken(); const auth = config.headers.Authorization; // 显式跳过鉴权(与单接口 headers 约定一致) if (auth === "no-auth") { delete config.headers.Authorization; return config; } // 未手动设置 Authorization 时自动附加 Bearer;已设置则保留(避免误删调用方传入的令牌) if (!auth && accessToken) { config.headers.Authorization = `Bearer ${accessToken}`; } return config; }, (error) => { const msg = error instanceof Error ? error.message : String(error); ElMessage.error(msg); return Promise.reject(error); } ); /** * 响应拦截器 - 统一处理响应和错误 */ httpRequest.interceptors.response.use( (response: AxiosResponse) => { // 如果响应是二进制流,则直接返回(用于文件下载、Excel 导出等) if (response.config.responseType === "blob") { return response; } const data = response.data; // 检查请求是否失败 if (data.code !== ResultEnum.SUCCESS) { if (!response.config.silent) { ElMessage.error(data.msg); } return Promise.reject(response); } // 如果请求不是 GET 请求,且不是登录或退出登录接口,请求成功时显示成功提示 if ( !response.config.silent && response.config.method?.toUpperCase() !== "GET" && !response.config.url?.includes("login") && !response.config.url?.includes("logout") ) { ElMessage.success(data.msg); } return response; }, async (error: AxiosError) => { const silent = error.config?.silent === true; // 处理网络错误(连接拒绝、超时等) if (!error.response) { let errorMessage = "网络连接异常"; // 根据错误类型提供更友好的提示 if (error.message?.includes("ECONNREFUSED")) { errorMessage = "服务器连接失败,请检查后端服务是否正常运行"; } else if (error.message?.includes("timeout")) { errorMessage = "请求超时,请稍后重试"; } else if (error.message?.includes("Network Error")) { errorMessage = "网络连接错误,请检查您的网络设置"; } console.error("网络请求失败:", error); if (!silent) { ElMessage.error(errorMessage); } return Promise.reject(new Error(errorMessage)); } const data = error.response?.data; // 处理blob类型的错误响应 if (error.response?.config.responseType === "blob" && error.response.data instanceof Blob) { try { // 将blob转换为JSON const text = await new Response(error.response.data).text(); const jsonData: ApiResponse = JSON.parse(text); if (jsonData.code === ResultEnum.ERROR) { ElMessage.error(jsonData.msg || "请求错误"); return Promise.reject(new Error(jsonData.msg || "请求错误")); } else if (jsonData.code === ResultEnum.EXCEPTION) { ElMessage.error(jsonData.msg || "服务异常"); return Promise.reject(new Error(jsonData.msg || "服务异常")); } } catch (e) { console.error("请求异常:", e); // 如果无法解析为JSON,则使用默认错误处理 ElMessage.error("数据解析失败"); return Promise.reject(new Error("数据解析失败")); } } const status = error.response.status; /** 是否为后端约定的 JSON 业务码结构 */ const hasApiCode = data !== undefined && data !== null && typeof data === "object" && "code" in data && typeof (data as ApiResponse).code === "number"; // HTTP 401 且无约定 body(如网关仅返回状态码、HTML、空 body):按登录失效处理 if (status === 401 && !hasApiCode) { await redirectToLogin("登录已失效,请重新登录"); return Promise.reject(new Error("Unauthorized")); } if (data?.code === ResultEnum.TOKEN_EXPIRED) { await redirectToLogin("登录已过期,请重新登录"); return Promise.reject(new Error(data.msg)); } else if (data?.code === ResultEnum.ERROR) { if (!silent) { ElMessage.error(data.msg || "请求错误"); } return Promise.reject(new Error(data.msg || "请求错误")); } else if (data?.code === ResultEnum.UNAUTHORIZED) { if (!silent) { ElMessage.error(data.msg || "暂无权限"); } return Promise.reject(new Error(data.msg || "请求错误")); } else if (data?.code === ResultEnum.EXCEPTION) { if (!silent) { ElMessage.error(data.msg || "服务异常"); } return Promise.reject(new Error(data.msg || "服务异常")); } else { if (!silent) { ElMessage.error("请求处理失败,请稍后重试"); } return Promise.reject(new Error("请求处理失败")); } } ); export default httpRequest;