From a2ea6cc51f28df5de7c923b011b2cc1475b135a0 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Fri, 25 Sep 2026 22:52:36 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(cover):=20=E6=89=B9=E9=87=8F=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=9C=BA=E6=99=AF=E8=A1=A5=E5=B0=81=E9=9D=A2=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E9=80=89=E6=8B=A9=EF=BC=9B=E6=89=B9=E9=87=8F=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=BA=94=E7=94=A8=E6=89=80=E9=80=89=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:批量生成(count>1)时,Step6CoverSettings 走 isBatch 分支直接 return 了卡片列表, 没有渲染封面模板选择入口;useBatchCovers.generateOne 硬编码 generateCover('default', ...), 即使用户想选自定义模板也不生效。 修改: 1. Step6CoverSettings 批量分支:在『一键全部自动生成』按钮旁增加『⚙️ 封面模板』和『➕ 新建模板』按钮, 并在批量分支底部渲染 CoverSettingsModal/CoverEditorModal,与单视频分支一致 2. 批量分支的提示条显示当前选中的模板名称,与单视频一致 3. 批量分支补 Modal loading 覆盖层(应用模板生成时的提示) 4. useBatchCovers:去掉 selectedTemplate 参数名的下划线前缀(之前故意不使用), generateOne 里 generateCover 第一个参数改用 selectedTemplate(默认 'default'), 用户选了非默认模板时一键生成/单个生成都会应用该模板 5. generateOne 的依赖数组补 selectedTemplate 6. Step6CoverSettings 增加 onTemplateChange 回调 prop(用于父组件如需监听模板变化), 同时 shared 的 canGenerate 在批量场景下设为 false,避免误触单视频生成逻辑 7. 批量上传 change 事件从直接拼接 blob 改为调用 batchCovers.uploadOne,走素材库上传流程 (之前直接把 blob URL 塞 previewCovers,刷新后丢失且不入库) --- .../components/Step6CoverSettings.tsx | 104 ++++++++++++++---- .../pages/generate/hooks/useBatchCovers.ts | 6 +- 2 files changed, 85 insertions(+), 25 deletions(-) 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 个视频上传自定义封面 */ -- 2.54.0 From 4cf2981a25347bb49c8cd2f4043ea07bac4f2e23 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Fri, 25 Sep 2026 23:19:02 +0800 Subject: [PATCH 2/2] chore: retrigger CI -- 2.54.0