From 5d3ef34857e588eeb2109ba3e57fdbb248c805f8 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 14 Sep 2026 21:23:24 +0800 Subject: [PATCH] style: format GeneratePage.tsx with prettier (#1899 #1900) --- apps/web/src/pages/generate/GeneratePage.tsx | 321 +++++++++++-------- 1 file changed, 188 insertions(+), 133 deletions(-) diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index 27bb8620e..fb46dcbbf 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -4,37 +4,43 @@ * 模板选择已删除,模板在后台自动加载/选择(后端 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, @@ -83,104 +89,120 @@ 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 - prevVoiceSyncRef.current = { voice: selectedVoice, batch: isBatch, perVideo: voiceModePerVideo } - if (!voiceChanged && !modeChanged) return - if (!isBatch) return + 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 (!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(null) - const ttsAbortRef = useRef(null) - const variant0Title = isBatch ? previewTitles?.[0] || "" : "" + const [previewVoiceAudioUrl, setPreviewVoiceAudioUrl] = useState< + string | null + >(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() - } - }, [selectedVoice, selectedClonedVoice, titleSettings.title, variant0Title, isBatch, voiceMaterials]) + cancelled = true; + controller.abort(); + }; + }, [ + selectedVoice, + selectedClonedVoice, + titleSettings.title, + 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( @@ -189,23 +211,25 @@ 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, @@ -222,7 +246,7 @@ const GeneratePage: React.FC = () => { voiceLibraryId: batchVoiceLibraryId, voiceLibraryIds: voiceLibraryIds || [], voiceModePerVideo, - }) + }); /* ── 批量变体配音预览 URL ── */ const variantVoiceAudioUrls = useVariantVoicePreview({ @@ -233,29 +257,33 @@ 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 { @@ -297,31 +325,37 @@ 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, @@ -333,33 +367,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, @@ -370,7 +404,7 @@ const GeneratePage: React.FC = () => { previewReady, handleGenerate, setCurrentStep, - ]) + ]); /* ── 步骤导航 ── */ const { goNext, goPrev } = useStepNavigation({ @@ -382,16 +416,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]); /* ================================================================ 渲染 @@ -401,7 +435,10 @@ const GeneratePage: React.FC = () => {
- +
{/* ════ 步骤3:左侧预览大区域 ════ */} @@ -419,7 +456,8 @@ 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, @@ -433,7 +471,12 @@ const GeneratePage: React.FC = () => {

🎬 {previewCount} 个视频预览

- + 实时预览,勾选要生成的视频
@@ -556,11 +599,24 @@ const GeneratePage: React.FC = () => { }} poster={finalVideo.thumbnail_url || undefined} /> -
- -
- ) -} - -export default GeneratePage + ); +}; +export default GeneratePage;