fix: 标题叠加层始终渲染+移除不存在的batch接口调用 #1419
@@ -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<PreviewVideoPanelProps> = ({
|
||||
titleSettings,
|
||||
}) => {
|
||||
const videoAspectStyle = { aspectRatio: (videoRatio || "16:9").replace(":", "/") }
|
||||
const hasTitle = !!titleSettings?.title?.trim()
|
||||
|
||||
return (
|
||||
<div className="xx-generate-preview">
|
||||
@@ -168,33 +169,40 @@ export const PreviewVideoPanel: React.FC<PreviewVideoPanelProps> = ({
|
||||
{assetsReady && assets.length > 0 && <span className="xx-preview-badge">实时预览</span>}
|
||||
</div>
|
||||
|
||||
{/* 加载中 */}
|
||||
{assetsLoading && (
|
||||
<div className="xx-preview-loading-panel">
|
||||
<div className="xx-preview-video" style={videoAspectStyle}>
|
||||
<div className="xx-preview-loading-center">
|
||||
<LoadingOutlined style={{ fontSize: 36, color: "#fff" }} spin />
|
||||
<p style={{ marginTop: 12, color: "rgba(255,255,255,0.8)", fontSize: 14 }}>
|
||||
加载素材中...
|
||||
</p>
|
||||
</div>
|
||||
{/* 预览容器 — 标题叠加层始终渲染在容器顶层,不受加载状态影响 */}
|
||||
<div className="xx-preview-video" style={{ ...videoAspectStyle, position: "relative" }}>
|
||||
{/* 内容层:加载中 or 前端播放器 */}
|
||||
{assetsLoading ? (
|
||||
<div
|
||||
className="xx-preview-loading-center"
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<LoadingOutlined style={{ fontSize: 36, color: "#fff" }} spin />
|
||||
<p style={{ marginTop: 12, color: "rgba(255,255,255,0.8)", fontSize: 14 }}>
|
||||
加载素材中...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 前端预览播放器 + CSS 标题叠加 */}
|
||||
{!assetsLoading && (
|
||||
<div className="xx-preview-video" style={{ ...videoAspectStyle, position: "relative" }}>
|
||||
) : (
|
||||
<FrontendPreviewPlayer
|
||||
assets={assets}
|
||||
template={template}
|
||||
videoRatio={videoRatio}
|
||||
ready={assetsReady}
|
||||
/>
|
||||
{/* CSS 标题实时预览层 — 与 FFmpeg ASS 渲染坐标对齐 */}
|
||||
{titleSettings && <TitleOverlay titleSettings={titleSettings} />}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* CSS 标题实时预览层 — 与 FFmpeg ASS 渲染坐标对齐
|
||||
始终渲染在内容层之上(z-index: 30),只要 titleSettings 有标题文字就显示 */}
|
||||
{hasTitle && <TitleOverlay titleSettings={titleSettings!} />}
|
||||
</div>
|
||||
|
||||
{/* 素材信息 */}
|
||||
{assetsReady && assets.length > 0 && (
|
||||
|
||||
@@ -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<AssetItem[]> {
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user