You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

302 lines
6.3 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Dify API 请求封装 —— Vue Composables 版本
// 对应源代码 useDifyApis.ts将 React Hooks 转换为 Vue Composables
import axios, {
type AxiosInstance,
type AxiosRequestConfig,
type AxiosResponse,
type Canceler,
} from "axios";
import { ref, onMounted } from "vue";
import { getCookie } from "./types";
export interface ApiResponse<T = any> {
code?: string | number;
message?: string;
status?: number;
data: T;
}
export interface UseGetOptions {
url?: string;
params?: object;
config?: AxiosRequestConfig;
moreOperations?: {
immediate?: boolean;
};
}
export interface UsePostOptions {
url?: string;
data?: object;
config?: AxiosRequestConfig;
moreOperations?: {
immediate?: boolean;
};
}
// 存储待取消请求
const pendingRequests = new Map<string, Canceler>();
// 获取请求唯一 Key
function getRequestKey(config: AxiosRequestConfig) {
const { method, url, params, data } = config;
return [method, url, JSON.stringify(params), JSON.stringify(data)].join("&");
}
// 添加请求
function addPendingRequest(config: AxiosRequestConfig) {
const requestKey = getRequestKey(config);
if (pendingRequests.has(requestKey)) {
config.cancelToken = new axios.CancelToken((cancel) => {
cancel(`重复请求被取消: ${requestKey}`);
});
} else {
config.cancelToken = new axios.CancelToken((cancel) => {
pendingRequests.set(requestKey, cancel);
});
}
}
// 删除请求
function removePendingRequest(config: AxiosRequestConfig) {
const requestKey = getRequestKey(config);
pendingRequests.delete(requestKey);
}
// 创建 axios
const service: AxiosInstance = axios.create({
baseURL: "/p30",
timeout: 45 * 1000,
});
// 请求拦截
service.interceptors.request.use(
(config) => {
removePendingRequest(config);
addPendingRequest(config);
config.headers = config.headers ?? {};
config.headers["x-csrf-token"] = getCookie("csrf_token") || "";
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截
service.interceptors.response.use(
(response: AxiosResponse<any, any>) => {
removePendingRequest(response.config);
// 成功直接返回 data
return response.data;
},
(error) => {
if (error.config) {
removePendingRequest(error.config);
}
if (axios.isCancel(error)) {
console.warn(error.message);
return Promise.reject(error);
}
const response = error.response;
// 统一处理 401
if (response?.status === 401) {
return Promise.reject({
code: response.data?.code,
message: response.data?.message,
status: response.status,
});
}
// 其它错误
return Promise.reject(response?.data ?? error);
}
);
export function useRequest() {
const result = ref<ApiResponse | null>(null);
const loading = ref(false);
const error = ref<any>(null);
const doRequest = async (
options: AxiosRequestConfig
): Promise<AxiosResponse<ApiResponse> | ApiResponse> => {
loading.value = true;
error.value = null;
try {
const resp = await service(options);
result.value = resp as ApiResponse;
return resp;
} catch (e) {
error.value = e;
throw e;
} finally {
loading.value = false;
}
};
return {
result,
loading,
error,
doRequest,
};
}
export function useDifyGet(options: UseGetOptions = {}) {
const { result, loading, error, doRequest } = useRequest();
const { url, params, config, moreOperations } = options;
const doGet = (override: Partial<UseGetOptions> = {}) => {
const finalUrl = override.url ?? url;
const finalParams = override.params ?? params;
const finalConfig = {
...config,
...override.config,
};
if (!finalUrl) {
throw new Error("useDifyGet: url is required");
}
return doRequest({
url: finalUrl,
method: "get",
params: finalParams,
...finalConfig,
});
};
if (moreOperations?.immediate && url) {
onMounted(() => {
doGet();
});
}
return {
result,
loading,
error,
doGet,
};
}
export function useDifyPost(options: UsePostOptions = {}) {
const { result, loading, error, doRequest } = useRequest();
const { url, data, config, moreOperations } = options;
const doPost = (override: Partial<UsePostOptions> = {}) => {
const finalUrl = override.url ?? url;
const finalData = override.data ?? data;
const finalConfig = {
...config,
...override.config,
};
if (!finalUrl) {
throw new Error("useDifyPost: url is required");
}
return doRequest({
url: finalUrl,
method: "post",
data: finalData,
...finalConfig,
});
};
if (moreOperations?.immediate && url) {
onMounted(() => {
doPost();
});
}
return {
result,
loading,
error,
doPost,
};
}
export function useDifyPatch(options: UsePostOptions = {}) {
const { result, loading, error, doRequest } = useRequest();
const { url, data, config, moreOperations } = options;
const doPatch = (override: Partial<UsePostOptions> = {}) => {
const finalUrl = override.url ?? url;
const finalData = override.data ?? data;
const finalConfig = {
...config,
...override.config,
};
if (!finalUrl) {
throw new Error("useDifyPatch: url is required");
}
return doRequest({
url: finalUrl,
method: "patch",
data: finalData,
...finalConfig,
});
};
if (moreOperations?.immediate && url) {
onMounted(() => {
doPatch();
});
}
return {
result,
loading,
error,
doPatch,
};
}
export function useDifyDelete(options: UsePostOptions = {}) {
const { result, loading, error, doRequest } = useRequest();
const { url, data, config, moreOperations } = options;
const doDelete = (override: Partial<UsePostOptions> = {}) => {
const finalUrl = override.url ?? url;
const finalData = override.data ?? data;
const finalConfig = {
...config,
...override.config,
};
if (!finalUrl) {
throw new Error("useDifyDelete: url is required");
}
return doRequest({
url: finalUrl,
method: "delete",
data: finalData,
...finalConfig,
});
};
if (moreOperations?.immediate && url) {
onMounted(() => {
doDelete();
});
}
return {
result,
loading,
error,
doDelete,
};
}