diff --git a/apps/web/src/pages/generate/hooks/usePreviewAssets.ts b/apps/web/src/pages/generate/hooks/usePreviewAssets.ts new file mode 100644 index 000000000..48f1bf04c --- /dev/null +++ b/apps/web/src/pages/generate/hooks/usePreviewAssets.ts @@ -0,0 +1,106 @@ +/** + * 预览素材加载 Hook + * 根据选中的素材 ID 列表,批量获取素材详情(含 file_url、duration 等) + * 供前端预览播放器使用 + */ +import { useState, useEffect, useCallback, useRef } from "react" +import type { AssetItem } from "@/api/assets" + +/** 批量获取素材详情的 API 路径 */ +const ASSETS_BATCH_URL = "/assets/batch" + +/** + * 通过 ID 列表批量获取素材 + * 优先使用批量接口,失败则回退为逐个获取 + */ +async function fetchAssetsByIds(ids: string[]): Promise { + if (!ids.length) return [] + + try { + // 尝试批量接口 + const { default: apiClient } = await import("@/api/client") + const response = await apiClient.post(ASSETS_BATCH_URL, { ids }) + const items: AssetItem[] = response.data?.items || response.data || [] + if (items.length > 0) return items + } catch { + // 批量接口不存在,回退为逐个获取 + } + + // 回退:逐个获取 + try { + const { default: apiClient } = await import("@/api/client") + const results = await Promise.allSettled( + ids.map((id) => apiClient.get(`/assets/${id}`)), + ) + return results + .filter( + (r): r is PromiseFulfilledResult<{ data: AssetItem }> => + r.status === "fulfilled" && r.value?.data, + ) + .map((r) => r.value.data) + } catch { + return [] + } +} + +interface UsePreviewAssetsReturn { + /** 加载后的素材列表 */ + assets: AssetItem[] + /** 是否正在加载 */ + loading: boolean + /** 是否已就绪(加载完成) */ + ready: boolean + /** 手动触发重新加载 */ + reload: () => void +} + +/** + * usePreviewAssets — 加载选中素材的视频文件信息 + */ +export function usePreviewAssets( + assetIds: string[], + enabled: boolean, +): UsePreviewAssetsReturn { + const [assets, setAssets] = useState([]) + const [loading, setLoading] = useState(false) + const [ready, setReady] = useState(false) + const requestIdRef = useRef(0) + + const load = useCallback(async () => { + if (!assetIds.length || !enabled) { + setAssets([]) + setReady(false) + return + } + + const thisRequestId = ++requestIdRef.current + setLoading(true) + setReady(false) + + try { + const result = await fetchAssetsByIds(assetIds) + // 防止竞态:只保留最新请求的结果 + if (requestIdRef.current === thisRequestId) { + setAssets(result) + setReady(result.length > 0) + } + } catch { + if (requestIdRef.current === thisRequestId) { + setAssets([]) + setReady(false) + } + } finally { + if (requestIdRef.current === thisRequestId) { + setLoading(false) + } + } + }, [assetIds, enabled]) + + useEffect(() => { + load() + }, [load]) + + return { assets, loading, ready, reload: load } +} + +export default usePreviewAssets