新增功能: - 添加详细的模板导入日志系统 - 实现全局进度存储机制 - 完善模板状态管理 修复问题: - 修复进度监控无限轮询问题 - 修复模板列表状态显示不正确问题 - 修复所有unwrap()导致的panic错误 - 修复外键约束失败问题 改进: - 优化素材上传逻辑,只上传视频/音频/图片 - 上传失败时自动跳过而不是中断导入 - 缺失文件时继续导入而不是失败 - 改进错误处理机制
283 lines
10 KiB
TypeScript
283 lines
10 KiB
TypeScript
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>
|
||
);
|
||
};
|