diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 38d75e1..447fb11 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -57,7 +57,8 @@ pub fn run() { commands::material_commands::extract_file_metadata, commands::material_commands::detect_video_scenes, commands::material_commands::generate_video_thumbnail, - commands::material_commands::test_scene_detection + commands::material_commands::test_scene_detection, + commands::material_commands::get_material_segments ]) .setup(|app| { // 初始化应用状态 diff --git a/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs index 02a1ec5..24df0e9 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs @@ -341,3 +341,19 @@ pub async fn test_scene_detection(file_path: String) -> Result { Ok(result) } + +/// 获取素材的切分片段信息命令 +#[command] +pub async fn get_material_segments( + state: State<'_, AppState>, + material_id: String, +) -> Result, String> { + let repository_guard = state.get_material_repository() + .map_err(|e| format!("获取素材仓库失败: {}", e))?; + + let repository = repository_guard.as_ref() + .ok_or("素材仓库未初始化")?; + + repository.get_segments(&material_id) + .map_err(|e| e.to_string()) +} diff --git a/apps/desktop/src/components/MaterialCard.tsx b/apps/desktop/src/components/MaterialCard.tsx new file mode 100644 index 0000000..ee9cf3a --- /dev/null +++ b/apps/desktop/src/components/MaterialCard.tsx @@ -0,0 +1,157 @@ +import React, { useState, useEffect } from 'react'; +import { FileVideo, FileAudio, FileImage, File, Clock, ExternalLink, ChevronDown, ChevronUp } from 'lucide-react'; +import { Material, MaterialSegment } from '../types/material'; +import { useMaterialStore } from '../store/materialStore'; + +interface MaterialCardProps { + material: Material; +} + +/** + * 素材卡片组件 + * 显示素材信息和切分片段 + */ +export const MaterialCard: React.FC = ({ material }) => { + const { getMaterialSegments } = useMaterialStore(); + const [segments, setSegments] = useState([]); + const [showSegments, setShowSegments] = useState(false); + const [loadingSegments, setLoadingSegments] = useState(false); + + // 获取素材类型图标 + const getTypeIcon = (type: string) => { + switch (type) { + case 'Video': + return ; + case 'Audio': + return ; + case 'Image': + return ; + default: + return ; + } + }; + + // 获取状态颜色 + const getStatusColor = (status: string) => { + switch (status) { + case 'Completed': + return 'text-green-600 bg-green-50'; + case 'Processing': + return 'text-blue-600 bg-blue-50'; + case 'Failed': + return 'text-red-600 bg-red-50'; + case 'Pending': + return 'text-yellow-600 bg-yellow-50'; + default: + return 'text-gray-600 bg-gray-50'; + } + }; + + // 格式化时间 + const formatTime = (seconds: number) => { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.floor(seconds % 60); + return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; + }; + + // 加载切分片段 + const loadSegments = async () => { + if (segments.length > 0) { + setShowSegments(!showSegments); + return; + } + + setLoadingSegments(true); + try { + const materialSegments = await getMaterialSegments(material.id); + setSegments(materialSegments); + setShowSegments(true); + } catch (error) { + console.error('加载切分片段失败:', error); + } finally { + setLoadingSegments(false); + } + }; + + // 打开文件所在文件夹 + const openFileLocation = (filePath: string) => { + // 这里可以调用 Tauri 的 API 来打开文件夹 + console.log('打开文件位置:', filePath); + }; + + return ( +
+ {/* 素材基本信息 */} +
+
+ {getTypeIcon(material.material_type)} +

{material.name}

+
+ + {material.processing_status} + +
+ + {/* 素材详细信息 */} +
+

类型: {material.material_type}

+

大小: {(material.file_size / 1024 / 1024).toFixed(2)} MB

+ {material.segments && material.segments.length > 0 && ( +

片段数: {material.segments.length}

+ )} +
+ + {/* 切分片段控制 */} + {material.material_type === 'Video' && material.processing_status === 'Completed' && ( +
+ + + {/* 切分片段列表 */} + {showSegments && segments.length > 0 && ( +
+
切分片段 ({segments.length})
+
+ {segments.map((segment) => ( +
+
+ #{segment.segment_index + 1} + + {formatTime(segment.start_time)} - {formatTime(segment.end_time)} + ({formatTime(segment.duration)}) +
+ +
+ ))} +
+
+ )} + + {showSegments && segments.length === 0 && ( +
+ 暂无切分片段 +
+ )} +
+ )} +
+ ); +}; diff --git a/apps/desktop/src/pages/ProjectDetails.tsx b/apps/desktop/src/pages/ProjectDetails.tsx index 943abed..4360c8b 100644 --- a/apps/desktop/src/pages/ProjectDetails.tsx +++ b/apps/desktop/src/pages/ProjectDetails.tsx @@ -1,14 +1,15 @@ import React, { useEffect, useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import { ArrowLeft, FolderOpen, Calendar, Settings, Upload } from 'lucide-react'; +import { ArrowLeft, FolderOpen, Calendar, Settings, Upload, FileVideo, Clock, ExternalLink } from 'lucide-react'; import { useProjectStore } from '../store/projectStore'; import { useMaterialStore } from '../store/materialStore'; import { Project } from '../types/project'; -import { MaterialImportResult } from '../types/material'; +import { MaterialImportResult, Material, MaterialSegment } from '../types/material'; import { LoadingSpinner } from '../components/LoadingSpinner'; import { ErrorMessage } from '../components/ErrorMessage'; import { MaterialImportDialog } from '../components/MaterialImportDialog'; import { FFmpegDebugPanel } from '../components/FFmpegDebugPanel'; +import { MaterialCard } from '../components/MaterialCard'; /** * 项目详情页面组件 @@ -240,18 +241,7 @@ export const ProjectDetails: React.FC = () => { ) : materials.length > 0 ? (
{materials.map((material) => ( -
-

{material.name}

-

- 类型: {material.material_type} -

-

- 状态: {material.processing_status} -

-

- 大小: {(material.file_size / 1024 / 1024).toFixed(2)} MB -

-
+ ))}
) : ( diff --git a/apps/desktop/src/store/materialStore.ts b/apps/desktop/src/store/materialStore.ts index d9886ee..6c99175 100644 --- a/apps/desktop/src/store/materialStore.ts +++ b/apps/desktop/src/store/materialStore.ts @@ -313,4 +313,15 @@ export const useMaterialStore = create((set, get) => ({ return `获取状态失败: ${error}`; } }, + + // 获取素材的切分片段 + getMaterialSegments: async (materialId: string) => { + try { + const segments = await invoke('get_material_segments', { materialId }); + return segments; + } catch (error) { + console.error('获取素材片段失败:', error); + return []; + } + }, }));