添加文件上传组件
- 实现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