|
|
// 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,
|
|
|
};
|
|
|
}
|