diff --git a/apps/web/src/pages/generate/components/PreviewVideoPanel.tsx b/apps/web/src/pages/generate/components/PreviewVideoPanel.tsx index 7ab9e7d12..2976a8156 100644 --- a/apps/web/src/pages/generate/components/PreviewVideoPanel.tsx +++ b/apps/web/src/pages/generate/components/PreviewVideoPanel.tsx @@ -132,7 +132,7 @@ const TitleOverlay: React.FC<{ titleSettings: TitleSettings }> = ({ titleSetting style={{ position: "absolute", inset: 0, - zIndex: 20, + zIndex: 30, pointerEvents: "none", }} > @@ -160,6 +160,7 @@ export const PreviewVideoPanel: React.FC = ({ titleSettings, }) => { const videoAspectStyle = { aspectRatio: (videoRatio || "16:9").replace(":", "/") } + const hasTitle = !!titleSettings?.title?.trim() return (
@@ -168,33 +169,40 @@ export const PreviewVideoPanel: React.FC = ({ {assetsReady && assets.length > 0 && 实时预览}
- {/* 加载中 */} - {assetsLoading && ( -
-
-
- -

- 加载素材中... -

-
+ {/* 预览容器 — 标题叠加层始终渲染在容器顶层,不受加载状态影响 */} +
+ {/* 内容层:加载中 or 前端播放器 */} + {assetsLoading ? ( +
+ +

+ 加载素材中... +

-
- )} - - {/* 前端预览播放器 + CSS 标题叠加 */} - {!assetsLoading && ( -
+ ) : ( - {/* CSS 标题实时预览层 — 与 FFmpeg ASS 渲染坐标对齐 */} - {titleSettings && } -
- )} + )} + + {/* CSS 标题实时预览层 — 与 FFmpeg ASS 渲染坐标对齐 + 始终渲染在内容层之上(z-index: 30),只要 titleSettings 有标题文字就显示 */} + {hasTitle && } +
{/* 素材信息 */} {assetsReady && assets.length > 0 && ( diff --git a/apps/web/src/pages/generate/hooks/usePreviewAssets.ts b/apps/web/src/pages/generate/hooks/usePreviewAssets.ts index 48b93387d..69d221a26 100644 --- a/apps/web/src/pages/generate/hooks/usePreviewAssets.ts +++ b/apps/web/src/pages/generate/hooks/usePreviewAssets.ts @@ -1,33 +1,22 @@ /** * 预览素材加载 Hook - * 根据选中的素材 ID 列表,批量获取素材详情(含 file_url、duration 等) + * 根据选中的素材 ID 列表,逐个获取素材详情(含 file_url、duration 等) * 供前端预览播放器使用 + * + * 注意:后端没有批量接口(/assets/batch 返回 405), + * 因此直接使用 Promise.allSettled 并发请求单个 GET /assets/{id} */ import { useState, useEffect, useCallback, useRef } from "react" import type { AssetItem } from "@/api/assets" import type { AxiosResponse } from "axios" -/** 批量获取素材详情的 API 路径 */ -const ASSETS_BATCH_URL = "/assets/batch" - /** - * 通过 ID 列表批量获取素材 - * 优先使用批量接口,失败则回退为逐个获取 + * 通过 ID 列表逐个获取素材(并发) + * 使用 Promise.allSettled 确保单个失败不影响整体 */ 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(