添加文件上传组件
- 实现SQL文件上传功能 - 支持拖拽上传
This commit is contained in:
parent
2e3eab5fed
commit
f14e62af66
294
src/components/FileUpload.js
Normal file
294
src/components/FileUpload.js
Normal 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;
|
||||
Loading…
Reference in New Issue
Block a user