Files
mixvideo-v2/apps/desktop/src/components/template/ImportTemplateModal.tsx
2025-07-14 22:12:39 +08:00

244 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from 'react';
import { X, Upload, AlertCircle, CheckCircle } from 'lucide-react';
import { invoke } from '@tauri-apps/api/core';
import { ImportTemplateRequest } from '../../types/template';
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 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);
}
};
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}
onClick={handleFileSelect}
>
{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>
</>
)}
</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="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>
);
};