Compare commits

...

2 Commits

Author SHA1 Message Date
xiaoxia 63e1889bc4 fix(cover): 打通批量封面模板选择的 prop 链路 (#2046)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 10s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 58s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 59s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m4s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 42s
CI/CD Pipeline / Build Staging API Image (push) Successful in 55s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m43s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 49s
CI/CD Pipeline / Integration Tests (push) Successful in 2m47s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m34s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m36s
CI/CD Pipeline / Validate - Style (push) Successful in 3m38s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m24s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m29s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m22s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m7s
AI Code Review / AI Code Review (pull_request) Successful in 6m58s
CI/CD Pipeline / Validate - Security (push) Successful in 8m10s
CI/CD Pipeline / Unit Tests (push) Successful in 13m56s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-26 00:03:19 +08:00
xiaoxia c7dffb858b fix(cover): 批量生成场景支持封面模板选择与应用 (#2045)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 11s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m14s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m31s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 4m1s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 4m10s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 4m16s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m47s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m33s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m23s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 4m0s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 52s
AI Code Review / AI Code Review (pull_request) Successful in 6m30s
CI/CD Pipeline / Validate - Style (push) Successful in 6m53s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 7m5s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m24s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m51s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m14s
CI/CD Pipeline / Validate - Security (push) Successful in 10m54s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 11m16s
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
CI/CD Pipeline / Unit Tests (push) Successful in 13m5s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
fix(cover): 批量生成场景补封面模板选择;批量生成应用所选模板

- Step6CoverSettings 批量分支补「封面模板」「新建模板」按钮
- 批量分支渲染 CoverSettingsModal/CoverEditorModal
- useBatchCovers 移除 _selectedTemplate 下划线,实际使用 selectedTemplate
- generateCover 传 selectedTemplate 而非硬编码 default
- 批量上传改走素材库(uploadOne),不再用临时 blob URL
2026-09-25 23:45:19 +08:00
4 changed files with 100 additions and 25 deletions
@@ -107,6 +107,7 @@ const GeneratePage: React.FC = () => {
setPreviewCovers,
selectedVariantIds,
setSelectedVariantIds,
setSelectedTemplate,
} = formState
const isBatch = previewCount > 1
@@ -577,6 +578,8 @@ const GeneratePage: React.FC = () => {
previewCovers={previewCovers}
onPreviewCoversChange={setPreviewCovers}
selectedVariantIds={selectedVariantIds}
selectedCoverTemplate={selectedTemplate}
onSelectedCoverTemplateChange={setSelectedTemplate}
/>
{/* ════ 步骤4(单视频):成片播放器 ════ */}
@@ -89,6 +89,8 @@ export interface GenerateStepContentProps {
previewCovers: string[]
onPreviewCoversChange: (urls: string[]) => void
selectedVariantIds?: number[]
selectedCoverTemplate?: string
onSelectedCoverTemplateChange?: (templateId: string) => void
}
export const GenerateStepContent: React.FC<GenerateStepContentProps> = (props) => {
@@ -142,6 +144,8 @@ export const GenerateStepContent: React.FC<GenerateStepContentProps> = (props) =
previewCovers,
onPreviewCoversChange,
selectedVariantIds,
selectedCoverTemplate,
onSelectedCoverTemplateChange,
} = props
switch (currentStep) {
@@ -250,6 +254,8 @@ export const GenerateStepContent: React.FC<GenerateStepContentProps> = (props) =
previewCovers={previewCovers}
onPreviewCoversChange={onPreviewCoversChange}
selectedVariantIndexes={selectedVariantIds}
selectedTemplate={selectedCoverTemplate}
onTemplateChange={onSelectedCoverTemplateChange}
/>
)
default:
@@ -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<Step6CoverSettingsProps> = (props) => {
@@ -46,12 +47,24 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (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,20 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
},
})
// 选中模板变化时通知父组件(用于批量生成时透传 template_id)
const { onTemplateChange, selectedTemplate: parentSelectedTemplate } = props
// 父组件 selectedTemplate 变化时同步到子(例如从 Step1/Step4 切换到 Step6 时)
useEffect(() => {
if (parentSelectedTemplate && parentSelectedTemplate !== shared.selectedTemplateId) {
shared.handleSelectTemplate(parentSelectedTemplate)
}
}, [parentSelectedTemplate]) // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
if (isBatch && onTemplateChange && shared.selectedTemplateId !== parentSelectedTemplate) {
onTemplateChange(shared.selectedTemplateId)
}
}, [isBatch, shared.selectedTemplateId, parentSelectedTemplate, onTemplateChange])
useEffect(() => {
shared.setOnUploadFile(() => null)
}, [shared])
@@ -99,21 +126,18 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (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 +163,6 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (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 +181,32 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
}}
>
🎬 共 {completedVideos.length} 个成片,封面将从对应成片中智能选帧并叠加该视频的标题
{shared.selectedTemplateId && shared.selectedTemplateId !== "default" && (
<>
{" · "}当前模板:<strong>{shared.selectedTemplateName}</strong>
</>
)}
</div>
<div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
<div style={{ display: "flex", gap: 8, marginBottom: 16, flexWrap: "wrap" }}>
<Button
buttonType="primary"
onClick={() => void batchCovers.generateAll()}
disabled={completedVideos.length === 0 || batchCovers.busyIndexes.length > 0}
style={{ whiteSpace: "nowrap", flexShrink: 0 }}
loading={batchCovers.busyIndexes.length > 0}
>
✨ 一键全部自动生成
</Button>
<Button buttonType="ghost" onClick={() => shared.setShowCoverSettings(true)}>
⚙️ 封面模板
{shared.selectedTemplateId && shared.selectedTemplateId !== "default"
? `:${shared.selectedTemplateName}`
: ""}
</Button>
<Button buttonType="ghost" onClick={shared.handleCreateTemplate}>
➕ 新建模板
</Button>
</div>
<div className="xx-cover-grid">
@@ -242,6 +277,35 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
)
})}
</div>
{/* 隐藏的文件选择 input,批量上传复用 */}
<input
ref={batchUploadRef}
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={handleBatchUploadChange}
/>
<CoverSettingsModal
open={shared.showCoverSettings}
onClose={() => 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}
/>
<CoverEditorModal
open={shared.showCoverEditor}
onClose={() => shared.setShowCoverEditor(false)}
template={shared.editingTemplate}
onSave={shared.handleSaveTemplate}
/>
</div>
)
}
@@ -360,4 +424,6 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
)
}
Step6CoverSettings.displayName = "Step6CoverSettings"
export default Step6CoverSettings
@@ -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 个视频上传自定义封面 */