refactor(editing-planner): 代码质量整理
Deploy / Deploy Staging (push) Failing after 110h30m20s
CI/CD Pipeline / Frontend Lint (push) Failing after 110h30m52s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 110h30m59s
Deploy / Production Browser E2E (push) Failing after 1769h4m56s
Deploy / Staging E2E Tests (push) Failing after 1769h4m58s
Deploy / Build Production Runtime Images (push) Failing after 1769h5m0s
Deploy / Deploy Production (push) Failing after 1769h36m24s

- 提取 GenerationHistoryModal 组件(EditingPlanner 减少约 85 行)
- 修复双重类型转换:as unknown as TaskItem → getTask(res.id)
- 删除未使用的 PLAN_STATUS_LABELS import
- 补充关键逻辑 JSDoc 注释(loadTemplates/handleAiRecommend/handleGenerate/轮询等)
This commit is contained in:
灵应
2026-07-04 22:23:31 +08:00
parent b4f4d4ffad
commit 8c2d9947e2
2 changed files with 150 additions and 100 deletions
@@ -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 && (
<div className="ep-modal-overlay" onClick={() => setGenHistoryOpen(false)}>
<div
className="ep-modal"
style={{ width: 520 }}
onClick={(e) => e.stopPropagation()}
>
<div className="ep-modal-header">
<h3>📋 生成历史</h3>
<button
className="ep-modal-close"
onClick={() => setGenHistoryOpen(false)}
>
✕
</button>
</div>
<div className="ep-modal-body" style={{ maxHeight: 400, overflowY: "auto" }}>
{genHistoryLoading ? (
<div style={{ textAlign: "center", padding: 24, color: "#999" }}>
加载中...
</div>
) : genHistory.length === 0 ? (
<div style={{ textAlign: "center", padding: 24, color: "#999" }}>
暂无生成记录
</div>
) : (
<table style={{ width: "100%", borderCollapse: "collapse" }}>
<thead>
<tr style={{ borderBottom: "1px solid #e8e8e8", textAlign: "left" }}>
<th style={{ padding: "8px 12px", fontSize: 12, color: "#666" }}>任务ID</th>
<th style={{ padding: "8px 12px", fontSize: 12, color: "#666" }}>状态</th>
<th style={{ padding: "8px 12px", fontSize: 12, color: "#666" }}>创建时间</th>
<th style={{ padding: "8px 12px", fontSize: 12, color: "#666" }}>更新时间</th>
</tr>
</thead>
<tbody>
{genHistory.map((gen) => (
<tr key={gen.id} style={{ borderBottom: "1px solid #f0f0f0" }}>
<td style={{ padding: "8px 12px", fontSize: 13, fontFamily: "monospace" }}>
{gen.generation_task_id.slice(0, 8)}...
</td>
<td style={{ padding: "8px 12px", fontSize: 13 }}>
<span
style={{
padding: "2px 8px",
borderRadius: 4,
fontSize: 12,
background:
gen.status === "completed"
? "#f6ffed"
: gen.status === "failed"
? "#fff2f0"
: gen.status === "rendering"
? "#e6f7ff"
: "#fafafa",
color:
gen.status === "completed"
? "#52c41a"
: gen.status === "failed"
? "#ff4d4f"
: gen.status === "rendering"
? "#1890ff"
: "#666",
}}
>
{PLAN_STATUS_LABELS[gen.status] || gen.status}
</span>
</td>
<td style={{ padding: "8px 12px", fontSize: 13, color: "#666" }}>
{gen.created_at ? new Date(gen.created_at).toLocaleString("zh-CN") : "—"}
</td>
<td style={{ padding: "8px 12px", fontSize: 13, color: "#666" }}>
{gen.updated_at ? new Date(gen.updated_at).toLocaleString("zh-CN") : "—"}
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
<div className="ep-modal-footer">
<button
className="ep-btn ep-btn-secondary"
onClick={() => setGenHistoryOpen(false)}
>
关闭
</button>
</div>
</div>
</div>
)}
{/* ═══ 生成历史弹窗 ═══ */}
<GenerationHistoryModal
open={genHistoryOpen}
loading={genHistoryLoading}
history={genHistory}
onClose={() => setGenHistoryOpen(false)}
/>
</div>
);
};
@@ -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<string, { bg: string; color: string }> = {
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<GenerationHistoryModalProps> = ({
open,
loading,
history,
onClose,
}) => {
if (!open) return null;
return (
<div className="ep-modal-overlay" onClick={onClose}>
<div
className="ep-modal"
style={{ width: 520 }}
onClick={(e) => e.stopPropagation()}
>
<div className="ep-modal-header">
<h3>📋 生成历史</h3>
<button className="ep-modal-close" onClick={onClose}>
✕
</button>
</div>
<div className="ep-modal-body" style={{ maxHeight: 400, overflowY: "auto" }}>
{loading ? (
<div style={{ textAlign: "center", padding: 24, color: "#999" }}>
加载中...
</div>
) : history.length === 0 ? (
<div style={{ textAlign: "center", padding: 24, color: "#999" }}>
暂无生成记录
</div>
) : (
<table style={{ width: "100%", borderCollapse: "collapse" }}>
<thead>
<tr style={{ borderBottom: "1px solid #e8e8e8", textAlign: "left" }}>
<th style={{ padding: "8px 12px", fontSize: 12, color: "#666" }}>任务ID</th>
<th style={{ padding: "8px 12px", fontSize: 12, color: "#666" }}>状态</th>
<th style={{ padding: "8px 12px", fontSize: 12, color: "#666" }}>创建时间</th>
<th style={{ padding: "8px 12px", fontSize: 12, color: "#666" }}>更新时间</th>
</tr>
</thead>
<tbody>
{history.map((gen) => {
const style = STATUS_STYLES[gen.status] || DEFAULT_STATUS_STYLE;
return (
<tr key={gen.id} style={{ borderBottom: "1px solid #f0f0f0" }}>
<td style={{ padding: "8px 12px", fontSize: 13, fontFamily: "monospace" }}>
{gen.generation_task_id.slice(0, 8)}...
</td>
<td style={{ padding: "8px 12px", fontSize: 13 }}>
<span
style={{
padding: "2px 8px",
borderRadius: 4,
fontSize: 12,
background: style.bg,
color: style.color,
}}
>
{PLAN_STATUS_LABELS[gen.status] || gen.status}
</span>
</td>
<td style={{ padding: "8px 12px", fontSize: 13, color: "#666" }}>
{gen.created_at ? new Date(gen.created_at).toLocaleString("zh-CN") : "—"}
</td>
<td style={{ padding: "8px 12px", fontSize: 13, color: "#666" }}>
{gen.updated_at ? new Date(gen.updated_at).toLocaleString("zh-CN") : "—"}
</td>
</tr>
);
})}
</tbody>
</table>
)}
</div>
<div className="ep-modal-footer">
<button className="ep-btn ep-btn-secondary" onClick={onClose}>
关闭
</button>
</div>
</div>
</div>
);
};
export default GenerationHistoryModal;