// ============================================================ // use-api.ts — 业务 API Hook 集合 // // 基于 useAsyncData / useAsyncMutation 封装各模块 API, // 组件只需调用 Hook 获取 { data, loading, error } 三元组, // 不再需要在组件内手写 useEffect + cancelled flag + useState。 // // 每个 Hook 自动处理: // - 登录状态门控(enabled: isLoggedIn) // - 竞态条件 // - 错误日志 // - 用户可读错误信息(errorMessage) // ============================================================ import { useMemo } from 'react'; import { useAppContext } from '@/contexts/app-context'; import { TaskAPI, ModelAPI, fetchBanners, MODEL_CATEGORY_LABELS, type TaskListRequestParams, type TaskInfoItemResponseBody, type CreatTaskRequestBody, type ModelsListResponseBody, type Banner, type ModelCategory, } from '@/services/modules'; import { useAsyncData, useAsyncMutation } from './use-async'; // ============================================================ // Banner // ============================================================ /** 首页轮播图数据 Hook */ export function useBannerList() { const { isLoggedIn } = useAppContext(); return useAsyncData( fetchBanners, [], { enabled: isLoggedIn, label: 'banner-list' }, ); } // ============================================================ // 模型 // ============================================================ /** 模型列表数据 Hook(按类别分别请求后合并) */ export function useModelList() { const { isLoggedIn } = useAppContext(); // 使用单个 fetcher 返回合并后的结果 const result = useAsyncData( async () => { const categories: ModelCategory[] = ['txt2img', 'img2img', 'img2video', 'txt2audio']; const results = await Promise.allSettled( categories.map((category) => ModelAPI.fetchModels({ provider_name: '', category, page: 1, page_size: 50, }), ), ); const allModels: ModelsListResponseBody[] = []; results.forEach((r) => { if (r.status === 'fulfilled') { allModels.push(...r.value); } }); // 按优先级降序排列 allModels.sort((a, b) => b.priority - a.priority); return allModels; }, [], { enabled: isLoggedIn, label: 'model-list' }, ); // 按类别分组 const groupedModels = useMemo(() => { const groups = new Map(); result.data?.forEach((model) => { const cat = model.category_name || 'other'; if (!groups.has(cat)) groups.set(cat, []); groups.get(cat)!.push(model); }); return groups; }, [result.data]); return { ...result, /** 按类别分组后的模型 */ groupedModels, }; } // ============================================================ // 任务列表 // ============================================================ export interface UseTaskListParams { /** 完整的 API 请求参数(由调用方根据 antd Table onChange 构建) */ requestParams: TaskListRequestParams; /** 外部传入的刷新信号(任意变化触发重新请求) */ refreshSignal?: number; } /** * 任务列表 Hook(分页 + 筛选 + 排序) * * 调用方负责构建 TaskListRequestParams(包括 edition-aware 的 user_ids 逻辑), * 本 Hook 仅负责数据获取、竞态控制、错误日志。 */ export function useTaskList(params: UseTaskListParams) { const { isLoggedIn } = useAppContext(); const { requestParams, refreshSignal = 0 } = params; return useAsyncData( () => TaskAPI.getTaskList(requestParams), [requestParams, refreshSignal], { enabled: isLoggedIn, label: 'task-list' }, ); } // ============================================================ // 任务详情 // ============================================================ /** 任务详情 Hook(taskId 为 null 时不请求) */ export function useTaskDetail(taskId: string | null | undefined) { const { isLoggedIn } = useAppContext(); return useAsyncData( () => TaskAPI.getTaskInfo(taskId!), [taskId], { enabled: isLoggedIn && !!taskId, label: 'task-detail' }, ); } // ============================================================ // 提交任务 // ============================================================ /** 提交任务 Mutation Hook */ export function useSubmitTask(options?: { onSuccess?: (data: TaskInfoItemResponseBody) => void; onError?: (err: Error) => void; }) { return useAsyncMutation( (body) => TaskAPI.submitTask(body), { label: 'submit-task', onSuccess: options?.onSuccess, onError: options?.onError, }, ); } // ---------- 重新导出类型和工具 ---------- export { MODEL_CATEGORY_LABELS }; export type { ModelCategory, ModelsListResponseBody, TaskInfoItemResponseBody, Banner };