// 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 { 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(); // 获取请求唯一 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) => { 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(null); const loading = ref(false); const error = ref(null); const doRequest = async ( options: AxiosRequestConfig ): Promise | 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 = {}) => { 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 = {}) => { 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 = {}) => { 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 = {}) => { 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, }; }