u
This commit is contained in:
commit
c570ec514c
550 files changed
+152597
No files matched your search
@@ -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]}`;
|
||||
}
|
||||
@@ -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 ?? {})
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user