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",
+};