diff --git a/apps/web/src/api/products.ts b/apps/web/src/api/products.ts deleted file mode 100644 index 615a5f45c..000000000 --- a/apps/web/src/api/products.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * 成品 / 视频相关 API - * 后端实际接口:/videos - */ -import apiClient from "./client" - -/** 复核状态 */ -export type ReviewStatus = "pending_review" | "approved" | "rejected" - -/** 成品条目 */ -export interface ProductItem { - id: string - title: string - video_url?: string - thumbnail_url?: string - duration_seconds?: number - file_size?: number - resolution?: string - status: "processing" | "completed" | "failed" - /** 复核状态 */ - review_status?: ReviewStatus - /** 所属项目 ID */ - project_id?: string - /** 所属项目名称 */ - project_name?: string - /** 查重率(百分比) */ - duplicate_rate?: number - created_at?: string - updated_at?: string -} - -/** 列表查询参数 */ -export interface ProductListParams { - page?: number - page_size?: number - project_id?: string - review_status?: ReviewStatus | "all" -} - -/** 分页响应 */ -export interface ProductListResponse { - items: ProductItem[] - total: number - page: number - page_size: number -} - -/** 批量下载任务状态 */ -export interface BatchDownloadStatus { - job_id: string - status: "processing" | "completed" | "failed" - /** 完成后返回的下载 URL */ - download_url?: string - /** 进度百分比 */ - progress?: number -} - -/** 后端 /videos 接口返回的原始视频条目 */ -interface VideoItem { - id: string - project_id: string - generation_task_id: string - name: string - file_url: string - file_size: number - duration: number - thumbnail_url: string | null - width: number - height: number - fps: number - status: string - review_status: ReviewStatus - generation_params: Record - download_url: string - generated_at: string -} - -/** - * 将后端 VideoItem 映射为 ProductItem 格式 - */ -function mapVideoToProductItem(video: VideoItem): ProductItem { - return { - id: video.id, - title: video.name || "未命名视频", - // 优先用 download_url(带签名)播放,file_url 无签名无法访问 - video_url: video.download_url || video.file_url, - thumbnail_url: video.thumbnail_url || undefined, - duration_seconds: video.duration, - file_size: video.file_size, - resolution: video.width && video.height ? `${video.width}x${video.height}` : undefined, - status: - video.status === "completed" - ? "completed" - : video.status === "failed" - ? "failed" - : "processing", - review_status: video.review_status, - project_id: video.project_id, - // 后端 /videos 接口暂无 project_name 字段 - project_name: undefined, - // 后端字段名为 generated_at,映射为 created_at 供前端统一使用 - created_at: video.generated_at, - updated_at: video.generated_at, - // 后端 /videos 接口暂无 duplicate_rate 字段 - duplicate_rate: undefined, - } -} - -/** 获取成品列表(支持分页和筛选) */ -export const getProducts = async (params?: ProductListParams): Promise => { - const response = await apiClient.get("/videos", { params }) - const data = response.data - const videos: VideoItem[] = Array.isArray(data?.items) - ? data.items - : Array.isArray(data) - ? data - : [] - return videos.map(mapVideoToProductItem) -} - -/** 获取单个成品详情 */ -export const getProduct = async (productId: string): Promise => { - const response = await apiClient.get(`/videos/${productId}`) - return mapVideoToProductItem(response.data as VideoItem) -} - -/** - * 删除成品 - * 注意:后端暂未实现 /videos DELETE 接口,调用会返回 405 - * 待后端实现后自动生效 - */ -export const deleteProduct = async (productId: string): Promise => { - await apiClient.delete(`/videos/${productId}`) -} - -/** - * 获取成品下载链接 - * 直接使用列表返回的 download_url(带OSS签名) - */ -export const getProductDownloadUrl = async ( - productId: string, -): Promise<{ url: string; expires_at: string }> => { - // 优先从列表缓存取;如果没有则调详情接口 - const product = await getProduct(productId) - if (!product.video_url) throw new Error("下载链接不可用") - return { url: product.video_url, expires_at: "" } -} - -/** 更新复核状态 — TODO: 后端暂无对应端点,暂存本地状态 */ -export const updateReviewStatus = async ( - productId: string, - status: ReviewStatus, -): Promise => { - // 后端暂无 /videos/{id}/review 端点 - // 暂时返回当前状态,后续可扩展 - const product = await getProduct(productId) - return { ...product, review_status: status } -} - -/** 发起批量下载 — TODO: 后端暂无对应端点 */ -export const batchDownload = async (videoIds: string[]): Promise<{ job_id: string }> => { - // 后端暂无 /videos/batch-download 端点 - // 暂时返回模拟 job_id,后续可扩展 - console.warn("[batchDownload] 后端暂无批量下载端点", videoIds) - return { job_id: `mock-${Date.now()}` } -} - -/** 查询批量下载状态 — TODO: 后端暂无对应端点 */ -export const getBatchDownloadStatus = async (jobId: string): Promise => { - // 后端暂无 /videos/batch-download/{jobId} 端点 - // 暂时返回模拟状态,后续可扩展 - console.warn("[getBatchDownloadStatus] 后端暂无批量下载状态端点", jobId) - return { job_id: jobId, status: "processing", progress: 0 } -} diff --git a/apps/web/src/api/products/index.ts b/apps/web/src/api/products/index.ts new file mode 100644 index 000000000..b4bf23a95 --- /dev/null +++ b/apps/web/src/api/products/index.ts @@ -0,0 +1,28 @@ +/** + * 成品 / 视频相关 API — 目录化入口 + * 保持与原 products.ts 相同导出,向后兼容 + */ + +// 类型 +export type { + ReviewStatus, + ProductItem, + ProductListParams, + ProductListResponse, + BatchDownloadStatus, + VideoItem, +} from "./types" + +// 工具函数 +export { mapVideoToProductItem } from "./utils" + +// API 函数 +export { + getProducts, + getProduct, + deleteProduct, + getProductDownloadUrl, + updateReviewStatus, + batchDownload, + getBatchDownloadStatus, +} from "./products" diff --git a/apps/web/src/api/products/products.ts b/apps/web/src/api/products/products.ts new file mode 100644 index 000000000..7c0fcb19f --- /dev/null +++ b/apps/web/src/api/products/products.ts @@ -0,0 +1,80 @@ +/** + * 成品 / 视频相关 API 函数 + * 后端实际接口:/videos + */ +import apiClient from "../client" +import type { + BatchDownloadStatus, + ProductItem, + ProductListParams, + VideoItem, + ReviewStatus, +} from "./types" +import { mapVideoToProductItem } from "./utils" + +/** 获取成品列表(支持分页和筛选) */ +export const getProducts = async (params?: ProductListParams): Promise => { + const response = await apiClient.get("/videos", { params }) + const data = response.data + const videos: VideoItem[] = Array.isArray(data?.items) + ? data.items + : Array.isArray(data) + ? data + : [] + return videos.map(mapVideoToProductItem) +} + +/** 获取单个成品详情 */ +export const getProduct = async (productId: string): Promise => { + const response = await apiClient.get(`/videos/${productId}`) + return mapVideoToProductItem(response.data as VideoItem) +} + +/** + * 删除成品 + * 注意:后端暂未实现 /videos DELETE 接口,调用会返回 405 + * 待后端实现后自动生效 + */ +export const deleteProduct = async (productId: string): Promise => { + await apiClient.delete(`/videos/${productId}`) +} + +/** + * 获取成品下载链接 + * 直接使用列表返回的 download_url(带OSS签名) + */ +export const getProductDownloadUrl = async ( + productId: string, +): Promise<{ url: string; expires_at: string }> => { + // 优先从列表缓存取;如果没有则调详情接口 + const product = await getProduct(productId) + if (!product.video_url) throw new Error("下载链接不可用") + return { url: product.video_url, expires_at: "" } +} + +/** 更新复核状态 — TODO: 后端暂无对应端点,暂存本地状态 */ +export const updateReviewStatus = async ( + productId: string, + status: ReviewStatus, +): Promise => { + // 后端暂无 /videos/{id}/review 端点 + // 暂时返回当前状态,后续可扩展 + const product = await getProduct(productId) + return { ...product, review_status: status } +} + +/** 发起批量下载 — TODO: 后端暂无对应端点 */ +export const batchDownload = async (videoIds: string[]): Promise<{ job_id: string }> => { + // 后端暂无 /videos/batch-download 端点 + // 暂时返回模拟 job_id,后续可扩展 + console.warn("[batchDownload] 后端暂无批量下载端点", videoIds) + return { job_id: `mock-${Date.now()}` } +} + +/** 查询批量下载状态 — TODO: 后端暂无对应端点 */ +export const getBatchDownloadStatus = async (jobId: string): Promise => { + // 后端暂无 /videos/batch-download/{jobId} 端点 + // 暂时返回模拟状态,后续可扩展 + console.warn("[getBatchDownloadStatus] 后端暂无批量下载状态端点", jobId) + return { job_id: jobId, status: "processing", progress: 0 } +} diff --git a/apps/web/src/api/products/types.ts b/apps/web/src/api/products/types.ts new file mode 100644 index 000000000..83a8af804 --- /dev/null +++ b/apps/web/src/api/products/types.ts @@ -0,0 +1,74 @@ +/** + * 成品 / 视频相关类型定义 + */ + +/** 复核状态 */ +export type ReviewStatus = "pending_review" | "approved" | "rejected" + +/** 成品条目 */ +export interface ProductItem { + id: string + title: string + video_url?: string + thumbnail_url?: string + duration_seconds?: number + file_size?: number + resolution?: string + status: "processing" | "completed" | "failed" + /** 复核状态 */ + review_status?: ReviewStatus + /** 所属项目 ID */ + project_id?: string + /** 所属项目名称 */ + project_name?: string + /** 查重率(百分比) */ + duplicate_rate?: number + created_at?: string + updated_at?: string +} + +/** 列表查询参数 */ +export interface ProductListParams { + page?: number + page_size?: number + project_id?: string + review_status?: ReviewStatus | "all" +} + +/** 分页响应 */ +export interface ProductListResponse { + items: ProductItem[] + total: number + page: number + page_size: number +} + +/** 批量下载任务状态 */ +export interface BatchDownloadStatus { + job_id: string + status: "processing" | "completed" | "failed" + /** 完成后返回的下载 URL */ + download_url?: string + /** 进度百分比 */ + progress?: number +} + +/** 后端 /videos 接口返回的原始视频条目 */ +export interface VideoItem { + id: string + project_id: string + generation_task_id: string + name: string + file_url: string + file_size: number + duration: number + thumbnail_url: string | null + width: number + height: number + fps: number + status: string + review_status: ReviewStatus + generation_params: Record + download_url: string + generated_at: string +} diff --git a/apps/web/src/api/products/utils.ts b/apps/web/src/api/products/utils.ts new file mode 100644 index 000000000..00fd89ef3 --- /dev/null +++ b/apps/web/src/api/products/utils.ts @@ -0,0 +1,35 @@ +/** + * 成品数据转换工具函数 + */ +import type { ProductItem, VideoItem } from "./types" + +/** + * 将后端 VideoItem 映射为 ProductItem 格式 + */ +export function mapVideoToProductItem(video: VideoItem): ProductItem { + return { + id: video.id, + title: video.name || "未命名视频", + // 优先用 download_url(带签名)播放,file_url 无签名无法访问 + video_url: video.download_url || video.file_url, + thumbnail_url: video.thumbnail_url || undefined, + duration_seconds: video.duration, + file_size: video.file_size, + resolution: video.width && video.height ? `${video.width}x${video.height}` : undefined, + status: + video.status === "completed" + ? "completed" + : video.status === "failed" + ? "failed" + : "processing", + review_status: video.review_status, + project_id: video.project_id, + // 后端 /videos 接口暂无 project_name 字段 + project_name: undefined, + // 后端字段名为 generated_at,映射为 created_at 供前端统一使用 + created_at: video.generated_at, + updated_at: video.generated_at, + // 后端 /videos 接口暂无 duplicate_rate 字段 + duplicate_rate: undefined, + } +}