import React, { useState, useEffect } from 'react' import { Upload, FolderOpen, Trash2, Eye, Download, Search, Filter, Grid, List } from 'lucide-react' import { invoke } from '@tauri-apps/api/core' import { TemplateService } from '../services/tauri' import { useTemplateProgress } from '../hooks/useProgressCommand' interface TemplateInfo { id: string name: string description: string thumbnail_path: string draft_content_path: string resources_path: string created_at: string updated_at: string canvas_config: any duration: number material_count: number track_count: number tags: string[] } // 轨道和片段的数据结构 interface TrackSegment { id: string type: 'video' | 'audio' | 'image' | 'text' | 'effect' name: string start_time: number end_time: number duration: number resource_path?: string properties?: any effects?: any[] } interface Track { id: string name: string type: 'video' | 'audio' | 'subtitle' index: number segments: TrackSegment[] properties?: any } interface TemplateDetail { id: string name: string description: string canvas_config: any tracks: Track[] duration: number fps: number sample_rate?: number } // Import the progress interface from the hook import type { ProgressState } from '../hooks/useProgressCommand' interface ImportResult { status: boolean msg: string imported_count: number failed_count: number imported_templates: TemplateInfo[] failed_templates: Array<{ name: string error: string }> } const TemplateManagePage: React.FC = () => { const [templates, setTemplates] = useState([]) const [loading, setLoading] = useState(false) const [searchTerm, setSearchTerm] = useState('') const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid') const [selectedTemplate, setSelectedTemplate] = useState(null) const [templateDetail, setTemplateDetail] = useState(null) const [loadingDetail, setLoadingDetail] = useState(false) const [showImportModal, setShowImportModal] = useState(false) // Use the progress hook for template operations const { isExecuting: importing, progress: importProgress, result: importResult, logs: importLogs, batchImport, reset: resetImport } = useTemplateProgress({ onSuccess: async (result) => { if (result.status && result.imported_count > 0) { await loadTemplates() // Reload templates after successful import } } }) // Load templates on component mount useEffect(() => { loadTemplates() }, []) const loadTemplates = async () => { try { setLoading(true) const result = await TemplateService.getTemplates() if (result.status) { setTemplates(result.templates || []) } else { console.error('Failed to load templates:', result.msg) setTemplates([]) } } catch (error) { console.error('Error loading templates:', error) setTemplates([]) } finally { setLoading(false) } } // 加载模板详情(包含轨道和片段信息) const loadTemplateDetail = async (template: TemplateInfo) => { try { setLoadingDetail(true) setSelectedTemplate(template) // 调用后端API获取模板详情 const detail = await TemplateService.getTemplateDetail(template.id) setTemplateDetail(detail) } catch (error) { console.error('Failed to load template detail:', error) // 如果加载详情失败,至少显示基本信息 setTemplateDetail(null) } finally { setLoadingDetail(false) } } const handleBatchImport = async () => { try { // Select folder using Tauri dialog const folderResult = await invoke('select_folder') if (!folderResult) return // Show import modal and start import setShowImportModal(true) await batchImport(folderResult) } catch (error) { console.error('Import failed:', error) } } const handleDeleteTemplate = async (templateId: string) => { if (!confirm('确定要删除这个模板吗?此操作不可撤销。')) return try { const result = await TemplateService.deleteTemplate(templateId) if (result.status) { setTemplates(templates.filter(t => t.id !== templateId)) if (selectedTemplate?.id === templateId) { setSelectedTemplate(null) } } else { alert('删除失败: ' + result.msg) } } catch (error) { console.error('Delete failed:', error) alert('删除失败: ' + (error instanceof Error ? error.message : 'Unknown error')) } } const filteredTemplates = templates.filter(template => template.name.toLowerCase().includes(searchTerm.toLowerCase()) || template.description.toLowerCase().includes(searchTerm.toLowerCase()) ) const formatDuration = (duration: number) => { const seconds = Math.floor(duration / 1000000) const minutes = Math.floor(seconds / 60) const remainingSeconds = seconds % 60 return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}` } // Helper function to format time (for segments, in seconds) const formatTime = (seconds: number): string => { const minutes = Math.floor(seconds / 60) const secs = (seconds % 60).toFixed(2) return `${minutes}:${secs.padStart(5, '0')}` } const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('zh-CN') } return (
{/* Header */}

模板管理

管理和导入视频编辑模板

{/* Actions Bar */}
setSearchTerm(e.target.value)} className="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{/* Import Result */} {importResult && (

导入结果

{importResult.msg}

{importResult.imported_count > 0 && (

成功导入 {importResult.imported_count} 个模板

)} {importResult.failed_count > 0 && (
{importResult.failed_count} 个模板导入失败 (点击查看详情)
{importResult.failed_templates.map((failed: any, index: number) => (
{failed.name}: {failed.error}
))}
)}
)} {/* Templates Grid/List */} {loading ? (
加载中...
) : filteredTemplates.length === 0 ? (

暂无模板

{searchTerm ? '没有找到匹配的模板' : '点击上方按钮开始导入模板'}

{!searchTerm && ( )}
) : (
{filteredTemplates.map((template) => (
{viewMode === 'grid' ? ( <> {/* Thumbnail */}
{template.thumbnail_path ? ( {template.name} ) : (
)}
{/* Content */}

{template.name}

{template.description}

时长: {formatDuration(template.duration)}
素材: {template.material_count} 个
轨道: {template.track_count} 个
创建: {formatDate(template.created_at)}
{/* Actions */}
) : ( <> {/* List View */}

{template.name}

{template.description}

{formatDuration(template.duration)} {template.material_count} 素材 {template.track_count} 轨道 {formatDate(template.created_at)}
)}
))}
)} {/* Import Progress Modal */} {showImportModal && (

模板导入进度

{!importing && ( )}
{/* Progress Bar */} {importProgress && (
{importProgress.step} {importProgress.progress >= 0 ? `${Math.round(importProgress.progress)}%` : '处理中...'}
= 0 ? `${importProgress.progress}%` : '50%' }} >

{importProgress.message}

{importProgress.details?.processed_templates !== undefined && importProgress.details?.total_templates !== undefined && (

已处理: {importProgress.details.processed_templates} / {importProgress.details.total_templates}

)}
)} {/* Real-time Logs */}

实时日志

{importLogs.map((log, index) => (
{log}
))} {importLogs.length === 0 && (
等待日志输出...
)}
{/* Import Result */} {importResult && (

导入完成

{importResult.msg}

{importResult.imported_count > 0 && (

成功导入 {importResult.imported_count} 个模板

)} {importResult.failed_count > 0 && (
{importResult.failed_count} 个模板导入失败 (点击查看详情)
{importResult.failed_templates.map((failed: any, index: number) => (
{failed.name}: {failed.error}
))}
)}
)} {/* Action Buttons */}
{importing ? (
导入中...
) : ( )}
)} {/* Template Detail Modal */} {selectedTemplate && (

模板详情

{selectedTemplate.name}

{selectedTemplate.description}

{formatDuration(selectedTemplate.duration)}

{selectedTemplate.material_count}

{selectedTemplate.track_count}

{selectedTemplate.canvas_config?.width || 0} × {selectedTemplate.canvas_config?.height || 0}

{new Date(selectedTemplate.created_at).toLocaleString('zh-CN')}

{new Date(selectedTemplate.updated_at).toLocaleString('zh-CN')}

{/* 轨道和片段信息 */}
{loadingDetail ? (
加载详情中...
) : templateDetail ? (
{/* 画布信息 */}

画布配置

尺寸: {templateDetail.canvas_config?.width || 0} × {templateDetail.canvas_config?.height || 0}
帧率: {templateDetail.fps || 30} FPS
时长: {formatDuration(templateDetail.duration)}
{templateDetail.sample_rate && (
采样率: {templateDetail.sample_rate} Hz
)}
{/* 轨道列表 */}
{templateDetail.tracks.map((track, trackIndex) => (

轨道 {track.index + 1}: {track.name}

{track.type === 'video' ? '视频' : track.type === 'audio' ? '音频' : '字幕'}
{/* 片段列表 */}
{track.segments.length > 0 ? ( track.segments.map((segment, segmentIndex) => (
{segment.name} {segment.type === 'video' ? '视频' : segment.type === 'audio' ? '音频' : segment.type === 'image' ? '图片' : segment.type === 'text' ? '文本' : '特效'}
开始: {formatTime(segment.start_time)}
结束: {formatTime(segment.end_time)}
时长: {formatTime(segment.duration)}
{segment.resource_path && (
资源: {segment.resource_path}
)}
)) ) : (
该轨道暂无片段
)}
))}
) : (

无法加载模板详情

请检查模板文件是否完整

)}
)}
) } export default TemplateManagePage