This commit is contained in:
oneao committed 2025-06-10 21:27:20 +08:00
commit c570ec514c
550 files changed
+152597

No files matched your search

+77
View File
@@ -0,0 +1,77 @@
import { Message } from "@arco-design/web-vue";
/**
* 复制文本到剪切板
* @param text 复制内容
* @returns 是否复制成功
*/
export function copyText(text: string) {
if (navigator.clipboard && window.isSecureContext) {
// 现代浏览器和安全环境下,直接用 Clipboard API
return navigator.clipboard
.writeText(text)
.then(() => {
Message.success("复制成功");
})
.catch((err) => {
Message.error("复制失败:" + err);
});
} else {
// 退回到传统方式
fallbackCopyText(text);
}
}
/**
* 复制文本到剪切板的传统方式
* @param text 复制内容
*/
function fallbackCopyText(text: string) {
const textArea = document.createElement("textarea");
textArea.value = text;
// 避免滚动到页面底部
textArea.style.position = "fixed";
textArea.style.top = "0";
textArea.style.left = "0";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand("copy");
if (successful) {
console.log("复制成功(fallback)");
} else {
console.log("复制失败(fallback)");
}
} catch (err) {
console.error("复制失败(fallback)", err);
}
document.body.removeChild(textArea);
}
export function formatFileSizeToString(size: number): string {
if (size < 0) return "0 B";
const units = ["B", "KB", "MB", "GB", "TB"];
let sizeD = size;
let unitIndex = 0;
while (sizeD >= 1024 && unitIndex < units.length - 1) {
sizeD /= 1024;
unitIndex++;
}
let formatted: string;
if (sizeD < 10) {
formatted = sizeD.toFixed(2);
} else if (sizeD < 100) {
formatted = sizeD.toFixed(1);
} else {
formatted = sizeD.toFixed(0);
}
return `${formatted} ${units[unitIndex]}`;
}
+177
View File
@@ -0,0 +1,177 @@
import { useRouterStore } from "@/store/modules/router";
import { useUserStore } from "@/store/modules/user";
import { Message } from "@arco-design/web-vue";
import { ErrorModalOptions, ErrorModalService } from "@/components/error-modal";
// 构建 baseURL
const buildBaseUrl = () => {
const isProxy = import.meta.env.VITE_APP_PROXY === "Y";
const mode = import.meta.env.MODE;
const proxyPrefix = import.meta.env.VITE_APP_PROXY_PREFIX;
const baseURL = import.meta.env.VITE_SERVER_BASEURL;
return mode === "prod" ? (isProxy ? `${baseURL}${proxyPrefix}` : baseURL) : isProxy ? proxyPrefix : baseURL;
};
// 定义响应类型
type Response<T = unknown> = {
code: number;
message: string;
data: T;
};
// 核心请求函数
export async function commonFetch<T = unknown>(
url: string,
options: {
stream?: boolean;
onStreamData?: (response: Response<T>) => void; // 修改回调参数类型
} = {},
init: RequestInit = {}
): Promise<Response<T>> {
const userStore = useUserStore();
const routerStore = useRouterStore();
// 登录状态检查
if (!userStore.isLogined) {
userStore.clearUserInfo();
routerStore.navigateTo("/login");
throw new Error("尚未登录,请先登录!");
}
// 构建完整URL
const baseURL = buildBaseUrl();
const fullUrl = `${baseURL}${url}`;
// 请求头配置
const headers: HeadersInit = {
Authorization: `Bearer ${userStore.userInfo.token}`,
id: userStore.userInfo.id,
...(init.headers || {})
};
// 发起请求
const response = await fetch(fullUrl, {
...init,
headers
});
// 流式数据处理
if (options.stream) {
const reader = response.body?.getReader();
if (!reader) throw new Error("流式读取器不可用");
const decoder = new TextDecoder();
try {
// 一边读取流,一边处理每个数据块
while (true) {
const { value, done } = await reader.read();
if (done) break;
// 实时解码处理
const jsonStr = decoder.decode(value, { stream: true });
try {
const resData: IResData<T> = JSON.parse(jsonStr);
if (resData.code === 401) {
Message.error(resData.message || "当前登录已过期或尚未登录,请重新登录!");
routerStore.navigateTo("/login");
// 不要 return Promise.reject,保持流继续运行
} else if (resData.code >= 400) {
if (resData.showDetail) {
ErrorModalService.show(resData as IResData<ErrorModalOptions>);
} else {
Message.error(resData.message || "服务端错误,请联系管理员!");
}
// 不中断流,或者用一个标志位后续统一 reject
} else {
options.onStreamData?.(resData);
}
} catch (e) {
console.error("流数据解析错误:", e);
}
}
// 返回流处理完成的结果
return {
code: 200,
message: "Stream completed",
data: null as T
};
} catch (error) {
console.log(error);
Message.error("流数据接收失败");
return Promise.reject(error);
}
}
// 常规请求处理
if (!response.ok) {
const errorText = await response.text();
Message.error(`请求失败: ${response.status} - ${errorText}`);
return Promise.reject(new Error(`HTTP error! status: ${response.status}`));
}
try {
const resData: Response<T> = await response.json();
// 错误处理
if (resData.code >= 400) {
if (resData.code === 401) {
routerStore.navigateTo("/login");
Message.warning("登录状态已过期,请重新登录");
} else {
Message.warning(resData.message || "请求错误");
}
return Promise.reject(resData);
}
return resData;
} catch (e) {
Message.error("响应数据解析失败");
return Promise.reject(e);
}
}
// GET请求封装
export async function fetchGet<T = any>(
url: string,
config: {
params?: Record<string, any>;
stream?: boolean;
onStreamData?: (response: Response<T>) => void; // 同步修改类型
} = {}
): Promise<Response<T>> {
const query = config.params ? `?${new URLSearchParams(config.params).toString()}` : "";
return commonFetch<T>(url + query, {
stream: config.stream,
onStreamData: config.onStreamData
});
}
// POST请求封装
export async function fetchPost<T = any>(
url: string,
config: {
data?: any;
stream?: boolean;
onStreamData?: (response: Response<T>) => void; // 同步修改类型
} = {}
): Promise<Response<T>> {
return commonFetch<T>(
url,
{
stream: config.stream,
onStreamData: config.onStreamData
},
{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(config.data ?? {})
}
);
}
+50
View File
@@ -0,0 +1,50 @@
import axiosInstance from "@/interceptors/http";
import type { AxiosResponse, AxiosProgressEvent } from "axios";
type Response<T = unknown> = {
code: number;
message: string;
data: T;
};
export function httpGet<T>(url: string, params?: Record<string, any>): Promise<Response<T>> {
return axiosInstance.get(url, { params });
}
export function httpPostJson<T>(url: string, data?: Record<string, any>): Promise<Response<T>> {
return axiosInstance.post(url, data, {
headers: {
"Content-Type": "application/json"
}
});
}
export function httpPostForm<T>(url: string, data?: any): Promise<Response<T>> {
return axiosInstance.post(url, data, {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
});
}
/**
* 下载文件(支持 POST)
* @param url 请求地址
* @param data 请求体
* @returns 文件流响应
*/
export function httpDownloadFile(url: string, data?: any): Promise<AxiosResponse<Blob>> {
return axiosInstance.post(url, data, {
responseType: "blob",
headers: {
"Content-Type": "application/json"
}
});
}
export function httpPostUpload<T>(url: string, formData: FormData, onUploadProgress?: (progressEvent: AxiosProgressEvent) => void): Promise<Response<T>> {
return axiosInstance.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data"
},
onUploadProgress
});
}