import { auth } from '@/hooks/'; import { message } from 'ant-design-vue'; const baseUrl = import.meta.env?.VITE_API_BASE_URL || ''; const DEFAULT_TIMEOUT = 10000; const activeControllers = new Set(); /** 统一处理 401 未授权逻辑 */ const handleUnauthorized = (msg?: string) => { auth.logout(); message.error(msg || '登录状态已过期,请重新登录'); window.location.href = '/login'; }; const request = ( url: string, options: RequestInit & { query?: Record; responseType?: 'json' | 'blob'; } = {}, ) => { let finalUrl = baseUrl + url; if (options.query) { finalUrl += '?' + new URLSearchParams(options.query).toString(); } const token = auth.token.value; const headers: Record = {}; if (!(options.body instanceof FormData)) { headers['Content-Type'] = 'application/json'; } if (token) { headers['Authorization'] = `Bearer ${token}`; } // 优先使用外部传入的 signal(来自 useRequest 取消机制); // 无外部 signal 时自建 controller 用于超时取消 const externalSignal = (options as RequestInit & { signal?: AbortSignal }).signal; const controller = new AbortController(); activeControllers.add(controller); // 合并外部 signal 与内部 controller:任一 abort 都会取消请求 const onExternalAbort = () => controller.abort(); if (externalSignal) { if (externalSignal.aborted) { controller.abort(); } else { externalSignal.addEventListener('abort', onExternalAbort, { once: true }); } } const init: RequestInit = { ...options, headers: { ...headers, ...(options.headers as Record) }, signal: controller.signal, }; const timeoutId = setTimeout(() => { controller.abort(); // 超时触发 abort }, DEFAULT_TIMEOUT); const cleanupSignal = () => { clearTimeout(timeoutId); if (externalSignal) externalSignal.removeEventListener('abort', onExternalAbort); }; return fetch(finalUrl, init) .then(async (res) => { cleanupSignal(); activeControllers.delete(controller); if (res.status === 401) { handleUnauthorized(); return Promise.reject(new Error('Unauthorized')); } if (res.status === 500) { message.error('服务器开小差了,请稍后再试'); const errorData = await res.json().catch(() => ({})); return Promise.reject(new Error(errorData.message || '服务器内部错误')); } if (!res.ok) { const errorData = await res.json().catch(() => ({})); message.error(errorData.message || '请求失败'); return Promise.reject(new Error(errorData.message || '请求失败')); } if (options.responseType === 'blob') { return res.blob(); } const data = await res.json(); if (data.code === 401) { handleUnauthorized(data.msg); return Promise.reject(new Error(data.msg || 'Unauthorized')); } return data; }) .catch((err) => { cleanupSignal(); activeControllers.delete(controller); if (err.name === 'AbortError' || err.message === 'The user aborted a request.') { // 使用特殊标记对象区分“请求被取消”和“请求成功但返回 null” return Promise.resolve({ __aborted: true }); } if (err.message === 'Failed to fetch') { message.error('网络连接失败,请检查网络'); } return Promise.reject(err); }); }; export const cancelAllRequests = () => { activeControllers.forEach((controller) => { controller.abort(); }); activeControllers.clear(); }; export { request }; export const get = (url: string, query?: Record, options?: { signal?: AbortSignal }) => request(url, { method: 'GET', query, signal: options?.signal }); export const post = ( url: string, body?: any, query?: Record, options?: { signal?: AbortSignal }, ) => request(url, { method: 'POST', body: body ? JSON.stringify(body) : undefined, query, signal: options?.signal, }); export const put = (url: string, body?: any, query?: Record) => request(url, { method: 'PUT', body: body ? JSON.stringify(body) : undefined, query, }); export const del = (url: string, query?: Record) => request(url, { method: 'DELETE', query }); export default { get, post, put, delete: del, };