新增功能: - 添加详细的模板导入日志系统 - 实现全局进度存储机制 - 完善模板状态管理 修复问题: - 修复进度监控无限轮询问题 - 修复模板列表状态显示不正确问题 - 修复所有unwrap()导致的panic错误 - 修复外键约束失败问题 改进: - 优化素材上传逻辑,只上传视频/音频/图片 - 上传失败时自动跳过而不是中断导入 - 缺失文件时继续导入而不是失败 - 改进错误处理机制
277 lines
9.4 KiB
TypeScript
277 lines
9.4 KiB
TypeScript
import React, { useState } from 'react';
|
||
import { X, Upload, File, AlertCircle, CheckCircle } from 'lucide-react';
|
||
import { invoke } from '@tauri-apps/api/core';
|
||
import { ImportTemplateRequest } from '../../types/template';
|
||
import { CustomSelect } from '../CustomSelect';
|
||
import { useProjectStore } from '../../store/projectStore';
|
||
|
||
interface ImportTemplateModalProps {
|
||
onClose: () => void;
|
||
onImport: (request: ImportTemplateRequest) => Promise<void>;
|
||
isLoading: boolean;
|
||
}
|
||
|
||
export const ImportTemplateModal: React.FC<ImportTemplateModalProps> = ({
|
||
onClose,
|
||
onImport,
|
||
isLoading,
|
||
}) => {
|
||
const [formData, setFormData] = useState<Partial<ImportTemplateRequest>>({
|
||
file_path: '',
|
||
template_name: '',
|
||
project_id: '',
|
||
auto_upload: true,
|
||
});
|
||
const [dragOver, setDragOver] = useState(false);
|
||
const [selectedFile, setSelectedFile] = useState<string>('');
|
||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||
|
||
const { projects } = useProjectStore();
|
||
|
||
// 处理文件选择
|
||
const handleFileSelect = async () => {
|
||
try {
|
||
// 使用 Tauri 命令选择文件
|
||
const selected = await invoke<string | null>('select_file', {
|
||
filters: [['剪映草稿文件', ['json']]]
|
||
});
|
||
|
||
if (selected) {
|
||
setSelectedFile(selected);
|
||
setFormData(prev => ({
|
||
...prev,
|
||
file_path: selected,
|
||
template_name: prev.template_name || extractTemplateNameFromPath(selected),
|
||
}));
|
||
setErrors(prev => ({ ...prev, file_path: '' }));
|
||
}
|
||
} catch (error) {
|
||
console.error('文件选择失败:', error);
|
||
}
|
||
};
|
||
|
||
// 从文件路径提取模板名称
|
||
const extractTemplateNameFromPath = (filePath: string) => {
|
||
const parts = filePath.split(/[/\\]/);
|
||
const fileName = parts[parts.length - 1];
|
||
return fileName.replace('.json', '').replace('draft_content', '模板');
|
||
};
|
||
|
||
// 处理拖拽
|
||
const handleDragOver = (e: React.DragEvent) => {
|
||
e.preventDefault();
|
||
setDragOver(true);
|
||
};
|
||
|
||
const handleDragLeave = (e: React.DragEvent) => {
|
||
e.preventDefault();
|
||
setDragOver(false);
|
||
};
|
||
|
||
const handleDrop = (e: React.DragEvent) => {
|
||
e.preventDefault();
|
||
setDragOver(false);
|
||
|
||
const files = Array.from(e.dataTransfer.files);
|
||
const jsonFile = files.find(file => file.name.endsWith('.json'));
|
||
|
||
if (jsonFile) {
|
||
// 注意:在 Tauri 中,我们需要使用文件路径而不是 File 对象
|
||
// 这里需要根据实际的 Tauri API 来处理文件路径获取
|
||
console.log('拖拽文件:', jsonFile.name);
|
||
// 实际实现中需要获取文件的完整路径
|
||
}
|
||
};
|
||
|
||
// 表单验证
|
||
const validateForm = () => {
|
||
const newErrors: Record<string, string> = {};
|
||
|
||
if (!formData.file_path) {
|
||
newErrors.file_path = '请选择剪映草稿文件';
|
||
}
|
||
|
||
if (!formData.template_name?.trim()) {
|
||
newErrors.template_name = '请输入模板名称';
|
||
}
|
||
|
||
setErrors(newErrors);
|
||
return Object.keys(newErrors).length === 0;
|
||
};
|
||
|
||
// 处理提交
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
|
||
if (!validateForm()) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await onImport(formData as ImportTemplateRequest);
|
||
} catch (error) {
|
||
console.error('导入失败:', error);
|
||
}
|
||
};
|
||
|
||
const projectOptions = [
|
||
{ value: '', label: '不关联项目' },
|
||
...projects.map(project => ({
|
||
value: project.id,
|
||
label: project.name,
|
||
})),
|
||
];
|
||
|
||
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">
|
||
{/* 模态框头部 */}
|
||
<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>
|
||
|
||
{/* 模态框内容 */}
|
||
<form onSubmit={handleSubmit} className="p-6">
|
||
{/* 文件选择区域 */}
|
||
<div className="mb-6">
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
剪映草稿文件 *
|
||
</label>
|
||
|
||
<div
|
||
className={`border-2 border-dashed rounded-lg p-6 text-center transition-colors ${
|
||
dragOver
|
||
? 'border-blue-400 bg-blue-50'
|
||
: errors.file_path
|
||
? 'border-red-300 bg-red-50'
|
||
: 'border-gray-300 hover:border-gray-400'
|
||
}`}
|
||
onDragOver={handleDragOver}
|
||
onDragLeave={handleDragLeave}
|
||
onDrop={handleDrop}
|
||
>
|
||
{selectedFile ? (
|
||
<div className="flex items-center justify-center">
|
||
<CheckCircle className="w-8 h-8 text-green-500 mr-3" />
|
||
<div className="text-left">
|
||
<div className="text-sm font-medium text-gray-900">
|
||
{selectedFile.split(/[/\\]/).pop()}
|
||
</div>
|
||
<div className="text-xs text-gray-500">
|
||
{selectedFile}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<>
|
||
<Upload className="w-8 h-8 text-gray-400 mx-auto mb-2" />
|
||
<div className="text-sm text-gray-600 mb-2">
|
||
拖拽 draft_content.json 文件到此处
|
||
</div>
|
||
<div className="text-xs text-gray-500 mb-3">
|
||
或者点击下方按钮选择文件
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
<button
|
||
type="button"
|
||
onClick={handleFileSelect}
|
||
className="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition-colors"
|
||
>
|
||
<File className="w-4 h-4 mr-2" />
|
||
选择文件
|
||
</button>
|
||
</div>
|
||
|
||
{errors.file_path && (
|
||
<div className="flex items-center mt-2 text-sm text-red-600">
|
||
<AlertCircle className="w-4 h-4 mr-1" />
|
||
{errors.file_path}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 模板名称 */}
|
||
<div className="mb-6">
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
模板名称 *
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={formData.template_name || ''}
|
||
onChange={(e) => setFormData(prev => ({ ...prev, template_name: e.target.value }))}
|
||
className={`w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||
errors.template_name ? 'border-red-300' : 'border-gray-300'
|
||
}`}
|
||
placeholder="输入模板名称"
|
||
/>
|
||
{errors.template_name && (
|
||
<div className="flex items-center mt-1 text-sm text-red-600">
|
||
<AlertCircle className="w-4 h-4 mr-1" />
|
||
{errors.template_name}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 关联项目 */}
|
||
<div className="mb-6">
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
关联项目
|
||
</label>
|
||
<CustomSelect
|
||
value={formData.project_id || ''}
|
||
onChange={(value) => setFormData(prev => ({ ...prev, project_id: value }))}
|
||
options={projectOptions}
|
||
placeholder="选择项目(可选)"
|
||
className="w-full"
|
||
/>
|
||
</div>
|
||
|
||
{/* 自动上传选项 */}
|
||
<div className="mb-6">
|
||
<label className="flex items-center">
|
||
<input
|
||
type="checkbox"
|
||
checked={formData.auto_upload}
|
||
onChange={(e) => setFormData(prev => ({ ...prev, auto_upload: e.target.checked }))}
|
||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||
/>
|
||
<span className="ml-2 text-sm text-gray-700">
|
||
自动上传素材到云端存储
|
||
</span>
|
||
</label>
|
||
<p className="text-xs text-gray-500 mt-1">
|
||
启用后将自动上传模板中的素材文件到云端,便于跨设备访问
|
||
</p>
|
||
</div>
|
||
|
||
{/* 操作按钮 */}
|
||
<div className="flex items-center justify-end space-x-3">
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
disabled={isLoading}
|
||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||
>
|
||
{isLoading ? '导入中...' : '开始导入'}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|