From 5a9c97d64c110d99db1ab56fdadcfa96bbb29390 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 14 Sep 2026 21:37:22 +0800 Subject: [PATCH] style: reformat GeneratePage.tsx with repo prettier config (#1899 #1900) --- apps/web/src/pages/generate/GeneratePage.tsx | 293 ++++++++----------- 1 file changed, 130 insertions(+), 163 deletions(-) diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index fb46dcbbf..2cbbdc72b 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -4,43 +4,37 @@ * 模板选择已删除,模板在后台自动加载/选择(后端 API 兼容)。 * 新增片段数量输入(默认3),系统按片段数智能匹配素材。 */ -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"; -import { useQuery } from "@tanstack/react-query"; -import { useCloneProgress } from "@/hooks/useCloneProgress"; -import CloneModal from "@/components/voice/CloneModal"; -import GenerateHeader from "./components/GenerateHeader"; -import FrontendPreviewPlayer from "./components/FrontendPreviewPlayer"; -import CanvasPreviewGrid from "./components/CanvasPreviewGrid"; -import PreviewCountModal from "./components/PreviewCountModal"; -import GenerateStepsBar from "./components/GenerateStepsBar"; -import GenerateStepContent from "./components/GenerateStepContent"; -import GenerateStepActions from "./components/GenerateStepActions"; -import { useGenerateFormState } from "./hooks/useGenerateFormState"; -import { useStepNavigation } from "./hooks/useStepNavigation"; -import { useGenerateVideo } from "./hooks/useGenerateVideo"; +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" +import { useQuery } from "@tanstack/react-query" +import { useCloneProgress } from "@/hooks/useCloneProgress" +import CloneModal from "@/components/voice/CloneModal" +import GenerateHeader from "./components/GenerateHeader" +import FrontendPreviewPlayer from "./components/FrontendPreviewPlayer" +import CanvasPreviewGrid from "./components/CanvasPreviewGrid" +import PreviewCountModal from "./components/PreviewCountModal" +import GenerateStepsBar from "./components/GenerateStepsBar" +import GenerateStepContent from "./components/GenerateStepContent" +import GenerateStepActions from "./components/GenerateStepActions" +import { useGenerateFormState } from "./hooks/useGenerateFormState" +import { useStepNavigation } from "./hooks/useStepNavigation" +import { useGenerateVideo } from "./hooks/useGenerateVideo" -import { usePreviewAssets } from "./hooks/usePreviewAssets"; -import { useBatchVariantPlans } from "./hooks/useBatchVariantPlans"; -import { useVariantVoicePreview } from "./hooks/useVariantVoicePreview"; -import { useTitleStyleUpdaters } from "./hooks/useStep4Title/useTitleStyleUpdaters"; -import { getAssetsByKind } from "@/api/assets"; -import { previewTts } from "@/api/tts"; -import "./generate.css"; +import { usePreviewAssets } from "./hooks/usePreviewAssets" +import { useBatchVariantPlans } from "./hooks/useBatchVariantPlans" +import { useVariantVoicePreview } from "./hooks/useVariantVoicePreview" +import { useTitleStyleUpdaters } from "./hooks/useStep4Title/useTitleStyleUpdaters" +import { getAssetsByKind } from "@/api/assets" +import { previewTts } from "@/api/tts" +import "./generate.css" const GeneratePage: React.FC = () => { - const navigate = useNavigate(); + const navigate = useNavigate() /* ── 表单状态 ── */ - const formState = useGenerateFormState(); + const formState = useGenerateFormState() const { currentStep, setCurrentStep, @@ -89,97 +83,92 @@ const GeneratePage: React.FC = () => { setPreviewCovers, selectedVariantIds, setSelectedVariantIds, - } = formState; + } = formState - const isBatch = previewCount > 1; + const isBatch = previewCount > 1 /* ── 配音选择同步 ── */ const prevVoiceSyncRef = useRef({ voice: selectedVoice, batch: isBatch, perVideo: voiceModePerVideo, - }); + }) useEffect(() => { - const prev = prevVoiceSyncRef.current; - const voiceChanged = prev.voice !== selectedVoice; - const modeChanged = - prev.batch !== isBatch || prev.perVideo !== voiceModePerVideo; + const prev = prevVoiceSyncRef.current + const voiceChanged = prev.voice !== selectedVoice + const modeChanged = prev.batch !== isBatch || prev.perVideo !== voiceModePerVideo prevVoiceSyncRef.current = { voice: selectedVoice, batch: isBatch, perVideo: voiceModePerVideo, - }; - if (!voiceChanged && !modeChanged) return; - if (!isBatch) return; + } + if (!voiceChanged && !modeChanged) return + if (!isBatch) return if (!voiceModePerVideo) { - setVoiceLibraryIds((prevIds) => - (prevIds || []).map((id) => id || selectedVoice), - ); + setVoiceLibraryIds((prevIds) => (prevIds || []).map((id) => id || selectedVoice)) } else if (voiceChanged) { setVoiceLibraryIds((prevIds) => (prevIds || []).map((id, i) => (i === 0 ? selectedVoice : id)), - ); + ) } - }, [selectedVoice, isBatch, voiceModePerVideo, setVoiceLibraryIds]); + }, [selectedVoice, isBatch, voiceModePerVideo, setVoiceLibraryIds]) /* ── 数量选择弹窗 ── */ - const [countModalOpen, setCountModalOpen] = useState(false); + const [countModalOpen, setCountModalOpen] = useState(false) /* ── 标题样式回调 ── */ const styleUpdaters = useTitleStyleUpdaters({ titleSettings, onTitleSettingsChange: setTitleSettings, - }); + }) /* ── 配音素材库(TTS 试听)── */ const { data: voiceMaterials = [] } = useQuery({ queryKey: ["assets", "voice"], queryFn: () => getAssetsByKind("voice", { limit: 50 }), - }); + }) - const [previewVoiceAudioUrl, setPreviewVoiceAudioUrl] = useState< - string | null - >(null); - const ttsAbortRef = useRef(null); - const variant0Title = isBatch ? previewTitles?.[0] || "" : ""; + const [previewVoiceAudioUrl, setPreviewVoiceAudioUrl] = useState(null) + const ttsAbortRef = useRef(null) + const variant0Title = isBatch ? previewTitles?.[0] || "" : "" useEffect(() => { - if (isBatch) return; - const voiceAsset = voiceMaterials.find((m) => m.id === selectedVoice); + if (isBatch) return + const voiceAsset = voiceMaterials.find((m) => m.id === selectedVoice) if (voiceAsset?.file_url) { - setPreviewVoiceAudioUrl(voiceAsset.file_url); - return; + setPreviewVoiceAudioUrl(voiceAsset.file_url) + return } - const ttsTitle = titleSettings.title; - const voiceId = selectedClonedVoice || selectedVoice; + const ttsTitle = titleSettings.title + const voiceId = selectedClonedVoice || selectedVoice if (!voiceId || !ttsTitle) { - setPreviewVoiceAudioUrl(null); - return; + setPreviewVoiceAudioUrl(null) + return } - ttsAbortRef.current?.abort(); - const controller = new AbortController(); - ttsAbortRef.current = controller; - let cancelled = false; + ttsAbortRef.current?.abort() + const controller = new AbortController() + ttsAbortRef.current = controller + let cancelled = false previewTts({ text: ttsTitle, voice_id: voiceId }) .then((res) => { if (!cancelled && res.audio_url) { - setPreviewVoiceAudioUrl(res.audio_url); + setPreviewVoiceAudioUrl(res.audio_url) } }) .catch((err) => { if (!cancelled) { - console.warn("[预览配音生成失败]", err); - setPreviewVoiceAudioUrl(null); + console.warn("[预览配音生成失败]", err) + setPreviewVoiceAudioUrl(null) } - }); + }) return () => { - cancelled = true; - controller.abort(); - }; + cancelled = true + controller.abort() + } }, [ selectedVoice, selectedClonedVoice, @@ -187,22 +176,22 @@ const GeneratePage: React.FC = () => { variant0Title, isBatch, voiceMaterials, - ]); + ]) /* ── 克隆声音 ── */ - const { addClone } = useCloneProgress(); + const { addClone } = useCloneProgress() const handleCloneSuccess = (voice: VoiceClone) => { - addClone(voice); - setCloneModalOpen(false); - message.success("音色克隆成功!"); - }; + addClone(voice) + setCloneModalOpen(false) + message.success("音色克隆成功!") + } /* ── 素材 ID 列表 ── */ const previewAssetIds = useMemo( () => (materialMode === "auto" ? smartSelectedIds : selectedMaterials), [materialMode, smartSelectedIds, selectedMaterials], - ); + ) /* ── BGM 配置 ── */ const bgmConfig = useMemo( @@ -211,25 +200,23 @@ const GeneratePage: React.FC = () => { music_id: "", }), [bgm], - ); + ) /* ── 加载素材详情 ── */ - const previewAssetsEnabled = previewAssetIds.length > 0; + const previewAssetsEnabled = previewAssetIds.length > 0 const { assets: previewAssets, ready: previewAssetsReady, ensureAssets, - } = usePreviewAssets(previewAssetIds, previewAssetsEnabled); + } = usePreviewAssets(previewAssetIds, previewAssetsEnabled) /* ── 预览就绪 ── */ // #1899: 不再依赖 currentTemplate,素材加载完即可预览 - const previewReady = previewAssetsReady; + const previewReady = previewAssetsReady /* ── 批量变体真实片段(#1744) ── */ const batchVoiceLibraryId = - voiceMode === "clone" - ? selectedClonedVoice || selectedVoice || "" - : selectedVoice || ""; + voiceMode === "clone" ? selectedClonedVoice || selectedVoice || "" : selectedVoice || "" const { clipsByVariant: variantClips, planIdsByVariant: variantPlanIds, @@ -246,7 +233,7 @@ const GeneratePage: React.FC = () => { voiceLibraryId: batchVoiceLibraryId, voiceLibraryIds: voiceLibraryIds || [], voiceModePerVideo, - }); + }) /* ── 批量变体配音预览 URL ── */ const variantVoiceAudioUrls = useVariantVoicePreview({ @@ -257,33 +244,29 @@ const GeneratePage: React.FC = () => { clonedVoiceId: selectedClonedVoice || "", variantVoiceIds: voiceLibraryIds || [], titles: previewTitles || [], - }); + }) /* ── 变体 clips 引用素材补拉 ── */ const clipAssetIds = useMemo(() => { - if (!isBatch || !variantClips?.length) return []; - const ids = new Set(); - variantClips.forEach((list) => - list.forEach((c) => c.asset_id && ids.add(c.asset_id)), - ); - return Array.from(ids); - }, [isBatch, variantClips]); + if (!isBatch || !variantClips?.length) return [] + const ids = new Set() + variantClips.forEach((list) => list.forEach((c) => c.asset_id && ids.add(c.asset_id))) + return Array.from(ids) + }, [isBatch, variantClips]) useEffect(() => { - if (clipAssetIds.length > 0) void ensureAssets(clipAssetIds); - }, [clipAssetIds, ensureAssets]); + if (clipAssetIds.length > 0) void ensureAssets(clipAssetIds) + }, [clipAssetIds, ensureAssets]) /* ── 勾选变体 ── */ const toggleVariantSelect = useCallback( (index: number) => { setSelectedVariantIds((prev) => { - const list = prev || []; - return list.includes(index) - ? list.filter((i) => i !== index) - : [...list, index].sort(); - }); + const list = prev || [] + return list.includes(index) ? list.filter((i) => i !== index) : [...list, index].sort() + }) }, [setSelectedVariantIds], - ); + ) /* ── 视频生成核心逻辑 ── */ const { @@ -325,37 +308,31 @@ const GeneratePage: React.FC = () => { variantCoverUrls: previewCovers, selectedVariantIndexes: isBatch ? selectedVariantIds : undefined, onGenerationSuccess: () => { - setPreviewTaskId(null); - setStoredSourceEditPlanId(null); + setPreviewTaskId(null) + setStoredSourceEditPlanId(null) }, - }); + }) /* ── 数量弹窗确认 ── */ const handleCountConfirm = useCallback( (count: number) => { - setPreviewCount(count); - setCountModalOpen(false); + setPreviewCount(count) + setCountModalOpen(false) setPreviewTitles((prev) => { - const list = prev || []; - const base = list[0] || titleSettings.title || ""; - return Array.from( - { length: count }, - (_, i) => list[i] ?? (i === 0 ? base : ""), - ); - }); + const list = prev || [] + const base = list[0] || titleSettings.title || "" + return Array.from({ length: count }, (_, i) => list[i] ?? (i === 0 ? base : "")) + }) setVoiceLibraryIds((prev) => { - const list = prev || []; - return Array.from( - { length: count }, - (_, i) => list[i] ?? selectedVoice ?? "", - ); - }); + const list = prev || [] + return Array.from({ length: count }, (_, i) => list[i] ?? selectedVoice ?? "") + }) setPreviewCovers((prev) => { - const list = prev || []; - return Array.from({ length: count }, (_, i) => list[i] ?? ""); - }); - setSelectedVariantIds(Array.from({ length: count }, (_, i) => i)); - setCurrentStep(2); + const list = prev || [] + return Array.from({ length: count }, (_, i) => list[i] ?? "") + }) + setSelectedVariantIds(Array.from({ length: count }, (_, i) => i)) + setCurrentStep(2) }, [ setPreviewCount, @@ -367,33 +344,33 @@ const GeneratePage: React.FC = () => { titleSettings.title, selectedVoice, ], - ); + ) /* ── 步骤3「确认生成视频」:校验通过 → 创建正式生成任务 → 跳步骤4看实时进展 ── */ const handleConfirmGenerate = useCallback(async () => { if (isBatch) { if (selectedVariantIds.length === 0) { - message.warning("请至少勾选一个视频"); - return; + message.warning("请至少勾选一个视频") + return } - const missing = selectedVariantIds.some((i) => !previewTitles[i]?.trim()); + const missing = selectedVariantIds.some((i) => !previewTitles[i]?.trim()) if (missing) { - message.warning("请为每个勾选的视频输入标题"); - return; + message.warning("请为每个勾选的视频输入标题") + return } } else if (!titleSettings.title?.trim()) { if (!titleSettings.aiAutoSelect) { - message.warning("请先选择或输入标题"); - return; + message.warning("请先选择或输入标题") + return } } if (!previewReady) { - message.warning("预览素材正在加载,请稍候"); - return; + message.warning("预览素材正在加载,请稍候") + return } - const ok = await handleGenerate(); + const ok = await handleGenerate() if (ok) { - setCurrentStep(4); + setCurrentStep(4) } }, [ isBatch, @@ -404,7 +381,7 @@ const GeneratePage: React.FC = () => { previewReady, handleGenerate, setCurrentStep, - ]); + ]) /* ── 步骤导航 ── */ const { goNext, goPrev } = useStepNavigation({ @@ -416,16 +393,16 @@ const GeneratePage: React.FC = () => { titleSettings, generated, onOpenCountModal: () => setCountModalOpen(true), - }); + }) /* ── 最终成片 ── */ - const finalVideo = generatedVideos[0]; + const finalVideo = generatedVideos[0] /* ── 布局 class ── */ const layoutClassName = useMemo(() => { - if (currentStep === 3) return "xx-generate-layout step4-layout"; - return "xx-generate-layout full-width"; - }, [currentStep]); + if (currentStep === 3) return "xx-generate-layout step4-layout" + return "xx-generate-layout full-width" + }, [currentStep]) /* ================================================================ 渲染 @@ -435,10 +412,7 @@ const GeneratePage: React.FC = () => {
- +
{/* ════ 步骤3:左侧预览大区域 ════ */} @@ -456,8 +430,7 @@ const GeneratePage: React.FC = () => { size: titleSettings.size, font: titleSettings.font, color: titleSettings.color, - position: titleSettings.position as - "top" | "center" | "bottom" | "custom", + position: titleSettings.position as "top" | "center" | "bottom" | "custom", bold: titleSettings.bold, italic: titleSettings.italic, stroke: titleSettings.stroke, @@ -607,16 +580,10 @@ const GeneratePage: React.FC = () => { justifyContent: "center", }} > - -
- ); -}; + ) +} -export default GeneratePage; +export default GeneratePage