This commit is contained in:
2026-02-05 18:12:49 +08:00
commit 7ea280beb2
520 changed files with 169373 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
export const HTTP_CONTENT_TYPE = {
MULTIPART_FORM_DATA: "multipart/form-data",
APPLICATION_FORM_URLENCODED: "application/x-www-form-urlencoded",
APPLICATION_JSON: "application/json",
} as const;

2
src/lib/http/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export { default as http } from "./instance";
export * from "./constants";

20
src/lib/http/instance.ts Normal file
View File

@@ -0,0 +1,20 @@
import axios from "axios";
import { HTTP_CONTENT_TYPE } from "./constants";
import { setupInterceptors } from "./interceptors";
const http = axios.create({
timeout: 600_000,
headers: {
"X-Requested-With": "XMLHttpRequest",
"Content-Type": HTTP_CONTENT_TYPE.APPLICATION_JSON,
},
baseURL: import.meta.env.VITE_API_URL,
});
// 设置实例的原型
// Object.setPrototypeOf(http, axios);
// 设置拦截器
setupInterceptors(http);
export default http;

View File

@@ -0,0 +1,98 @@
import { useUserStore } from "@/stores/user";
import type { AxiosInstance, InternalAxiosRequestConfig } from "axios";
import { toast } from "sonner";
import router from "@/router";
export const downloadFile = (res: any) => {
const [, filename] = res.headers["content-disposition"]?.split(";");
const link = document.createElement("a");
link.download = decodeURIComponent(
filename?.replace("filename=", "").replace(/\"/g, "").trim() || "",
);
link.style.display = "none";
link.href = URL.createObjectURL(res.data);
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
};
export const setupInterceptors = (instance: AxiosInstance) => {
// 请求拦截
instance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
console.info(
"%c request ------>%o",
"background:#4f46e5;color:#fff;",
config,
);
// @ts-ignore
config.headers = {
...config.headers,
// @ts-ignore
Authorization: `Bearer ${useUserStore.getState().token}`,
};
return config;
},
(err: any) => {
console.error(
"%c request ------>%o",
"background:#dc2626;color:#fff;",
err,
);
return Promise.reject(err);
},
);
// 响应拦截
instance.interceptors.response.use(
(res: any) => {
console.info(
"%c response ------>%o",
"background:#059669;color:#fff;",
res,
);
if (res.status !== 200) {
return Promise.reject(res.data);
}
// 文件下载
if (!res.headers["content-type"]?.startsWith("application/json")) {
downloadFile(res);
return Promise.resolve();
}
const { code, msg, errorData } = res.data;
// 成功处理
if (["200"].includes(code) || res.headers.code === 200) {
return Promise.resolve(res.data);
}
// 未登录、登录超时、无权访问,重定向到登录页面
if (["401", "402"].includes(code)) {
toast.error(msg || "未登录/无权限");
useUserStore.setState({ token: null });
router.navigate({ to: "/login", replace: true });
return Promise.reject();
}
// token变更
if (code === "603") {
useUserStore.setState({ token: errorData.token });
return Promise.resolve();
}
// 失败统一弹窗处理
if (["555", "666", "999", "401", "402", "700"].includes(code)) {
toast.error(msg || "系统错误,请稍后再试");
}
return Promise.reject(res.data);
},
(err) => {
console.error(
"%c response ------>%o",
"background:#dc2626;color:#fff;",
err,
);
return Promise.reject(err);
},
);
return instance;
};

18
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,18 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// 将文件大小转换为人类可读的格式
export function formatFileSize(bytes: number): string {
if (bytes === undefined || bytes === null) return "-";
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB", "TB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
}