Compare commits

...

1 Commits

Author SHA1 Message Date
Audit Bot a29e30eb26 feat: 剪辑模板 API 对接真实后端 + 清理遗留文件
- editingPlanner.ts: 移除全部 mock 数据,7 个函数改为通过 apiClient 调用 /api/v1/templates 端点
- 删除遗留文件 editPlans.ts(无任何引用)

Closes: v0.1.91 测试问题 #1 #2
2026-06-29 16:15:13 +08:00
2 changed files with 43 additions and 294 deletions
-98
View File
@@ -1,98 +0,0 @@
/**
* 编辑计划 API
* Phase 1 重构:去掉 projectId,编辑计划直接归属用户
*/
import apiClient from './client';
/** 编辑模式 */
export type EditingMode = 'one-take' | 'pip' | 'voiceover' | 'voice_pip';
/** 编辑模板 */
export interface EditTemplateItem {
id: string;
name: string;
description: string;
target_duration: number;
clip_count: number;
is_active: boolean;
category?: string;
thumbnail_url?: string;
}
/** 编辑计划片段 */
export interface EditPlanClipItem {
id: string;
asset_id: string;
asset_name: string;
sequence: number;
start_time: number;
duration: number;
reason: string;
layer?: 'main' | 'pip' | 'broll';
thumbnail_url?: string;
}
/** 编辑计划 */
export interface EditPlanItem {
id: string;
template_id: string;
asset_library_id: string;
title_id: string;
status: string;
editing_mode?: EditingMode;
summary: string;
clips: EditPlanClipItem[];
created_at?: string;
updated_at?: string;
}
// ─── 编辑计划 ──────────────────────────────────────────────
/** 获取当前用户的编辑计划列表 */
export const getEditPlans = async (): Promise<EditPlanItem[]> => {
const response = await apiClient.get('/edit-plans');
return response.data.items || [];
};
/** 获取单个编辑计划 */
export const getEditPlan = async (planId: string): Promise<EditPlanItem> => {
const response = await apiClient.get(`/edit-plans/${planId}`);
return response.data;
};
/** 创建编辑计划 */
export const createEditPlan = async (data: {
asset_library_id: string;
template_id?: string;
title_id?: string;
}): Promise<EditPlanItem> => {
const response = await apiClient.post('/edit-plans', data);
return response.data;
};
/** 智能编排 - 自动生成编辑计划 */
export const autoGenerateEditPlan = async (params: {
template_id: string;
asset_ids?: string[];
title_ids?: string[];
voice_ids?: string[];
editing_mode?: EditingMode;
target_duration?: number;
}): Promise<EditPlanItem> => {
const response = await apiClient.post('/edit-plans/auto-generate', params);
return response.data;
};
/** 更新编辑计划 */
export const updateEditPlan = async (
planId: string,
data: Partial<EditPlanItem>
): Promise<EditPlanItem> => {
const response = await apiClient.patch(`/edit-plans/${planId}`, data);
return response.data;
};
/** 删除编辑计划 */
export const deleteEditPlan = async (planId: string): Promise<void> => {
await apiClient.delete(`/edit-plans/${planId}`);
};
+43 -196
View File
@@ -1,8 +1,8 @@
/**
* 剪辑计划编辑器 API
* 当前使用 mock 数据,后端 API 就绪后替换
* 剪辑模板 API 模块
* 对接后端 /api/v1/templates RESTful 端点
*/
// import apiClient from './client'; // TODO: 后端 API 就绪后启用
import apiClient from './client';
/* ──────────── 类型定义 ──────────── */
@@ -72,14 +72,14 @@ export interface EditingTemplate {
bgm_config: BgmConfig;
estimated_duration: number;
segments: TemplateSegment[];
created_at: string;
updated_at: string;
created_at?: string;
updated_at?: string;
}
/** 模板分类 */
export interface TemplateCategory {
id: string;
name: string;
count?: number;
}
/** 创建/更新模板请求体 */
@@ -106,209 +106,56 @@ export interface GenerateFromTemplateResponse {
warning?: string;
}
/* ──────────── Mock 数据 ──────────── */
/* ──────────── API 函数 ──────────── */
let _nextId = 100;
const nextId = () => String(++_nextId);
const MOCK_CATEGORIES: TemplateCategory[] = [
{ id: 'cat-1', name: '生活' },
{ id: 'cat-2', name: '美食' },
{ id: 'cat-3', name: '旅行' },
{ id: 'cat-4', name: '知识' },
];
const MOCK_TEMPLATES: EditingTemplate[] = [
{
id: 'tpl-1',
name: '生活 Vlog 模板',
mode: 'pip',
category: '生活',
tags: ['vlog', '日常'],
title_config: {
ai_auto_select: true,
content: '',
font_preset: '思源黑体',
font_color: '#ffffff',
font_size: 32,
position: 'top',
},
subtitle_config: {
enabled: true,
position: 'bottom',
font: '思源黑体',
color: '#ffffff',
size: 24,
animation: 'fade',
},
bgm_config: { enabled: true, music_id: 'bgm-1' },
estimated_duration: 30,
segments: [
{ id: 'seg-1', segment_order: 1, duration_min: 5, duration_max: 15, material_type: null },
{ id: 'seg-2', segment_order: 2, duration_min: 10, duration_max: 20, material_type: null },
],
created_at: '2026-06-20T10:00:00Z',
updated_at: '2026-06-20T10:00:00Z',
},
{
id: 'tpl-2',
name: '知识分享口播',
mode: 'voice_over',
category: '知识',
tags: ['口播', '分享'],
title_config: {
ai_auto_select: false,
content: '每日知识分享',
font_preset: '站酷快乐体',
font_color: '#ffdd00',
font_size: 36,
position: 'top',
},
subtitle_config: {
enabled: true,
position: 'bottom',
font: '思源黑体',
color: '#ffffff',
size: 28,
animation: 'typewriter',
},
bgm_config: { enabled: false, music_id: '' },
estimated_duration: 60,
segments: [
{ id: 'seg-3', segment_order: 1, duration_min: 10, duration_max: 30, material_type: null },
{ id: 'seg-4', segment_order: 2, duration_min: 20, duration_max: 40, material_type: null },
{ id: 'seg-5', segment_order: 3, duration_min: 10, duration_max: 20, material_type: null },
],
created_at: '2026-06-21T10:00:00Z',
updated_at: '2026-06-21T10:00:00Z',
},
{
id: 'tpl-3',
name: '一镜到底展示',
mode: 'one_take',
category: '生活',
tags: ['一镜到底'],
title_config: {
ai_auto_select: true,
content: '',
font_preset: '思源黑体',
font_color: '#ffffff',
font_size: 32,
position: 'center',
},
subtitle_config: { enabled: false, position: 'bottom', font: '思源黑体', color: '#ffffff', size: 24, animation: 'fade' },
bgm_config: { enabled: true, music_id: 'bgm-2' },
estimated_duration: 15,
segments: [
{ id: 'seg-6', segment_order: 1, duration_min: 10, duration_max: 20, material_type: null },
],
created_at: '2026-06-22T10:00:00Z',
updated_at: '2026-06-22T10:00:00Z',
},
];
/* ──────────── Mock API 函数 ──────────── */
const delay = (ms = 200) => new Promise((r) => setTimeout(r, ms));
/** 获取模板列表 */
export const getEditingTemplates = async (params?: {
/** 获取模板列表(支持分类/标签筛选) */
export async function getEditingTemplates(params?: {
category?: string;
tag?: string;
}): Promise<EditingTemplate[]> => {
await delay();
let list = [...MOCK_TEMPLATES];
if (params?.category) list = list.filter((t) => t.category === params.category);
if (params?.tag) list = list.filter((t) => t.tags.includes(params.tag!));
return list;
};
}): Promise<EditingTemplate[]> {
const { data } = await apiClient.get('/templates', { params });
return data;
}
/** 获取模板详情 */
export const getEditingTemplate = async (id: string): Promise<EditingTemplate> => {
await delay();
const tpl = MOCK_TEMPLATES.find((t) => t.id === id);
if (!tpl) throw new Error('模板不存在');
return { ...tpl };
};
export async function getEditingTemplate(id: string): Promise<EditingTemplate> {
const { data } = await apiClient.get(`/templates/${id}`);
return data;
}
/** 创建模板 */
export const createEditingTemplate = async (
data: SaveTemplatePayload,
): Promise<EditingTemplate> => {
await delay(300);
const now = new Date().toISOString();
const tpl: EditingTemplate = {
id: nextId(),
name: data.name,
mode: data.mode,
category: data.category,
tags: data.tags,
title_config: data.title_config,
subtitle_config: data.subtitle_config,
bgm_config: data.bgm_config,
estimated_duration: data.estimated_duration,
segments: data.segments.map((s, i) => ({
...s,
id: nextId(),
segment_order: i + 1,
})),
created_at: now,
updated_at: now,
};
MOCK_TEMPLATES.push(tpl);
return tpl;
};
export async function createEditingTemplate(
payload: SaveTemplatePayload,
): Promise<EditingTemplate> {
const { data } = await apiClient.post('/templates', payload);
return data;
}
/** 更新模板 */
export const updateEditingTemplate = async (
export async function updateEditingTemplate(
id: string,
data: SaveTemplatePayload,
): Promise<EditingTemplate> => {
await delay(300);
const idx = MOCK_TEMPLATES.findIndex((t) => t.id === id);
if (idx === -1) throw new Error('模板不存在');
const updated: EditingTemplate = {
...MOCK_TEMPLATES[idx],
name: data.name,
mode: data.mode,
category: data.category,
tags: data.tags,
title_config: data.title_config,
subtitle_config: data.subtitle_config,
bgm_config: data.bgm_config,
estimated_duration: data.estimated_duration,
segments: data.segments.map((s, i) => ({
...s,
id: nextId(),
segment_order: i + 1,
})),
updated_at: new Date().toISOString(),
};
MOCK_TEMPLATES[idx] = updated;
return updated;
};
payload: SaveTemplatePayload,
): Promise<EditingTemplate> {
const { data } = await apiClient.put(`/templates/${id}`, payload);
return data;
}
/** 删除模板 */
export const deleteEditingTemplate = async (id: string): Promise<void> => {
await delay();
const idx = MOCK_TEMPLATES.findIndex((t) => t.id === id);
if (idx !== -1) MOCK_TEMPLATES.splice(idx, 1);
};
export async function deleteEditingTemplate(id: string): Promise<void> {
await apiClient.delete(`/templates/${id}`);
}
/** 获取模板分类列表 */
export const getTemplateCategories = async (): Promise<TemplateCategory[]> => {
await delay();
return [...MOCK_CATEGORIES];
};
export async function getTemplateCategories(): Promise<TemplateCategory[]> {
const { data } = await apiClient.get('/templates/categories');
return data;
}
/** 使用模板生成视频 */
export const generateFromTemplate = async (
_templateId: string,
_data: GenerateFromTemplatePayload,
): Promise<GenerateFromTemplateResponse> => {
await delay(500);
return {
task_id: nextId(),
warning: undefined,
};
};
export async function generateFromTemplate(
templateId: string,
payload: GenerateFromTemplatePayload,
): Promise<GenerateFromTemplateResponse> {
const { data } = await apiClient.post(`/templates/${templateId}/generate`, payload);
return data;
}