| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- 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<ApiResponse>) => {
- // 如果响应是二进制流,则直接返回(用于文件下载、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<ApiResponse>) => {
- 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;
|