diff --git a/apps/web/src/pages/edit-plans/EditPlans.tsx b/apps/web/src/pages/edit-plans/EditPlans.tsx index 26fc1f736..4ac6116e0 100755 --- a/apps/web/src/pages/edit-plans/EditPlans.tsx +++ b/apps/web/src/pages/edit-plans/EditPlans.tsx @@ -26,6 +26,7 @@ import { ThunderboltOutlined, CopyOutlined, UnorderedListOutlined, + StopOutlined, } from "@ant-design/icons"; import type { ColumnsType } from "antd/es/table"; import { @@ -82,6 +83,11 @@ const STATUS_CONFIG: Record< color: "error", icon: , }, + cancelled: { + label: "已取消", + color: "default", + icon: , + }, }; /* ──────────── 工具函数 ──────────── */ diff --git a/apps/web/src/pages/editing-planner/EditingPlanner.tsx b/apps/web/src/pages/editing-planner/EditingPlanner.tsx index c57a46603..e2635d2f7 100755 --- a/apps/web/src/pages/editing-planner/EditingPlanner.tsx +++ b/apps/web/src/pages/editing-planner/EditingPlanner.tsx @@ -89,8 +89,8 @@ import ClipPropertiesPanel from "./components/ClipPropertiesPanel"; import EditorClipList from "./components/EditorClipList"; import BgmSelector from "./components/BgmSelector"; import SubtitleStylePanel from "./components/SubtitleStylePanel"; -import type { SubtitleStyleConfig } from "./components/SubtitleStylePanel"; -import { DEFAULT_SUBTITLE_STYLE } from "./components/SubtitleStylePanel"; +import type { SubtitleStyleConfig } from "./types/subtitle"; +import { DEFAULT_SUBTITLE_STYLE } from "./types/subtitle"; import TransitionSelector from "./components/TransitionSelector"; import SpeedPanel from "./components/SpeedPanel"; import TtsPanel from "./components/TtsPanel"; @@ -626,11 +626,14 @@ const EditingPlanner: React.FC = () => { if (selectedClipId === clipId) setSelectedClipId(null); }; - const handleClipUpdate = (clipId: string, data: Partial) => { - setClips((prev) => - prev.map((c) => (c.id === clipId ? { ...c, ...data } : c)), - ); - }; + const handleClipUpdate = useCallback( + (clipId: string, data: Partial) => { + setClips((prev) => + prev.map((c) => (c.id === clipId ? { ...c, ...data } : c)), + ); + }, + [setClips], + ); /** * 添加片段(不绑定任何素材) @@ -738,7 +741,7 @@ const EditingPlanner: React.FC = () => { } // 同时更新全局默认转场(供新片段使用) }, - [transitionTargetClipId], + [transitionTargetClipId, handleClipUpdate], ); /* ── 打开转场选择器 ── */ @@ -754,7 +757,7 @@ const EditingPlanner: React.FC = () => { handleClipUpdate(speedTargetClipId, { speed: config }); } }, - [speedTargetClipId], + [speedTargetClipId, handleClipUpdate], ); /* ── 打开调速面板 ── */ @@ -769,7 +772,7 @@ const EditingPlanner: React.FC = () => { if (!ttsTargetClipId) return; handleClipUpdate(ttsTargetClipId, { tts_config: ttsConfig }); }, - [ttsTargetClipId], + [ttsTargetClipId, handleClipUpdate], ); /* ── 打开 TTS 配音面板 ── */ @@ -782,7 +785,7 @@ const EditingPlanner: React.FC = () => { const handleApplySpeedAll = useCallback((config: SpeedConfig) => { setClips((prev) => prev.map((c) => ({ ...c, speed: { ...config } }))); message.success("已应用到所有片段"); - }, []); + }, [setClips]); /* ── 水印配置变更 ── */ const handleWatermarkChange = useCallback((config: WatermarkConfig) => { @@ -1214,9 +1217,11 @@ const EditingPlanner: React.FC = () => { message.info("取消请求已提交,正在停止..."); // 停止本地轮询,等待服务端状态通过轮询或下一次检查返回 cancelled // 这里不立即停止,让轮询检测到 cancelled 状态后再收尾,以确保服务端确实处理了 - } catch (err: any) { + } catch (err: unknown) { console.error("[取消生成失败]", err); - const msg = err?.response?.data?.detail || "取消失败,请稍后重试"; + const msg = + (err as { response?: { data?: { detail?: string } } })?.response + ?.data?.detail || "取消失败,请稍后重试"; message.error(msg); } }, @@ -1443,7 +1448,7 @@ const EditingPlanner: React.FC = () => { if (idx < clips.length - 1) handleClipReorder(idx, idx + 1); }} onRemove={handleClipRemove} - onAdd={handleAddClip} + onAdd={() => handleAddClip("pip", 3)} /> )} diff --git a/apps/web/src/pages/editing-planner/components/SubtitleStylePanel.tsx b/apps/web/src/pages/editing-planner/components/SubtitleStylePanel.tsx old mode 100644 new mode 100755 index 5f75536cf..40aeff7d0 --- a/apps/web/src/pages/editing-planner/components/SubtitleStylePanel.tsx +++ b/apps/web/src/pages/editing-planner/components/SubtitleStylePanel.tsx @@ -5,46 +5,8 @@ import React from "react"; import { Drawer, Slider, ColorPicker, Select } from "antd"; import type { Color } from "antd/es/color-picker"; - -/* ──────────── 类型 ──────────── */ - -export type SubtitleMode = "manual" | "asr"; - -export interface SubtitleStyleConfig { - /** 是否启用字幕 */ - enabled: boolean; - /** 字幕模式:手动输入 / ASR 自动识别 */ - mode: SubtitleMode; - /** 字体大小 px */ - fontSize: number; - /** 字体颜色 */ - fontColor: string; - /** 描边 */ - stroke: boolean; - /** 阴影 */ - shadow: boolean; - /** 字幕位置 */ - position: "top" | "center" | "bottom"; - /** 字体 */ - font: string; - /** 动画效果 */ - animation: string; - /** ASR 语言(仅 ASR 模式) */ - asrLanguage: "zh" | "en"; -} - -export const DEFAULT_SUBTITLE_STYLE: SubtitleStyleConfig = { - enabled: true, - mode: "asr", - fontSize: 16, - fontColor: "#ffffff", - stroke: true, - shadow: false, - position: "bottom", - font: "思源黑体", - animation: "none", - asrLanguage: "zh", -}; +import type { SubtitleMode, SubtitleStyleConfig } from "../types/subtitle"; +import { DEFAULT_SUBTITLE_STYLE } from "../types/subtitle"; /* ──────────── 选项常量 ──────────── */ diff --git a/apps/web/src/pages/editing-planner/types.ts b/apps/web/src/pages/editing-planner/types.ts old mode 100644 new mode 100755 index 110bffcdb..3271f0e9b --- a/apps/web/src/pages/editing-planner/types.ts +++ b/apps/web/src/pages/editing-planner/types.ts @@ -512,6 +512,8 @@ export interface ClipData { type: ClipType; // 片段类型:voice(口播)或 pip(画中画) duration: number; // 时长(秒) startOffset: number; // 仅 voice 类型:在口播素材中的起始时间(秒) + /** 素材库素材 ID(main/pip 类型片段使用) */ + media_asset_id?: string; // 保留兼容字段(后端序列化需要) template_segment_id?: string; script_text?: string; diff --git a/apps/web/src/pages/editing-planner/types/subtitle.ts b/apps/web/src/pages/editing-planner/types/subtitle.ts new file mode 100755 index 000000000..336effe1f --- /dev/null +++ b/apps/web/src/pages/editing-planner/types/subtitle.ts @@ -0,0 +1,46 @@ +/** + * 字幕样式相关类型与常量 + * 单独抽离以满足 react-refresh/only-export-components 规则 + */ + +/* ──────────── 类型 ──────────── */ + +export type SubtitleMode = "manual" | "asr"; + +export interface SubtitleStyleConfig { + /** 是否启用字幕 */ + enabled: boolean; + /** 字幕模式:手动输入 / ASR 自动识别 */ + mode: SubtitleMode; + /** 字体大小 px */ + fontSize: number; + /** 字体颜色 */ + fontColor: string; + /** 描边 */ + stroke: boolean; + /** 阴影 */ + shadow: boolean; + /** 字幕位置 */ + position: "top" | "center" | "bottom"; + /** 字体 */ + font: string; + /** 动画效果 */ + animation: string; + /** ASR 语言(仅 ASR 模式) */ + asrLanguage: "zh" | "en"; +} + +/* ──────────── 默认值 ──────────── */ + +export const DEFAULT_SUBTITLE_STYLE: SubtitleStyleConfig = { + enabled: true, + mode: "asr", + fontSize: 16, + fontColor: "#ffffff", + stroke: true, + shadow: false, + position: "bottom", + font: "思源黑体", + animation: "none", + asrLanguage: "zh", +};