From f8412d116aa0e870cf2b17e74341509e8b89a743 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 27 Jul 2026 00:35:27 +0800 Subject: [PATCH 1/2] =?UTF-8?q?refactor(editing-planner):=20=E6=B7=B1?= =?UTF-8?q?=E5=8C=96=20EditingPlanner=20=E6=8B=86=E5=88=86=EF=BC=8C?= =?UTF-8?q?=E6=8A=BD=E7=A6=BB=E5=85=A8=E5=B1=80=E9=85=8D=E7=BD=AE=E4=B8=8E?= =?UTF-8?q?=E9=85=8D=E9=9F=B3=E7=B4=A0=E6=9D=90=20Hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 抽 useGlobalSettings: 集中管理 9 个全局配置状态(标题/字幕/BGM/水印/片头片尾/画中画/滤镜/绿幕/贴纸/封面) - 抽 useVoiceMaterials: 配音素材数据加载(queryKey 与 VoiceMaterialLibrary 共享缓存) - 主文件 413→340 行 (-18%) --- .../pages/editing-planner/EditingPlanner.tsx | 169 +++++------------- .../hooks/useGlobalSettings.ts | 121 +++++++++++++ .../hooks/useVoiceMaterials.ts | 32 ++++ 3 files changed, 201 insertions(+), 121 deletions(-) create mode 100644 apps/web/src/pages/editing-planner/hooks/useGlobalSettings.ts create mode 100644 apps/web/src/pages/editing-planner/hooks/useVoiceMaterials.ts diff --git a/apps/web/src/pages/editing-planner/EditingPlanner.tsx b/apps/web/src/pages/editing-planner/EditingPlanner.tsx index df4b05940..c8a373275 100644 --- a/apps/web/src/pages/editing-planner/EditingPlanner.tsx +++ b/apps/web/src/pages/editing-planner/EditingPlanner.tsx @@ -1,15 +1,21 @@ /** * 模板编辑器 — 制作/编辑剪辑模板 * 四行布局:顶栏(42px) → 模式栏(48px) → 三栏主体 → 底栏(40px) + * + * 主组件仅保留 Hook 组装与整体布局 + * 全局配置 → hooks/useGlobalSettings + * 配音素材 → hooks/useVoiceMaterials + * 撤销重做 → hooks/useUndoRedo + * 抽屉管理 → hooks/useEditorDrawers + * 播放控制 → hooks/usePlaybackControl + * 片段操作 → hooks/useClipOperations + * 模板管理 → hooks/useTemplateManagement */ import React, { useState } from "react" import { useSearchParams } from "react-router-dom" -import { useQuery } from "@tanstack/react-query" import { MODE_LABELS } from "@/api/editing-planner" import { MODE_LIST } from "./constants" -import type { MediaAsset, TitleConfig } from "@/api/template-editor" -import { ensureDefaultLibrary, getAssetsByKind, type AssetItem } from "@/api/assets" -import { getOrCreateDefaultProject } from "@/api/projects" +import type { MediaAsset } from "@/api/template-editor" import MediaPanel from "./components/MediaPanel" import PreviewPlayer from "./components/PreviewPlayer" @@ -26,36 +32,12 @@ import { useEditorDrawers } from "./hooks/useEditorDrawers" import { usePlaybackControl } from "./hooks/usePlaybackControl" import { useClipOperations } from "./hooks/useClipOperations" import { useTemplateManagement, FILTER_CATEGORIES } from "./hooks/useTemplateManagement" +import { useGlobalSettings } from "./hooks/useGlobalSettings" +import { useVoiceMaterials } from "./hooks/useVoiceMaterials" -import type { - ClipData, - WatermarkConfig, - IntroOutroConfig, - PipConfig, - FilterConfig, - ChromaKeyConfig, - StickerConfig, - CoverConfig, -} from "./types" -import { - DEFAULT_WATERMARK, - DEFAULT_INTRO_OUTRO, - DEFAULT_PIP_CONFIG, - DEFAULT_FILTER_CONFIG, - DEFAULT_CHROMA_KEY_CONFIG, - DEFAULT_STICKER_CONFIG, - DEFAULT_COVER_CONFIG, -} from "./types" - -import type { SubtitleStyleConfig } from "./types/subtitle" -import { DEFAULT_SUBTITLE_STYLE } from "./types/subtitle" -import { DEFAULT_BGM_MIX_CONFIG, type BgmMixConfig } from "@/api/bgm" +import type { ClipData } from "./types" import "./EditingPlanner.css" -/* ──────────── 常量 ──────────── */ - -/* ──────────── 组件 ──────────── */ - const EditingPlanner: React.FC = () => { const [searchParams] = useSearchParams() const urlTemplateId = searchParams.get("templateId") || "" @@ -72,50 +54,29 @@ const EditingPlanner: React.FC = () => { reset: resetClips, } = useUndoRedo([]) - /* ── 全局配置 state ── */ - const [titleConfig, setTitleConfig] = useState({ - ai_auto_select: false, - content: "", - position: "bottom", - font_preset: "思源黑体", - font_size: 28, - font_color: "#ffffff", - }) - - const [subtitleSettings, setSubtitleSettings] = useState({ - ...DEFAULT_SUBTITLE_STYLE, - }) - - const [bgmSettings, setBgmSettings] = useState({ - ...DEFAULT_BGM_MIX_CONFIG, - }) - - const [watermarkSettings, setWatermarkSettings] = useState({ - ...DEFAULT_WATERMARK, - }) - const [introOutroSettings, setIntroOutroSettings] = useState({ - ...DEFAULT_INTRO_OUTRO, - }) - - const [pipSettings, setPipSettings] = useState({ - ...DEFAULT_PIP_CONFIG, - }) - - const [filterSettings, setFilterSettings] = useState({ - ...DEFAULT_FILTER_CONFIG, - }) - - const [chromaKeySettings, setChromaKeySettings] = useState({ - ...DEFAULT_CHROMA_KEY_CONFIG, - }) - - const [stickerSettings, setStickerSettings] = useState({ - ...DEFAULT_STICKER_CONFIG, - }) - - const [coverConfig, setCoverConfig] = useState({ - ...DEFAULT_COVER_CONFIG, - }) + /* ── 全局配置 ── */ + const { + titleConfig, + setTitleConfig, + subtitleSettings, + setSubtitleSettings, + bgmSettings, + setBgmSettings, + watermarkSettings, + setWatermarkSettings, + introOutroSettings, + setIntroOutroSettings, + pipSettings, + setPipSettings, + filterSettings, + setFilterSettings, + chromaKeySettings, + setChromaKeySettings, + stickerSettings, + setStickerSettings, + coverConfig, + setCoverConfig, + } = useGlobalSettings() /* ── 右侧栏 Tab ── */ const [rightTab, setRightTab] = useState<"properties" | "clips">("properties") @@ -128,18 +89,9 @@ const EditingPlanner: React.FC = () => { setSelectedAssetIds(ids) } - /* ── 配音素材(queryKey 与 VoiceMaterialLibrary 共享缓存) ── */ - const voiceMaterialsQuery = useQuery({ - queryKey: ["assets", "voice"], - queryFn: async () => { - const project = await getOrCreateDefaultProject() - await ensureDefaultLibrary({ project_id: project.id, kind: "voice" }) - const assets = await getAssetsByKind("voice") - return assets - }, - staleTime: 30_000, - }) - const voiceMaterials: AssetItem[] = voiceMaterialsQuery.data ?? [] + /* ── 配音素材 ── */ + const { voiceMaterials, loading: voiceMaterialsLoading, refetch: refetchVoiceMaterials } = + useVoiceMaterials() /* ── 派生计算 ── */ const totalDuration = clips.reduce((sum, c) => sum + c.duration, 0) @@ -179,31 +131,6 @@ const EditingPlanner: React.FC = () => { coverConfig, }) - /* ── 配置变更 handlers ── */ - const handleWatermarkChange = (config: WatermarkConfig) => { - setWatermarkSettings(config) - } - - const handleIntroOutroChange = (config: IntroOutroConfig) => { - setIntroOutroSettings(config) - } - - const handlePipChange = (config: PipConfig) => { - setPipSettings(config) - } - - const handleFilterChange = (config: FilterConfig) => { - setFilterSettings(config) - } - - const handleChromaKeyChange = (config: ChromaKeyConfig) => { - setChromaKeySettings(config) - } - - const handleStickerChange = (config: StickerConfig) => { - setStickerSettings(config) - } - /* ──────────── 渲染 ──────────── */ return ( @@ -294,15 +221,15 @@ const EditingPlanner: React.FC = () => { totalDuration={totalDuration} currentMode={tpl.currentMode} onSubtitleSettingsChange={(partial) => - setSubtitleSettings((prev) => ({ ...prev, ...partial }) as SubtitleStyleConfig) + setSubtitleSettings((prev) => ({ ...prev, ...partial })) } onBgmSettingsChange={(partial) => setBgmSettings((prev) => ({ ...prev, ...partial }))} onClipUpdate={clipOps.handleClipUpdate} onOpenBgmDrawer={() => drawers.setBgmDrawerOpen(true)} onOpenSubtitleDrawer={() => drawers.setSubtitleDrawerOpen(true)} voiceMaterials={voiceMaterials} - voiceMaterialsLoading={voiceMaterialsQuery.isLoading} - onRefreshVoiceMaterials={() => voiceMaterialsQuery.refetch()} + voiceMaterialsLoading={voiceMaterialsLoading} + onRefreshVoiceMaterials={refetchVoiceMaterials} onClipVoiceSelect={clipOps.handleClipVoiceSelect} onOpenTransitionDrawer={drawers.openTransitionDrawer} onOpenSpeedDrawer={drawers.openSpeedDrawer} @@ -382,28 +309,28 @@ const EditingPlanner: React.FC = () => { onCloseTtsDrawer={() => drawers.setTtsDrawerOpen(false)} watermarkDrawerOpen={drawers.watermarkDrawerOpen} watermarkSettings={watermarkSettings} - onWatermarkChange={handleWatermarkChange} + onWatermarkChange={setWatermarkSettings} onCloseWatermarkDrawer={() => drawers.setWatermarkDrawerOpen(false)} introOutroDrawerOpen={drawers.introOutroDrawerOpen} introOutroSettings={introOutroSettings} - onIntroOutroChange={handleIntroOutroChange} + onIntroOutroChange={setIntroOutroSettings} onCloseIntroOutroDrawer={() => drawers.setIntroOutroDrawerOpen(false)} pipDrawerOpen={drawers.pipDrawerOpen} pipSettings={pipSettings} totalDuration={totalDuration} - onPipChange={handlePipChange} + onPipChange={setPipSettings} onClosePipDrawer={() => drawers.setPipDrawerOpen(false)} filterDrawerOpen={drawers.filterDrawerOpen} filterSettings={filterSettings} - onFilterChange={handleFilterChange} + onFilterChange={setFilterSettings} onCloseFilterDrawer={() => drawers.setFilterDrawerOpen(false)} chromaKeyDrawerOpen={drawers.chromaKeyDrawerOpen} chromaKeySettings={chromaKeySettings} - onChromaKeyChange={handleChromaKeyChange} + onChromaKeyChange={setChromaKeySettings} onCloseChromaKeyDrawer={() => drawers.setChromaKeyDrawerOpen(false)} stickerDrawerOpen={drawers.stickerDrawerOpen} stickerSettings={stickerSettings} - onStickerChange={handleStickerChange} + onStickerChange={setStickerSettings} onCloseStickerDrawer={() => drawers.setStickerDrawerOpen(false)} /> diff --git a/apps/web/src/pages/editing-planner/hooks/useGlobalSettings.ts b/apps/web/src/pages/editing-planner/hooks/useGlobalSettings.ts new file mode 100644 index 000000000..27f653743 --- /dev/null +++ b/apps/web/src/pages/editing-planner/hooks/useGlobalSettings.ts @@ -0,0 +1,121 @@ +/** + * EditingPlanner 全局配置状态管理 + * 集中管理 9 个全局配置:标题/字幕/BGM/水印/片头片尾/画中画/滤镜/绿幕/贴纸/封面 + */ +import { useState } from "react" +import type { TitleConfig } from "@/api/template-editor" +import type { + WatermarkConfig, + IntroOutroConfig, + PipConfig, + FilterConfig, + ChromaKeyConfig, + StickerConfig, + CoverConfig, +} from "../types" +import { + DEFAULT_WATERMARK, + DEFAULT_INTRO_OUTRO, + DEFAULT_PIP_CONFIG, + DEFAULT_FILTER_CONFIG, + DEFAULT_CHROMA_KEY_CONFIG, + DEFAULT_STICKER_CONFIG, + DEFAULT_COVER_CONFIG, +} from "../types" +import type { SubtitleStyleConfig } from "../types/subtitle" +import { DEFAULT_SUBTITLE_STYLE } from "../types/subtitle" +import { DEFAULT_BGM_MIX_CONFIG, type BgmMixConfig } from "@/api/bgm" + +export interface GlobalSettings { + titleConfig: TitleConfig + setTitleConfig: (config: TitleConfig | ((prev: TitleConfig) => TitleConfig)) => void + subtitleSettings: SubtitleStyleConfig + setSubtitleSettings: ( + settings: SubtitleStyleConfig | ((prev: SubtitleStyleConfig) => SubtitleStyleConfig), + ) => void + bgmSettings: BgmMixConfig + setBgmSettings: (settings: BgmMixConfig | ((prev: BgmMixConfig) => BgmMixConfig)) => void + watermarkSettings: WatermarkConfig + setWatermarkSettings: (config: WatermarkConfig) => void + introOutroSettings: IntroOutroConfig + setIntroOutroSettings: (config: IntroOutroConfig) => void + pipSettings: PipConfig + setPipSettings: (config: PipConfig) => void + filterSettings: FilterConfig + setFilterSettings: (config: FilterConfig) => void + chromaKeySettings: ChromaKeyConfig + setChromaKeySettings: (config: ChromaKeyConfig) => void + stickerSettings: StickerConfig + setStickerSettings: (config: StickerConfig) => void + coverConfig: CoverConfig + setCoverConfig: (config: CoverConfig | ((prev: CoverConfig) => CoverConfig)) => void +} + +export const useGlobalSettings = (): GlobalSettings => { + const [titleConfig, setTitleConfig] = useState({ + ai_auto_select: false, + content: "", + position: "bottom", + font_preset: "思源黑体", + font_size: 28, + font_color: "#ffffff", + }) + + const [subtitleSettings, setSubtitleSettings] = useState({ + ...DEFAULT_SUBTITLE_STYLE, + }) + + const [bgmSettings, setBgmSettings] = useState({ + ...DEFAULT_BGM_MIX_CONFIG, + }) + + const [watermarkSettings, setWatermarkSettings] = useState({ + ...DEFAULT_WATERMARK, + }) + const [introOutroSettings, setIntroOutroSettings] = useState({ + ...DEFAULT_INTRO_OUTRO, + }) + + const [pipSettings, setPipSettings] = useState({ + ...DEFAULT_PIP_CONFIG, + }) + + const [filterSettings, setFilterSettings] = useState({ + ...DEFAULT_FILTER_CONFIG, + }) + + const [chromaKeySettings, setChromaKeySettings] = useState({ + ...DEFAULT_CHROMA_KEY_CONFIG, + }) + + const [stickerSettings, setStickerSettings] = useState({ + ...DEFAULT_STICKER_CONFIG, + }) + + const [coverConfig, setCoverConfig] = useState({ + ...DEFAULT_COVER_CONFIG, + }) + + return { + titleConfig, + setTitleConfig, + subtitleSettings, + setSubtitleSettings, + bgmSettings, + setBgmSettings, + watermarkSettings, + setWatermarkSettings, + introOutroSettings, + setIntroOutroSettings, + pipSettings, + setPipSettings, + filterSettings, + setFilterSettings, + chromaKeySettings, + setChromaKeySettings, + stickerSettings, + setStickerSettings, + coverConfig, + setCoverConfig, + } +} diff --git a/apps/web/src/pages/editing-planner/hooks/useVoiceMaterials.ts b/apps/web/src/pages/editing-planner/hooks/useVoiceMaterials.ts new file mode 100644 index 000000000..5b7227157 --- /dev/null +++ b/apps/web/src/pages/editing-planner/hooks/useVoiceMaterials.ts @@ -0,0 +1,32 @@ +/** + * EditingPlanner 配音素材数据加载 + * queryKey 与 VoiceMaterialLibrary 共享缓存 + */ +import { useQuery } from "@tanstack/react-query" +import { ensureDefaultLibrary, getAssetsByKind, type AssetItem } from "@/api/assets" +import { getOrCreateDefaultProject } from "@/api/projects" + +export interface UseVoiceMaterialsReturn { + voiceMaterials: AssetItem[] + loading: boolean + refetch: () => Promise +} + +export const useVoiceMaterials = (): UseVoiceMaterialsReturn => { + const query = useQuery({ + queryKey: ["assets", "voice"], + queryFn: async () => { + const project = await getOrCreateDefaultProject() + await ensureDefaultLibrary({ project_id: project.id, kind: "voice" }) + const assets = await getAssetsByKind("voice") + return assets + }, + staleTime: 30_000, + }) + + return { + voiceMaterials: query.data ?? [], + loading: query.isLoading, + refetch: query.refetch, + } +} -- 2.54.0 From 7b1ce1eac6854f17c7ebb8003c217e489339e6f2 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 27 Jul 2026 08:39:47 +0800 Subject: [PATCH 2/2] style: prettier format fix --- apps/web/src/pages/editing-planner/EditingPlanner.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/web/src/pages/editing-planner/EditingPlanner.tsx b/apps/web/src/pages/editing-planner/EditingPlanner.tsx index c8a373275..5e0f08a86 100644 --- a/apps/web/src/pages/editing-planner/EditingPlanner.tsx +++ b/apps/web/src/pages/editing-planner/EditingPlanner.tsx @@ -90,8 +90,11 @@ const EditingPlanner: React.FC = () => { } /* ── 配音素材 ── */ - const { voiceMaterials, loading: voiceMaterialsLoading, refetch: refetchVoiceMaterials } = - useVoiceMaterials() + const { + voiceMaterials, + loading: voiceMaterialsLoading, + refetch: refetchVoiceMaterials, + } = useVoiceMaterials() /* ── 派生计算 ── */ const totalDuration = clips.reduce((sum, c) => sum + c.duration, 0) -- 2.54.0