/** * 通用 SWR Hooks * 封装 useSWR + fetchWithAuth,统一处理加载/错误/重试逻辑 */ import useSWR, { type SWRConfiguration, type KeyedMutator } from "swr"; import { useCallback, useState } from "react"; import { fetchWithAuth } from "@/lib/api/client"; // ── 类型定义 ──────────────────────────────────────────────────────────────────── export interface UseApiReturn { data: T | undefined; isLoading: boolean; error: Error | undefined; mutate: KeyedMutator; /** 手动重新获取数据 */ refresh: () => void; } export interface UsePaginatedApiReturn extends UseApiReturn { total: number; page: number; pageSize: number; setPage: (page: number) => void; } export interface UseApiMutationReturn { trigger: (body?: B) => Promise; isMutating: boolean; error: Error | undefined; reset: () => void; } // ── SWR 全局 Fetcher ───────────────────────────────────────────────────────── /** * 基于 fetchWithAuth 的 SWR fetcher * key 格式:url string 或 [url, token] */ export async function swrFetcher(url: string): Promise { return fetchWithAuth(url) as Promise; } // ── 主 Hook ────────────────────────────────────────────────────────────────── /** * 基础数据获取 Hook * @param url API 路径(null 时暂停请求) * @param options SWR 配置项 */ export function useApi( url: string | null, options?: SWRConfiguration ): UseApiReturn { const { data, error, isLoading, mutate } = useSWR( url, swrFetcher, { revalidateOnFocus: false, shouldRetryOnError: true, errorRetryCount: 2, errorRetryInterval: 3000, ...options, } ); const refresh = useCallback(() => { mutate(); }, [mutate]); return { data, isLoading, error: error as Error | undefined, mutate, refresh, }; } // ── 分页 Hook ──────────────────────────────────────────────────────────────── export interface PaginatedResponse { items: T[]; total: number; } /** * 支持分页的数据获取 Hook * @param baseUrl 基础 API 路径(不含分页参数) * @param params 分页参数 */ export function usePaginatedApi( baseUrl: string | null, params: { page: number; pageSize: number } ): UsePaginatedApiReturn { const [page, setPage] = useState(params.page); const { pageSize } = params; const url = baseUrl ? `${baseUrl}?limit=${pageSize}&offset=${(page - 1) * pageSize}` : null; const { data, error, isLoading, mutate } = useSWR>( url, swrFetcher>, { revalidateOnFocus: false, shouldRetryOnError: true, errorRetryCount: 2, } ); const refresh = useCallback(() => { mutate(); }, [mutate]); return { data: data?.items, total: data?.total ?? 0, isLoading, error: error as Error | undefined, mutate: mutate as unknown as KeyedMutator, refresh, page, pageSize, setPage, }; } // ── Mutation Hook ──────────────────────────────────────────────────────────── /** * API 数据变更 Hook(POST / PUT / DELETE) * @param url API 路径 * @param method HTTP 方法(默认 POST) */ export function useApiMutation( url: string, method: string = "POST" ): UseApiMutationReturn { const [isMutating, setIsMutating] = useState(false); const [error, setError] = useState(undefined); const trigger = useCallback( async (body?: B): Promise => { try { setIsMutating(true); setError(undefined); const result = await fetchWithAuth(url, { method, body: body !== undefined ? JSON.stringify(body) : undefined, }) as T; return result; } catch (err) { const e = err instanceof Error ? err : new Error(String(err)); setError(e); return null; } finally { setIsMutating(false); } }, [url, method] ); const reset = useCallback(() => { setError(undefined); }, []); return { trigger, isMutating, error, reset }; }