From 8c2d9947e2a160b58b7ebaf4ea419642f3a5aa7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=B5=E5=BA=94?= Date: Sat, 4 Jul 2026 22:23:31 +0800 Subject: [PATCH] =?UTF-8?q?refactor(editing-planner):=20=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E8=B4=A8=E9=87=8F=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 提取 GenerationHistoryModal 组件(EditingPlanner 减少约 85 行) - 修复双重类型转换:as unknown as TaskItem → getTask(res.id) - 删除未使用的 PLAN_STATUS_LABELS import - 补充关键逻辑 JSDoc 注释(loadTemplates/handleAiRecommend/handleGenerate/轮询等) --- .../pages/editing-planner/EditingPlanner.tsx | 142 ++++++------------ .../components/GenerationHistoryModal.tsx | 108 +++++++++++++ 2 files changed, 150 insertions(+), 100 deletions(-) create mode 100644 apps/web/src/pages/editing-planner/components/GenerationHistoryModal.tsx diff --git a/apps/web/src/pages/editing-planner/EditingPlanner.tsx b/apps/web/src/pages/editing-planner/EditingPlanner.tsx index 4e3c55730..8e819f2a1 100644 --- a/apps/web/src/pages/editing-planner/EditingPlanner.tsx +++ b/apps/web/src/pages/editing-planner/EditingPlanner.tsx @@ -30,7 +30,6 @@ import { getMediaAssets, getEditPlanGenerations, MATERIAL_TYPE_LABELS, - PLAN_STATUS_LABELS, aiRecommendClips, generateCover, } from "@/api/editPlans"; @@ -45,6 +44,7 @@ import ClipPropertiesPanel from "./components/ClipPropertiesPanel"; import SaveModal from "./components/SaveModal"; import GenerationProgressModal from "./components/GenerationProgressModal"; import type { GenPhase } from "./components/GenerationProgressModal"; +import GenerationHistoryModal from "./components/GenerationHistoryModal"; import "./EditingPlanner.css"; /* ──────────── 类型 ──────────── */ @@ -167,6 +167,10 @@ const EditingPlanner: React.FC = () => { /* ──────────── 加载 ──────────── */ + /** + * 并行加载模板列表、分类、素材库 + * 首次挂载时调用,三个接口无依赖关系,用 Promise.all 并发 + */ const loadTemplates = useCallback(async () => { setLoadingTemplates(true); try { @@ -189,7 +193,11 @@ const EditingPlanner: React.FC = () => { loadTemplates(); }, [loadTemplates]); - /* 加载模板详情 */ + /** + * 加载模板详情并初始化片段列表 + * 将后端 segments 映射为前端 ClipData,取 duration_min/max 均值作为默认时长 + * 同时还原标题/字幕/BGM 配置 + */ useEffect(() => { if (!loadedTemplateId) return; getEditingTemplate(loadedTemplateId) @@ -316,7 +324,11 @@ const EditingPlanner: React.FC = () => { setClips((prev) => [...prev, newClip]); }; - /* AI 推荐片段 */ + /** + * AI 推荐片段方案 + * 将当前已绑定素材的片段 ID 列表 + 编辑模式 + 目标时长发送给后端 + * 返回的推荐结果直接替换当前片段列表(不保留原手动编排) + */ const handleAiRecommend = async () => { if (!loadedTemplateId) { msg.warning("请先加载模板"); @@ -448,13 +460,16 @@ const EditingPlanner: React.FC = () => { } }; - /* 跳转到一键生成页面,携带当前编辑器配置 */ + /** + * 跳转到一键生成页面 + * 通过 URL SearchParams 传递 edit_plan_id 和完整 planConfig(JSON 序列化) + * 一键生成页面从 params 解析配置,无需重复请求接口 + */ const handleGoToGenerate = () => { if (!loadedTemplateId) { msg.warning("请先加载一个模板"); return; } - // 构建配置对象,传递给一键生成页面 const planConfig = { title_config: { ai_auto_select: titleSettings.aiAutoSelect, @@ -496,6 +511,12 @@ const EditingPlanner: React.FC = () => { navigate(`/app/generate?${params.toString()}`); }; + /** + * 创建生成任务(两步) + * 1. generateFromTemplate — 通知后端基于模板生成视频 + * 2. createGenerationTask — 创建任务记录,返回精简响应 + * 再用 getTask 查询完整 TaskItem 供轮询使用 + */ const handleGenerate = async () => { if (!loadedTemplateId) return; setGenSubmitting(true); @@ -503,7 +524,7 @@ const EditingPlanner: React.FC = () => { await generateFromTemplate(loadedTemplateId, { voiceover_duration: voiceoverDuration || totalDuration, }); - const task = await createGenerationTask({ + const res = await createGenerationTask({ template_id: loadedTemplateId, asset_ids: clips .map((c) => c.media_asset_id) @@ -511,7 +532,9 @@ const EditingPlanner: React.FC = () => { title_ids: [], voice_ids: [], }); - setGenTask(task as unknown as TaskItem); + /* 创建接口返回的是精简响应,需查询完整 TaskItem 用于轮询 */ + const task = await getTask(res.id); + setGenTask(task); setGenPhase("progress"); msg.info("生成任务已创建"); } catch { @@ -521,7 +544,11 @@ const EditingPlanner: React.FC = () => { } }; - /* 轮询任务状态 */ + /** + * 轮询生成任务状态(每 3 秒) + * 仅在 genPhase === "progress" 且有任务 ID 时启动 + * 任务完成/失败时自动停止轮询 + */ useEffect(() => { if (genPhase !== "progress" || !genTask?.id) return; const timer = setInterval(async () => { @@ -762,98 +789,13 @@ const EditingPlanner: React.FC = () => { onClose={handleCancelGen} /> - {/* ═══ 生成历史 Modal ═══ */} - {genHistoryOpen && ( -
setGenHistoryOpen(false)}> -
e.stopPropagation()} - > -
-

📋 生成历史

- -
-
- {genHistoryLoading ? ( -
- 加载中... -
- ) : genHistory.length === 0 ? ( -
- 暂无生成记录 -
- ) : ( - - - - - - - - - - - {genHistory.map((gen) => ( - - - - - - - ))} - -
任务ID状态创建时间更新时间
- {gen.generation_task_id.slice(0, 8)}... - - - {PLAN_STATUS_LABELS[gen.status] || gen.status} - - - {gen.created_at ? new Date(gen.created_at).toLocaleString("zh-CN") : "—"} - - {gen.updated_at ? new Date(gen.updated_at).toLocaleString("zh-CN") : "—"} -
- )} -
-
- -
-
-
- )} + {/* ═══ 生成历史弹窗 ═══ */} + setGenHistoryOpen(false)} + /> ); }; diff --git a/apps/web/src/pages/editing-planner/components/GenerationHistoryModal.tsx b/apps/web/src/pages/editing-planner/components/GenerationHistoryModal.tsx new file mode 100644 index 000000000..447803986 --- /dev/null +++ b/apps/web/src/pages/editing-planner/components/GenerationHistoryModal.tsx @@ -0,0 +1,108 @@ +/** + * 生成历史弹窗 — 展示当前剪辑计划的生成任务记录 + * 从 EditingPlanner 拆分,避免主文件过大 + */ +import React from "react"; +import type { EditPlanGeneration } from "@/api/editPlans"; +import { PLAN_STATUS_LABELS } from "@/api/editPlans"; + +interface GenerationHistoryModalProps { + open: boolean; + loading: boolean; + history: EditPlanGeneration[]; + onClose: () => void; +} + +/** 状态标签颜色映射 */ +const STATUS_STYLES: Record = { + completed: { bg: "#f6ffed", color: "#52c41a" }, + failed: { bg: "#fff2f0", color: "#ff4d4f" }, + rendering: { bg: "#e6f7ff", color: "#1890ff" }, +}; +const DEFAULT_STATUS_STYLE = { bg: "#fafafa", color: "#666" }; + +const GenerationHistoryModal: React.FC = ({ + open, + loading, + history, + onClose, +}) => { + if (!open) return null; + + return ( +
+
e.stopPropagation()} + > +
+

📋 生成历史

+ +
+
+ {loading ? ( +
+ 加载中... +
+ ) : history.length === 0 ? ( +
+ 暂无生成记录 +
+ ) : ( + + + + + + + + + + + {history.map((gen) => { + const style = STATUS_STYLES[gen.status] || DEFAULT_STATUS_STYLE; + return ( + + + + + + + ); + })} + +
任务ID状态创建时间更新时间
+ {gen.generation_task_id.slice(0, 8)}... + + + {PLAN_STATUS_LABELS[gen.status] || gen.status} + + + {gen.created_at ? new Date(gen.created_at).toLocaleString("zh-CN") : "—"} + + {gen.updated_at ? new Date(gen.updated_at).toLocaleString("zh-CN") : "—"} +
+ )} +
+
+ +
+
+
+ ); +}; + +export default GenerationHistoryModal;