feat(generate): 确认生成按钮移至标题步骤(步骤4直接触发最终渲染) #1535

Merged
auto-approve-bot merged 1 commits from feat/generate-button-on-title-step into develop 2026-08-29 00:47:26 +08:00
5 changed files with 97 additions and 31 deletions
+18 -2
View File
@@ -8,7 +8,7 @@
* - 步骤 6 封面从最终成片中智能选帧(MediaKit)
* - 点"确认生成"时调用 createGenerationTask 创建一次服务器渲染任务
*/
import React, { useMemo, useState, useEffect, useRef } from "react"
import React, { useMemo, useState, useEffect, useRef, useCallback } from "react"
import { message } from "antd"
import { useNavigate } from "react-router-dom"
import type { VoiceClone } from "@/api/voice-clone"
@@ -217,6 +217,22 @@ const GeneratePage: React.FC = () => {
},
})
/* ── 步骤4「确认生成视频」:校验标题/预览 → 创建最终渲染任务 → 成功后进入步骤5 ── */
const handleConfirmGenerate = useCallback(async () => {
if (!titleSettings.title.trim()) {
message.warning("请选择或输入标题")
return
}
if (!previewReady) {
message.warning("预览视频正在加载,请稍候")
return
}
const ok = await handleGenerate()
if (ok) {
setCurrentStep(5)
}
}, [titleSettings.title, previewReady, handleGenerate, setCurrentStep])
/* ── 步骤导航 ── */
const { goNext, goPrev } = useStepNavigation({
currentStep,
@@ -300,7 +316,7 @@ const GeneratePage: React.FC = () => {
currentStep={currentStep}
onPrev={goPrev}
onNext={goNext}
onGenerate={handleGenerate}
onConfirmGenerate={handleConfirmGenerate}
generating={generating}
generated={generated}
generateError={generateError}
@@ -1,14 +1,19 @@
/**
* GeneratePage 步骤底部操作按钮
*
* 步骤 1~3:上一步 / 下一步
* 步骤 4(标题+预览):上一步 / 确认生成视频(点击后直接创建最终渲染任务,成功后跳转步骤5)
* 步骤 5(确认生成):上一步 / 下一步(渲染中禁用,渲染完成后可进入封面)
* 步骤 6(选择封面):仅上一步
*/
import React from "react"
import { ThunderboltOutlined } from "@ant-design/icons"
export interface GenerateStepActionsProps {
currentStep: number
onPrev: () => void
onNext: () => void
onGenerate: () => void
/** 步骤4:确认生成视频(校验 + 创建渲染任务 + 成功后进入步骤5) */
onConfirmGenerate: () => void | Promise<void>
generating: boolean
generated: boolean
generateError: string | null
@@ -18,36 +23,65 @@ export const GenerateStepActions: React.FC<GenerateStepActionsProps> = ({
currentStep,
onPrev,
onNext,
onGenerate,
onConfirmGenerate,
generating,
generated,
generateError,
}) => {
const renderPrimaryButton = () => {
/* 步骤 4:确认生成视频(触发按钮在标题页) */
if (currentStep === 4) {
if (generating) {
return (
<button className="xx-btn xx-btn-primary" disabled>
</button>
)
}
if (generateError) {
return (
<button className="xx-btn xx-btn-primary" onClick={onConfirmGenerate}>
🔄
</button>
)
}
if (generated) {
return (
<button className="xx-btn xx-btn-primary" onClick={onNext}>
</button>
)
}
return (
<button className="xx-btn xx-btn-primary" onClick={onConfirmGenerate}>
</button>
)
}
/* 步骤 5:渲染中禁用,完成后下一步进入封面 */
if (currentStep === 5) {
return (
<button
className="xx-btn xx-btn-primary"
onClick={onNext}
disabled={generating || !generated}
>
{generating ? "视频生成中…" : "下一步 →"}
</button>
)
}
/* 步骤 6(最后一步):无主按钮 */
return null
}
return (
<div className="xx-step-actions">
<button className="xx-btn xx-btn-ghost" onClick={onPrev} disabled={currentStep === 1}>
</button>
{currentStep < 6 ? (
<button className="xx-btn xx-btn-primary" onClick={onNext}>
</button>
) : (
<button
className="xx-btn xx-btn-primary"
onClick={onGenerate}
disabled={generating || (generated && !generateError)}
>
<ThunderboltOutlined />
{generating
? "生成中…"
: generated && !generateError
? "已生成"
: generateError
? "🔄 重新生成"
: "✨ 确认生成"}
</button>
)}
{renderPrimaryButton()}
</div>
)
}
@@ -25,10 +25,21 @@ const GenerationStatus: React.FC<GenerationStatusProps> = ({
onRetry,
onDismissError,
}) => {
if (!generating && !generated && !generateError) return null
return (
<div style={{ marginTop: 16 }}>
{!generating && !generated && !generateError && (
<div className="xx-gen-progress-card" style={{ opacity: 0.85 }}>
<div className="xx-gen-progress-header">
<div className="xx-gen-progress-icon">🎬</div>
<div className="xx-gen-progress-info">
<div className="xx-gen-progress-phase"></div>
<div className="xx-gen-progress-sub">
</div>
</div>
</div>
</div>
)}
{generating && (
<div className="xx-gen-progress-card">
<div className="xx-gen-progress-header">
@@ -44,12 +44,13 @@ export function useGenerateVideo(props: UseGenerateVideoProps) {
onFailed: handleFailed,
})
/* ── 生成视频 ── */
const generate = useCallback(async () => {
/* ── 生成视频 ──
返回 true 表示任务创建成功并已开始轮询;false 表示校验未通过或创建失败 */
const generate = useCallback(async (): Promise<boolean> => {
const errorMsg = validateGenerateInputs(props)
if (errorMsg) {
message.warning(errorMsg)
return
return false
}
setGenerating(true)
@@ -142,7 +143,9 @@ export function useGenerateVideo(props: UseGenerateVideoProps) {
const finalMsg = translateError(backendMsg)
setGenerateError(finalMsg)
message.error(finalMsg)
return false
}
return true
}, [props, clearTimer, startPolling, selectedTemplate])
const retry = useCallback(() => {
@@ -87,7 +87,9 @@ export function useStep7Generate({
}
const handleScrollToPreview = () => {
const el = document.querySelector(".xx-preview-section")
const el =
document.querySelector(".xx-inline-video-player") ||
document.querySelector(".xx-preview-section")
el?.scrollIntoView({ behavior: "smooth", block: "start" })
}