diff --git a/apps/web/src/pages/generate/components/Step6CoverSettings.tsx b/apps/web/src/pages/generate/components/Step6CoverSettings.tsx index 1630cf697..cd447d4db 100755 --- a/apps/web/src/pages/generate/components/Step6CoverSettings.tsx +++ b/apps/web/src/pages/generate/components/Step6CoverSettings.tsx @@ -1,11 +1,11 @@ /** - * Step 5/6 选择封面(Issue #1677 批量生成改造 + #2033 封面bug修复) + * Step 5/6 选择封面(Issue #1677 批量生成改造 + #2033 封面bug修复 + #2044 批量模板选择) * - 单视频:保留原封面流程(自动生成/封面设置模板/封面预览/自定义上传) - * - N 个视频:N 张封面卡片,每张带对应视频标题,可逐个自动生成或上传 + * - N 个视频:N 张封面卡片,每张带对应视频标题,支持统一选择封面模板、逐个自动生成或上传 * * 模板 CRUD + 编辑器弹窗 + 自动生成 + 上传 复用 components/cover/useSharedCover */ -import React, { useEffect } from "react" +import React, { useEffect, useMemo } from "react" import { Modal, Spin } from "antd" import { LoadingOutlined } from "@ant-design/icons" import type { CoverConfig } from "../types/cover" @@ -29,6 +29,7 @@ interface Step6CoverSettingsProps { previewCovers?: string[] onPreviewCoversChange?: (urls: string[]) => void selectedVariantIndexes?: number[] + onTemplateChange?: (templateId: string) => void } const Step6CoverSettings: React.FC = (props) => { @@ -46,12 +47,24 @@ const Step6CoverSettings: React.FC = (props) => { props.generatedVideos.find((v) => v.status === "completed" || v.status === "awaiting_cover") || props.generatedVideos[0] + const completedVideos = useMemo( + () => + props.generatedVideos.filter( + (v) => v.status === "completed" || v.status === "awaiting_cover", + ), + [props.generatedVideos], + ) + + /** + * 单视频自动生成(点击"自动生成封面"按钮):使用当前选中的模板 + * 批量场景 canGenerate=false,避免 shared.generateAutoCover 被误触发 + */ const shared = useSharedCover({ - canGenerate: !!finalVideo, - disabledHint: "请先生成视频再选择封面", - initialTemplateId: "default", + canGenerate: !!finalVideo && !isBatch, + disabledHint: isBatch ? "批量场景请在上方操作卡片" : "请先生成视频再选择封面", + initialTemplateId: props.selectedTemplate || "default", generateFn: async (tplId) => { - if (!finalVideo) return null + if (!finalVideo || isBatch) return null const response = await apiGenerateCover(tplId, { generated_video_id: finalVideo.id, video_url: finalVideo.file_url || finalVideo.download_url || "", @@ -83,6 +96,14 @@ const Step6CoverSettings: React.FC = (props) => { }, }) + // 选中模板变化时通知父组件(用于批量生成时透传 template_id) + const { onTemplateChange, selectedTemplate: parentSelectedTemplate } = props + useEffect(() => { + if (isBatch && onTemplateChange && shared.selectedTemplateId !== parentSelectedTemplate) { + onTemplateChange(shared.selectedTemplateId) + } + }, [isBatch, shared.selectedTemplateId, parentSelectedTemplate, onTemplateChange]) + useEffect(() => { shared.setOnUploadFile(() => null) }, [shared]) @@ -99,21 +120,18 @@ const Step6CoverSettings: React.FC = (props) => { const cardPos = batchUploadCard setBatchUploadCard(null) if (!file || cardPos == null) return - const url = URL.createObjectURL(file) - const prevCardView = cardIndexes.map((vi) => (props.previewCovers || [])[vi] || "") - const nextCardView = [...prevCardView] - nextCardView[cardPos] = url - const next = [...(props.previewCovers || [])] - cardIndexes.forEach((vi, i) => { - next[vi] = nextCardView[i] || "" - }) - props.onPreviewCoversChange?.(next) + void batchCovers.uploadOne(cardPos, file) } const batchTitles = cardIndexes.map((vi) => previewTitles[vi] || "") const batchCoversList = cardIndexes.map((vi) => previewCovers[vi] || "") + + /** + * 批量生成:selectedTemplateId 来自用户在 CoverSettingsModal 中选择的模板, + * 透传给 useBatchCovers,由其在 generateOne/generateAll 中发给后端。 + */ const batchCovers = useBatchCovers({ - selectedTemplate: props.selectedTemplate || "", + selectedTemplate: shared.selectedTemplateId || "default", generatedVideos: props.generatedVideos, titles: batchTitles, titleStyle: { @@ -139,10 +157,6 @@ const Step6CoverSettings: React.FC = (props) => { const previewUrl = props.coverSettings.thumbnail_url || props.coverSettings.upload_url - const completedVideos = props.generatedVideos.filter( - (v) => v.status === "completed" || v.status === "awaiting_cover", - ) - /* ── 批量封面 ── */ if (isBatch) { return ( @@ -161,17 +175,32 @@ const Step6CoverSettings: React.FC = (props) => { }} > 🎬 共 {completedVideos.length} 个成片,封面将从对应成片中智能选帧并叠加该视频的标题 + {shared.selectedTemplateId && shared.selectedTemplateId !== "default" && ( + <> + {" · "}当前模板:{shared.selectedTemplateName} + + )} -
+
+ +
@@ -242,6 +271,35 @@ const Step6CoverSettings: React.FC = (props) => { ) })}
+ + {/* 隐藏的文件选择 input,批量上传复用 */} + + + shared.setShowCoverSettings(false)} + templates={shared.templates} + loading={shared.templatesLoading} + error={shared.templatesError} + selectedTemplateId={shared.selectedTemplateId} + onSelectTemplate={shared.handleSelectTemplate} + onEditTemplate={shared.handleEditTemplate} + onDeleteTemplate={shared.handleDeleteTemplate} + onCreateNew={shared.handleCreateTemplate} + /> + + shared.setShowCoverEditor(false)} + template={shared.editingTemplate} + onSave={shared.handleSaveTemplate} + />
) } @@ -360,4 +418,6 @@ const Step6CoverSettings: React.FC = (props) => { ) } +Step6CoverSettings.displayName = "Step6CoverSettings" + export default Step6CoverSettings diff --git a/apps/web/src/pages/generate/hooks/useBatchCovers.ts b/apps/web/src/pages/generate/hooks/useBatchCovers.ts index 9a16dda98..859c893c0 100644 --- a/apps/web/src/pages/generate/hooks/useBatchCovers.ts +++ b/apps/web/src/pages/generate/hooks/useBatchCovers.ts @@ -53,7 +53,7 @@ interface UseBatchCoversOptions { } export function useBatchCovers({ - selectedTemplate: _selectedTemplate, + selectedTemplate, generatedVideos, titles, titleStyle, @@ -107,7 +107,7 @@ export function useBatchCovers({ addBusy(index) try { const titleText = titles[index] || "" - const response = await generateCover("default", { + const response = await generateCover(selectedTemplate || "default", { generated_video_id: target.id, video_url: target.file_url || target.download_url || "", cover_type: "ai_frame", @@ -171,7 +171,7 @@ export function useBatchCovers({ removeBusy(index) } }, - [generatedVideos, titles, titleStyle, patchCover, addBusy, removeBusy], + [generatedVideos, titles, titleStyle, selectedTemplate, patchCover, addBusy, removeBusy], ) /** 为第 index 个视频上传自定义封面 */