feat: 完善模板导入功能

新增功能:
- 添加详细的模板导入日志系统
- 实现全局进度存储机制
- 完善模板状态管理

 修复问题:
- 修复进度监控无限轮询问题
- 修复模板列表状态显示不正确问题
- 修复所有unwrap()导致的panic错误
- 修复外键约束失败问题

 改进:
- 优化素材上传逻辑,只上传视频/音频/图片
- 上传失败时自动跳过而不是中断导入
- 缺失文件时继续导入而不是失败
- 改进错误处理机制
This commit is contained in:
imeepos
2025-07-14 21:34:07 +08:00
parent b3321114ac
commit 939efd70d4
39 changed files with 9152 additions and 6 deletions

View File

@@ -0,0 +1,282 @@
import React, { useEffect, useState } from 'react';
import { X, CheckCircle, Clock, AlertCircle, Pause, Square } from 'lucide-react';
import { invoke } from '@tauri-apps/api/core';
interface BatchImportProgressModalProps {
onClose: () => void;
onComplete?: () => void;
}
interface BatchProgress {
total_items: number;
completed_items: number;
failed_items: number;
current_item?: string;
overall_progress: number;
is_running: boolean;
}
export const BatchImportProgressModal: React.FC<BatchImportProgressModalProps> = ({
onClose,
onComplete,
}) => {
const [progress, setProgress] = useState<BatchProgress | null>(null);
const [isCompleted, setIsCompleted] = useState(false);
const [queueStatus, setQueueStatus] = useState<[number, number, number, number] | null>(null);
useEffect(() => {
const fetchProgress = async () => {
try {
const progressData = await invoke<BatchProgress>('get_batch_import_progress');
setProgress(progressData);
const status = await invoke<[number, number, number, number]>('get_queue_status');
setQueueStatus(status);
if (progressData && !progressData.is_running &&
progressData.completed_items + progressData.failed_items >= progressData.total_items) {
setIsCompleted(true);
if (onComplete) {
setTimeout(() => {
onComplete();
}, 3000); // 3秒后自动关闭
}
}
} catch (error) {
console.error('获取批量导入进度失败:', error);
}
};
// 立即获取一次进度
fetchProgress();
// 定期轮询进度
const interval = setInterval(() => {
if (!isCompleted) {
fetchProgress();
}
}, 1000);
return () => clearInterval(interval);
}, [isCompleted, onComplete]);
const handleStopImport = async () => {
try {
await invoke('stop_batch_import');
setProgress(prev => prev ? { ...prev, is_running: false } : null);
} catch (error) {
console.error('停止批量导入失败:', error);
}
};
const handleClearQueue = async () => {
try {
await invoke('clear_import_queue');
setProgress(null);
setQueueStatus(null);
onClose();
} catch (error) {
console.error('清空队列失败:', error);
}
};
if (!progress && !queueStatus) {
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 p-6">
<div className="flex items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-3 text-gray-600">...</span>
</div>
</div>
</div>
);
}
const totalItems = progress?.total_items || 0;
const completedItems = progress?.completed_items || 0;
const failedItems = progress?.failed_items || 0;
const overallProgress = progress?.overall_progress || 0;
const isRunning = progress?.is_running || false;
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg shadow-xl w-full max-w-lg mx-4">
{/* 模态框头部 */}
<div className="flex items-center justify-between p-6 border-b border-gray-200">
<h2 className="text-xl font-semibold text-gray-900"></h2>
<button
onClick={onClose}
className="p-1 rounded-full hover:bg-gray-100 transition-colors"
>
<X className="w-5 h-5 text-gray-500" />
</button>
</div>
{/* 模态框内容 */}
<div className="p-6">
{/* 状态指示器 */}
<div className="flex items-center mb-6">
{isRunning ? (
<Clock className="w-6 h-6 text-blue-500 animate-spin" />
) : isCompleted ? (
<CheckCircle className="w-6 h-6 text-green-500" />
) : (
<Pause className="w-6 h-6 text-yellow-500" />
)}
<div className="ml-3">
<div className={`text-lg font-medium ${
isRunning ? 'text-blue-600' :
isCompleted ? 'text-green-600' :
'text-yellow-600'
}`}>
{isRunning ? '正在批量导入' :
isCompleted ? '批量导入完成' :
'批量导入已暂停'}
</div>
{progress?.current_item && (
<div className="text-sm text-gray-600">
: {progress.current_item.split(/[/\\]/).pop()}
</div>
)}
</div>
</div>
{/* 进度条 */}
<div className="mb-6">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-gray-700"></span>
<span className="text-sm text-gray-600">
{Math.round(overallProgress)}%
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-3">
<div
className={`h-3 rounded-full transition-all duration-300 ${
isCompleted ? 'bg-green-500' : 'bg-blue-500'
}`}
style={{ width: `${Math.min(100, Math.max(0, overallProgress))}%` }}
/>
</div>
</div>
{/* 统计信息 */}
<div className="grid grid-cols-4 gap-4 mb-6">
<div className="text-center">
<div className="text-lg font-semibold text-gray-900">
{totalItems}
</div>
<div className="text-xs text-gray-500"></div>
</div>
<div className="text-center">
<div className="text-lg font-semibold text-blue-600">
{totalItems - completedItems - failedItems}
</div>
<div className="text-xs text-gray-500"></div>
</div>
<div className="text-center">
<div className="text-lg font-semibold text-green-600">
{completedItems}
</div>
<div className="text-xs text-gray-500"></div>
</div>
<div className="text-center">
<div className="text-lg font-semibold text-red-600">
{failedItems}
</div>
<div className="text-xs text-gray-500"></div>
</div>
</div>
{/* 队列状态 */}
{queueStatus && (
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
<div className="text-sm font-medium text-gray-700 mb-2"></div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium">{queueStatus[0]}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-yellow-600">{queueStatus[1]}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-blue-600">{queueStatus[2]}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-green-600">{queueStatus[3]}</span>
</div>
</div>
</div>
)}
{/* 完成信息 */}
{isCompleted && (
<div className="mb-6 p-4 bg-green-50 border border-green-200 rounded-lg">
<div className="flex items-center">
<CheckCircle className="w-5 h-5 text-green-500 mr-2" />
<div className="text-sm text-green-800">
<div className="font-medium"></div>
<div>
{completedItems}
{failedItems > 0 && `${failedItems} 个失败`}
</div>
</div>
</div>
</div>
)}
{/* 操作提示 */}
{isRunning && (
<div className="mb-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-start">
<AlertCircle className="w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0" />
<div className="text-sm text-blue-800">
<div className="font-medium mb-1"></div>
<div>
使
</div>
</div>
</div>
</div>
)}
</div>
{/* 模态框底部 */}
<div className="flex items-center justify-between p-6 border-t border-gray-200">
<div className="flex items-center space-x-2">
{isRunning && (
<button
onClick={handleStopImport}
className="flex items-center px-3 py-2 text-sm font-medium text-red-600 bg-red-50 border border-red-200 rounded-lg hover:bg-red-100 transition-colors"
>
<Square className="w-4 h-4 mr-1" />
</button>
)}
{!isRunning && totalItems > 0 && (
<button
onClick={handleClearQueue}
className="flex items-center px-3 py-2 text-sm font-medium text-gray-600 bg-gray-50 border border-gray-200 rounded-lg hover:bg-gray-100 transition-colors"
>
</button>
)}
</div>
<button
onClick={onClose}
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors"
>
{isRunning ? '后台运行' : '关闭'}
</button>
</div>
</div>
</div>
);
};