d2ea8ba579
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 47s
CI/CD Pipeline / Frontend Lint (push) Failing after 58s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 58s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m55s
CI/CD Pipeline / Unit Tests (push) Successful in 2m3s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m5s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m26s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m12s
- TemplateItem 新增 estimated_duration 字段,匹配后端实际返回字段名 - target_duration 标记为 deprecated,保留向后兼容 - TemplateLibrary 两处引用改为 estimated_duration ?? target_duration 回退 - 新增 mode/user_id 可选字段,description/clip_count/is_active 改 optional 修复模板卡片时长显示 NaN 问题(根因:前端读 target_duration 但后端返回 estimated_duration)
179 lines
5.0 KiB
TypeScript
179 lines
5.0 KiB
TypeScript
/**
|
||
* 模板相关 API
|
||
* 对接后端模板管理接口:
|
||
* - GET /api/v1/templates — 模板列表(分页/筛选)
|
||
* - GET /api/v1/templates/{id} — 模板详情
|
||
* - POST /api/v1/templates/{id}/copy — 复制模板
|
||
* - POST /api/v1/templates/{id}/generate — 从模板生成剪辑计划
|
||
* - POST /api/v1/templates/{id}/toggle-favorite — 收藏/取消收藏
|
||
*/
|
||
import apiClient from "./client";
|
||
import type { TitleConfig, SubtitleConfig, BgmConfig } from "./editingPlanner";
|
||
import type { EditPlanConfig } from "./editPlans";
|
||
|
||
/* ──────────── 类型定义 ──────────── */
|
||
|
||
/** 模板条目(后端 TemplateResponse) */
|
||
export interface TemplateItem {
|
||
id: string;
|
||
user_id?: string;
|
||
name: string;
|
||
description?: string;
|
||
mode?: string;
|
||
category: string;
|
||
tags?: string[];
|
||
/** 预估时长(后端字段名 estimated_duration) */
|
||
estimated_duration?: number;
|
||
/** @deprecated 后端已改名为 estimated_duration,保留兼容 */
|
||
target_duration?: number;
|
||
clip_count?: number;
|
||
/** 使用次数 */
|
||
usage_count?: number;
|
||
thumbnail_url?: string;
|
||
preview_url?: string;
|
||
is_active?: boolean;
|
||
is_favorite?: boolean;
|
||
/** 素材规则(片段配置) */
|
||
segments?: TemplateSegment[];
|
||
/** 字幕样式 */
|
||
subtitle_config?: SubtitleConfig;
|
||
/** BGM 配置 */
|
||
bgm_config?: BgmConfig;
|
||
/** 标题配置 */
|
||
title_config?: TitleConfig;
|
||
/** 视频比例 */
|
||
aspect_ratio?: string;
|
||
created_at?: string;
|
||
updated_at?: string;
|
||
}
|
||
|
||
/** 模板片段(素材规则) */
|
||
export interface TemplateSegment {
|
||
id?: string;
|
||
segment_order: number;
|
||
duration_min: number;
|
||
duration_max: number;
|
||
material_type: string | null;
|
||
description?: string;
|
||
}
|
||
|
||
/** 模板列表查询参数 */
|
||
export interface TemplateListParams {
|
||
page?: number;
|
||
page_size?: number;
|
||
category?: string;
|
||
tags?: string;
|
||
keyword?: string;
|
||
/** 时长筛选(秒):short < 30, medium 30-120, long > 120 */
|
||
duration_range?: "short" | "medium" | "long";
|
||
}
|
||
|
||
/** 模板列表分页响应 */
|
||
export interface TemplateListResponse {
|
||
items: TemplateItem[];
|
||
total: number;
|
||
page: number;
|
||
page_size: number;
|
||
}
|
||
|
||
/** 从模板生成剪辑计划请求 */
|
||
export interface GenerateFromTemplateRequest {
|
||
asset_ids?: string[];
|
||
name?: string;
|
||
config?: EditPlanConfig;
|
||
}
|
||
|
||
/** 从模板生成剪辑计划响应 */
|
||
export interface GenerateFromTemplateResponse {
|
||
plan_id: string;
|
||
template_id: string;
|
||
status: string;
|
||
name: string;
|
||
}
|
||
|
||
/** 复制模板响应 */
|
||
export interface CopyTemplateResponse {
|
||
id: string;
|
||
name: string;
|
||
source_template_id: string;
|
||
}
|
||
|
||
/* ──────────── API 函数 ──────────── */
|
||
|
||
/** 获取模板列表(支持分页和筛选) */
|
||
export const getTemplates = async (
|
||
params?: TemplateListParams,
|
||
): Promise<TemplateListResponse> => {
|
||
const { data } = await apiClient.get<TemplateListResponse>("/templates", {
|
||
params,
|
||
});
|
||
return data;
|
||
};
|
||
|
||
/** 获取模板列表(兼容旧接口,返回数组) */
|
||
export const getTemplatesList = async (): Promise<TemplateItem[]> => {
|
||
const response = await apiClient.get("/templates");
|
||
return response.data.items || response.data || [];
|
||
};
|
||
|
||
/** 获取单个模板详情 */
|
||
export const getTemplate = async (
|
||
templateId: string,
|
||
): Promise<TemplateItem> => {
|
||
const response = await apiClient.get(`/templates/${templateId}`);
|
||
return response.data;
|
||
};
|
||
|
||
/** 收藏 / 取消收藏模板 */
|
||
export const toggleFavoriteTemplate = async (
|
||
templateId: string,
|
||
): Promise<{ is_favorite: boolean }> => {
|
||
const response = await apiClient.post(
|
||
`/templates/${templateId}/toggle-favorite`,
|
||
);
|
||
return response.data;
|
||
};
|
||
|
||
/** 复制模板(创建副本到我的模板) */
|
||
export const copyTemplate = async (
|
||
templateId: string,
|
||
): Promise<CopyTemplateResponse> => {
|
||
const response = await apiClient.post<CopyTemplateResponse>(
|
||
`/templates/${templateId}/copy`,
|
||
);
|
||
return response.data;
|
||
};
|
||
|
||
/** 从模板生成剪辑计划 */
|
||
export const generateFromTemplate = async (
|
||
templateId: string,
|
||
data?: GenerateFromTemplateRequest,
|
||
): Promise<GenerateFromTemplateResponse> => {
|
||
const response = await apiClient.post<GenerateFromTemplateResponse>(
|
||
`/templates/${templateId}/generate`,
|
||
data,
|
||
);
|
||
return response.data;
|
||
};
|
||
|
||
/* ──────────── 常量 ──────────── */
|
||
|
||
/** 模板分类选项 */
|
||
export const TEMPLATE_CATEGORY_OPTIONS = [
|
||
{ value: "", label: "全部分类" },
|
||
{ value: "口播", label: "口播" },
|
||
{ value: "种草", label: "种草" },
|
||
{ value: "产品", label: "产品" },
|
||
{ value: "品牌", label: "品牌" },
|
||
{ value: "混剪", label: "混剪" },
|
||
{ value: "Vlog", label: "Vlog" },
|
||
];
|
||
|
||
/** 时长筛选选项 */
|
||
export const TEMPLATE_DURATION_OPTIONS = [
|
||
{ value: "", label: "全部时长" },
|
||
{ value: "short", label: "30秒以内" },
|
||
{ value: "medium", label: "30秒-2分钟" },
|
||
{ value: "long", label: "2分钟以上" },
|
||
];
|