添加文件上传组件

- 实现SQL文件上传功能
- 支持拖拽上传
This commit is contained in:
Leo 2025-10-20 14:05:57 +08:00
parent 2e3eab5fed
commit f14e62af66

View File

@ -0,0 +1,294 @@
/**
* 文件上传组件
*/
import React, { useState } from 'react';
import {
Upload,
Button,
Progress,
Alert,
Card,
Row,
Col,
Typography,
Space,
Divider,
Tag,
message,
} from 'antd';
import {
UploadOutlined,
CheckCircleOutlined,
ExclamationCircleOutlined,
DownloadOutlined,
CopyOutlined,
} from '@ant-design/icons';
import { apiService } from '../services/api';
import {
formatFileSize,
formatConversionTime,
validateSQLFile,
downloadFile,
copyToClipboard
} from '../utils/helpers';
const { Title, Text, Paragraph } = Typography;
const { Dragger } = Upload;
const FileUpload = ({ onUploadSuccess }) => {
const [uploading, setUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
// 处理文件上传
const handleUpload = async (file) => {
// 验证文件类型
if (!validateSQLFile(file)) {
message.error('只能上传.sql格式的文件');
return false;
}
// 验证文件大小 (50MB)
if (file.size > 50 * 1024 * 1024) {
message.error('文件大小不能超过50MB');
return false;
}
setUploading(true);
setUploadProgress(0);
setResult(null);
setError(null);
try {
const response = await apiService.uploadFile(file, (progress) => {
setUploadProgress(progress);
});
if (response.data.status === 'success') {
setResult(response.data.data);
message.success('文件转换成功!');
if (onUploadSuccess) {
onUploadSuccess(response.data.data);
}
} else {
throw new Error(response.data.message || '转换失败');
}
} catch (err) {
const errorMsg = err.message || '上传失败,请重试';
setError(errorMsg);
message.error(errorMsg);
} finally {
setUploading(false);
setUploadProgress(0);
}
return false; // 阻止默认上传
};
// 下载转换后的文件
const handleDownload = async () => {
if (!result?.record_id) return;
try {
const response = await apiService.downloadFile(result.record_id);
const blob = new Blob([response.data], { type: 'text/plain' });
downloadFile(blob, result.converted_filename);
message.success('文件下载成功');
} catch (err) {
message.error('下载失败: ' + err.message);
}
};
// 复制URL到剪贴板
const handleCopyUrl = async (url) => {
const success = await copyToClipboard(url);
if (success) {
message.success('URL已复制到剪贴板');
} else {
message.error('复制失败');
}
};
return (
<Card title="上传SQL文件进行转换" style={{ marginBottom: 24 }}>
<Row gutter={[24, 24]}>
<Col xs={24} lg={12}>
<Dragger
name="file"
accept=".sql"
beforeUpload={handleUpload}
showUploadList={false}
disabled={uploading}
style={{ padding: '40px 20px' }}
>
<p className="ant-upload-drag-icon">
<UploadOutlined style={{ fontSize: 48, color: '#1890ff' }} />
</p>
<p className="ant-upload-text">
点击或拖拽SQL文件到此区域上传
</p>
<p className="ant-upload-hint">
支持单个文件上传文件格式.sql最大支持50MB
</p>
</Dragger>
{uploading && (
<div style={{ marginTop: 16 }}>
<Progress
percent={uploadProgress}
status="active"
strokeColor={{
from: '#108ee9',
to: '#87d068',
}}
/>
<Text type="secondary" style={{ marginTop: 8, display: 'block' }}>
正在上传并转换文件...
</Text>
</div>
)}
{error && (
<Alert
message="转换失败"
description={error}
type="error"
showIcon
style={{ marginTop: 16 }}
/>
)}
</Col>
<Col xs={24} lg={12}>
<div style={{ padding: '20px 0' }}>
<Title level={4}>转换说明</Title>
<Paragraph>
本工具支持将 <Tag color="blue">SQL Server 2008</Tag> SQL <Tag color="green">MySQL 8</Tag>
</Paragraph>
<Divider />
<Title level={5}>主要转换内容</Title>
<ul style={{ paddingLeft: 20 }}>
<li>数据类型转换nvarchar VARCHAR</li>
<li>标识符转换[schema].[table] `table`</li>
<li>自增长字段IDENTITY AUTO_INCREMENT</li>
<li>函数转换GETDATE() NOW()</li>
<li>事务语法BEGIN TRANSACTION START TRANSACTION</li>
<li>移除SQL Server特定语法</li>
</ul>
</div>
</Col>
</Row>
{result && (
<div style={{ marginTop: 24 }}>
<Divider />
<Alert
message="转换成功"
type="success"
showIcon
style={{ marginBottom: 16 }}
icon={<CheckCircleOutlined />}
/>
<Card
title="转换结果"
size="small"
extra={
<Space>
<Button
type="primary"
icon={<DownloadOutlined />}
onClick={handleDownload}
>
下载文件
</Button>
</Space>
}
>
<Row gutter={[16, 16]}>
<Col xs={24} sm={12}>
<Text strong>原始文件</Text>
<br />
<Text>{result.original_filename}</Text>
</Col>
<Col xs={24} sm={12}>
<Text strong>转换后文件</Text>
<br />
<Text>{result.converted_filename}</Text>
</Col>
<Col xs={24} sm={12}>
<Text strong>文件大小</Text>
<br />
<Text>{formatFileSize(result.file_size)}</Text>
</Col>
<Col xs={24} sm={12}>
<Text strong>转换耗时</Text>
<br />
<Text>{formatConversionTime(result.conversion_time)}</Text>
</Col>
</Row>
{result.converted_url && (
<div style={{ marginTop: 16 }}>
<Text strong>下载链接</Text>
<br />
<Space>
<Text code copyable style={{ maxWidth: 400 }}>
{result.converted_url}
</Text>
<Button
size="small"
icon={<CopyOutlined />}
onClick={() => handleCopyUrl(result.converted_url)}
>
复制
</Button>
</Space>
</div>
)}
{result.warnings && result.warnings.length > 0 && (
<div style={{ marginTop: 16 }}>
<Alert
message="转换警告"
description={
<ul style={{ margin: 0, paddingLeft: 20 }}>
{result.warnings.map((warning, index) => (
<li key={index}>{warning}</li>
))}
</ul>
}
type="warning"
showIcon
icon={<ExclamationCircleOutlined />}
/>
</div>
)}
{result.validation_issues && result.validation_issues.length > 0 && (
<div style={{ marginTop: 16 }}>
<Alert
message="验证问题"
description={
<ul style={{ margin: 0, paddingLeft: 20 }}>
{result.validation_issues.map((issue, index) => (
<li key={index}>{issue}</li>
))}
</ul>
}
type="info"
showIcon
/>
</div>
)}
</Card>
</div>
)}
</Card>
);
};
export default FileUpload;