feat(导入功能): 增强导入流程的用户体验和错误处理
为耗材、设备和机柜管理页面添加导入进度显示和阶段提示 优化错误处理逻辑,提供更详细的错误信息 统一三个页面的导入界面样式和交互
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||||
import { Table, Button, Modal, Form, Input, Select, InputNumber, message, Card, Space, Popconfirm, Upload, Table as AntTable } from 'antd';
|
import { Table, Button, Modal, Form, Input, Select, InputNumber, message, Card, Space, Popconfirm, Upload, Table as AntTable, Progress } from 'antd';
|
||||||
import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, ExportOutlined, ImportOutlined, UploadOutlined, FileExcelOutlined, InboxOutlined } from '@ant-design/icons';
|
import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, ExportOutlined, ImportOutlined, UploadOutlined, FileExcelOutlined, InboxOutlined } from '@ant-design/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
@@ -25,7 +25,9 @@ function ConsumableManagement() {
|
|||||||
const [importPreview, setImportPreview] = useState([]);
|
const [importPreview, setImportPreview] = useState([]);
|
||||||
const [importFile, setImportFile] = useState(null);
|
const [importFile, setImportFile] = useState(null);
|
||||||
const [importing, setImporting] = useState(false);
|
const [importing, setImporting] = useState(false);
|
||||||
const importFormRef = useRef(null);
|
const [importProgress, setImportProgress] = useState(0);
|
||||||
|
const [importPhase, setImportPhase] = useState('');
|
||||||
|
const [importResult, setImportResult] = useState(null);
|
||||||
const [stockModalVisible, setStockModalVisible] = useState(false);
|
const [stockModalVisible, setStockModalVisible] = useState(false);
|
||||||
const [stockRecord, setStockRecord] = useState(null);
|
const [stockRecord, setStockRecord] = useState(null);
|
||||||
const [stockType, setStockType] = useState('in');
|
const [stockType, setStockType] = useState('in');
|
||||||
@@ -218,6 +220,9 @@ function ConsumableManagement() {
|
|||||||
setImportModalVisible(false);
|
setImportModalVisible(false);
|
||||||
setImportPreview([]);
|
setImportPreview([]);
|
||||||
setImportFile(null);
|
setImportFile(null);
|
||||||
|
setImportProgress(0);
|
||||||
|
setImportPhase('');
|
||||||
|
setImportResult(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImport = async () => {
|
const handleImport = async () => {
|
||||||
@@ -227,30 +232,119 @@ function ConsumableManagement() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setImporting(true);
|
setImporting(true);
|
||||||
|
setImportProgress(0);
|
||||||
|
setImportPhase('正在读取文件...');
|
||||||
|
setImportResult(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = async (e) => {
|
reader.onload = async (e) => {
|
||||||
const text = e.target.result;
|
const text = e.target.result;
|
||||||
|
setImportProgress(10);
|
||||||
|
setImportPhase('正在读取文件...');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setImportProgress(20);
|
||||||
|
setImportPhase('正在解析CSV数据...');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
const items = parseCSV(text);
|
const items = parseCSV(text);
|
||||||
|
const totalItems = items.length;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setImportProgress(30);
|
||||||
|
setImportPhase(`共解析 ${totalItems} 条记录,准备提交...`);
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setImportProgress(40);
|
||||||
|
setImportPhase('正在连接服务器...');
|
||||||
|
}, 300);
|
||||||
|
|
||||||
const response = await axios.post('/api/consumables/import', { items });
|
const response = await axios.post('/api/consumables/import', { items });
|
||||||
message.success(response.data.message);
|
|
||||||
|
|
||||||
if (response.data.results.failed > 0) {
|
setTimeout(() => {
|
||||||
response.data.results.errors.forEach(err => console.error(err));
|
setImportProgress(60);
|
||||||
}
|
setImportPhase('正在处理服务器响应...');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
setImportModalVisible(false);
|
setTimeout(() => {
|
||||||
setImportPreview([]);
|
setImportProgress(80);
|
||||||
setImportFile(null);
|
setImportPhase('正在更新本地数据...');
|
||||||
fetchConsumables();
|
}, 200);
|
||||||
|
|
||||||
|
const results = response.data.results;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setImportResult(results);
|
||||||
|
setImportProgress(100);
|
||||||
|
setImportPhase('导入完成');
|
||||||
|
setImporting(false);
|
||||||
|
|
||||||
|
if (results.failed > 0) {
|
||||||
|
message.warning(`导入完成,成功 ${results.success} 条,失败 ${results.failed} 条`);
|
||||||
|
} else {
|
||||||
|
message.success(response.data.message || `成功导入 ${results.success} 条记录`);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
reader.onerror = () => {
|
||||||
setImporting(false);
|
setImporting(false);
|
||||||
|
setImportProgress(0);
|
||||||
|
setImportPhase('文件读取失败');
|
||||||
|
setImportResult({
|
||||||
|
success: false,
|
||||||
|
total: 0,
|
||||||
|
imported: 0,
|
||||||
|
failed: 0,
|
||||||
|
errors: [{ row: '-', error: '文件读取失败,请检查文件是否损坏' }],
|
||||||
|
message: '文件读取失败'
|
||||||
|
});
|
||||||
|
message.error('文件读取失败');
|
||||||
};
|
};
|
||||||
reader.readAsText(importFile);
|
reader.readAsText(importFile);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('导入失败');
|
|
||||||
console.error('导入失败:', error);
|
|
||||||
setImporting(false);
|
setImporting(false);
|
||||||
|
setImportProgress(0);
|
||||||
|
setImportPhase('导入失败');
|
||||||
|
|
||||||
|
let errorMessage = '导入失败,请检查网络连接或服务器状态';
|
||||||
|
let errorDetails = [];
|
||||||
|
|
||||||
|
if (error.response && error.response.data) {
|
||||||
|
const { data } = error.response;
|
||||||
|
if (data.errors && Array.isArray(data.errors) && data.errors.length > 0) {
|
||||||
|
errorDetails = data.errors.map((err, index) => ({
|
||||||
|
row: err.row || index + 1,
|
||||||
|
error: err.error || err.message || '数据格式错误'
|
||||||
|
}));
|
||||||
|
errorMessage = `导入失败,共发现 ${errorDetails.length} 处数据错误`;
|
||||||
|
} else if (data.message) {
|
||||||
|
errorMessage = data.message;
|
||||||
|
} else if (data.error) {
|
||||||
|
errorMessage = data.error;
|
||||||
|
}
|
||||||
|
} else if (error.message) {
|
||||||
|
if (error.message.includes('Network Error') || error.message.includes('network')) {
|
||||||
|
errorMessage = '网络连接失败,请检查服务器是否运行';
|
||||||
|
} else if (error.message.includes('timeout')) {
|
||||||
|
errorMessage = '请求超时,请稍后重试';
|
||||||
|
} else {
|
||||||
|
errorMessage = error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setImportResult({
|
||||||
|
success: false,
|
||||||
|
total: 0,
|
||||||
|
imported: 0,
|
||||||
|
failed: 0,
|
||||||
|
errors: errorDetails,
|
||||||
|
message: errorMessage
|
||||||
|
});
|
||||||
|
|
||||||
|
message.error(errorMessage);
|
||||||
|
console.error('导入耗材失败:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -164,9 +164,10 @@ function DeviceManagement() {
|
|||||||
// 设备字段配置
|
// 设备字段配置
|
||||||
const [deviceFields, setDeviceFields] = useState([]);
|
const [deviceFields, setDeviceFields] = useState([]);
|
||||||
const [loadingFields, setLoadingFields] = useState(true);
|
const [loadingFields, setLoadingFields] = useState(true);
|
||||||
// 导入导出状态
|
// 导入状态
|
||||||
const [importModalVisible, setImportModalVisible] = useState(false);
|
const [importModalVisible, setImportModalVisible] = useState(false);
|
||||||
const [importProgress, setImportProgress] = useState(0);
|
const [importProgress, setImportProgress] = useState(0);
|
||||||
|
const [importPhase, setImportPhase] = useState('');
|
||||||
const [isImporting, setIsImporting] = useState(false);
|
const [isImporting, setIsImporting] = useState(false);
|
||||||
const [importResult, setImportResult] = useState(null);
|
const [importResult, setImportResult] = useState(null);
|
||||||
const [selectedDevices, setSelectedDevices] = useState([]);
|
const [selectedDevices, setSelectedDevices] = useState([]);
|
||||||
@@ -522,34 +523,41 @@ function DeviceManagement() {
|
|||||||
try {
|
try {
|
||||||
setIsImporting(true);
|
setIsImporting(true);
|
||||||
setImportProgress(0);
|
setImportProgress(0);
|
||||||
|
setImportPhase('正在上传文件...');
|
||||||
setImportResult(null);
|
setImportResult(null);
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('csvFile', file);
|
formData.append('csvFile', file);
|
||||||
|
|
||||||
// 模拟进度
|
|
||||||
const progressInterval = setInterval(() => {
|
|
||||||
setImportProgress(prev => {
|
|
||||||
if (prev >= 90) {
|
|
||||||
clearInterval(progressInterval);
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
return prev + 10;
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
const response = await axios.post('/api/devices/import', formData, {
|
const response = await axios.post('/api/devices/import', formData, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data'
|
||||||
|
},
|
||||||
|
onUploadProgress: (progressEvent) => {
|
||||||
|
const progress = Math.round((progressEvent.loaded * 50) / progressEvent.total);
|
||||||
|
setImportProgress(Math.min(progress, 50));
|
||||||
|
setImportPhase('正在上传文件...');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
clearInterval(progressInterval);
|
setImportProgress(60);
|
||||||
setImportProgress(100);
|
setImportPhase('正在处理数据...');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setImportProgress(80);
|
||||||
|
setImportPhase('正在验证数据...');
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setImportProgress(90);
|
||||||
|
setImportPhase('正在保存数据...');
|
||||||
|
}, 400);
|
||||||
|
|
||||||
setImportResult(response.data);
|
setImportResult(response.data);
|
||||||
|
setImportProgress(100);
|
||||||
|
setImportPhase('导入完成');
|
||||||
setIsImporting(false);
|
setIsImporting(false);
|
||||||
|
|
||||||
// 根据导入结果显示不同的消息
|
|
||||||
const { success, failed } = response.data.statistics;
|
const { success, failed } = response.data.statistics;
|
||||||
if (failed > 0) {
|
if (failed > 0) {
|
||||||
message.warning(`导入完成,但有 ${failed} 条记录导入失败`);
|
message.warning(`导入完成,但有 ${failed} 条记录导入失败`);
|
||||||
@@ -557,18 +565,47 @@ function DeviceManagement() {
|
|||||||
message.success('所有记录导入成功');
|
message.success('所有记录导入成功');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新加载设备列表
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fetchDevices();
|
fetchDevices();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsImporting(false);
|
setIsImporting(false);
|
||||||
message.error('导入失败');
|
setImportProgress(0);
|
||||||
|
|
||||||
|
let errorMessage = '导入失败';
|
||||||
|
let errorDetails = [];
|
||||||
|
|
||||||
|
if (error.response) {
|
||||||
|
const { data } = error.response;
|
||||||
|
if (data && data.errors && Array.isArray(data.errors)) {
|
||||||
|
errorDetails = data.errors.map((err, index) => ({
|
||||||
|
row: err.row || index + 1,
|
||||||
|
error: err.error || err.message || '未知错误'
|
||||||
|
}));
|
||||||
|
errorMessage = `导入失败,共发现 ${errorDetails.length} 处数据错误`;
|
||||||
|
} else if (data && data.message) {
|
||||||
|
errorMessage = data.message;
|
||||||
|
}
|
||||||
|
} else if (error.message) {
|
||||||
|
errorMessage = error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
setImportResult({
|
||||||
|
success: false,
|
||||||
|
statistics: {
|
||||||
|
total: 0,
|
||||||
|
success: 0,
|
||||||
|
failed: 0,
|
||||||
|
errors: errorDetails,
|
||||||
|
message: errorMessage
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
message.error(errorMessage);
|
||||||
console.error('导入设备失败:', error);
|
console.error('导入设备失败:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 阻止默认上传行为
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1188,11 +1225,12 @@ function DeviceManagement() {
|
|||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setImportModalVisible(false);
|
setImportModalVisible(false);
|
||||||
setImportProgress(0);
|
setImportProgress(0);
|
||||||
|
setImportPhase('');
|
||||||
setImportResult(null);
|
setImportResult(null);
|
||||||
setIsImporting(false);
|
setIsImporting(false);
|
||||||
}}
|
}}
|
||||||
footer={null}
|
footer={null}
|
||||||
width={600}
|
width={650}
|
||||||
destroyOnHidden
|
destroyOnHidden
|
||||||
styles={{ header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' }, body: { padding: '24px' } }}
|
styles={{ header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' }, body: { padding: '24px' } }}
|
||||||
>
|
>
|
||||||
@@ -1233,11 +1271,36 @@ function DeviceManagement() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : isImporting ? (
|
||||||
<div>
|
<div>
|
||||||
<p style={{ marginBottom: '16px', color: '#666' }}>正在导入数据...</p>
|
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '16px' }}>
|
||||||
<Progress percent={importProgress} status="active" style={{ marginBottom: '20px' }} strokeColor={{ '0%': '#667eea', '100%': '#764ba2' }} />
|
<div style={{
|
||||||
{importResult && importResult.statistics && (
|
width: '48px',
|
||||||
|
height: '48px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: '16px',
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: '20px'
|
||||||
|
}}>
|
||||||
|
<UploadOutlined spin />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style={{ margin: '0 0 4px 0', fontWeight: '600', color: '#333', fontSize: '16px' }}>正在导入设备数据</p>
|
||||||
|
<p style={{ margin: 0, color: '#667eea', fontSize: '14px' }}>{importPhase}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Progress
|
||||||
|
percent={importProgress}
|
||||||
|
status="active"
|
||||||
|
strokeColor={{ '0%': '#667eea', '100%': '#764ba2' }}
|
||||||
|
format={() => `${importProgress}%`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : importResult && importResult.statistics && (
|
||||||
<div>
|
<div>
|
||||||
<p style={{ marginBottom: '10px', fontWeight: '600' }}>导入完成:</p>
|
<p style={{ marginBottom: '10px', fontWeight: '600' }}>导入完成:</p>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '12px', marginBottom: '16px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '12px', marginBottom: '16px' }}>
|
||||||
@@ -1299,8 +1362,6 @@ function DeviceManagement() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ function RackManagement() {
|
|||||||
// 导入模态框状态
|
// 导入模态框状态
|
||||||
const [importModalVisible, setImportModalVisible] = useState(false);
|
const [importModalVisible, setImportModalVisible] = useState(false);
|
||||||
const [importProgress, setImportProgress] = useState(0);
|
const [importProgress, setImportProgress] = useState(0);
|
||||||
|
const [importPhase, setImportPhase] = useState('');
|
||||||
const [isImporting, setIsImporting] = useState(false);
|
const [isImporting, setIsImporting] = useState(false);
|
||||||
const [importResult, setImportResult] = useState(null);
|
const [importResult, setImportResult] = useState(null);
|
||||||
|
|
||||||
@@ -148,35 +149,41 @@ function RackManagement() {
|
|||||||
try {
|
try {
|
||||||
setIsImporting(true);
|
setIsImporting(true);
|
||||||
setImportProgress(0);
|
setImportProgress(0);
|
||||||
|
setImportPhase('正在上传文件...');
|
||||||
setImportResult(null);
|
setImportResult(null);
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
// 模拟进度
|
|
||||||
const progressInterval = setInterval(() => {
|
|
||||||
setImportProgress(prev => {
|
|
||||||
if (prev >= 90) {
|
|
||||||
clearInterval(progressInterval);
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
return prev + 10;
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
// 发送文件到后端处理
|
|
||||||
const response = await axios.post('/api/racks/import', formData, {
|
const response = await axios.post('/api/racks/import', formData, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data'
|
||||||
|
},
|
||||||
|
onUploadProgress: (progressEvent) => {
|
||||||
|
const progress = Math.round((progressEvent.loaded * 50) / progressEvent.total);
|
||||||
|
setImportProgress(Math.min(progress, 50));
|
||||||
|
setImportPhase('正在上传文件...');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
clearInterval(progressInterval);
|
setImportProgress(60);
|
||||||
|
setImportPhase('正在处理数据...');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setImportProgress(80);
|
||||||
|
setImportPhase('正在验证数据...');
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setImportProgress(90);
|
||||||
|
setImportPhase('正在保存数据...');
|
||||||
|
}, 400);
|
||||||
|
|
||||||
setImportProgress(100);
|
setImportProgress(100);
|
||||||
|
setImportPhase('导入完成');
|
||||||
setImportResult(response.data);
|
setImportResult(response.data);
|
||||||
setIsImporting(false);
|
setIsImporting(false);
|
||||||
|
|
||||||
// 根据导入结果显示不同的消息
|
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
const { imported, duplicates, total } = response.data;
|
const { imported, duplicates, total } = response.data;
|
||||||
if (duplicates > 0) {
|
if (duplicates > 0) {
|
||||||
@@ -188,31 +195,44 @@ function RackManagement() {
|
|||||||
message.error(response.data.message || '导入失败');
|
message.error(response.data.message || '导入失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 阻止自动上传
|
|
||||||
return false;
|
return false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsImporting(false);
|
setIsImporting(false);
|
||||||
|
setImportProgress(0);
|
||||||
|
|
||||||
let errorMessage = '机柜导入失败';
|
let errorMessage = '机柜导入失败';
|
||||||
let errorDetails = null;
|
let errorDetails = [];
|
||||||
|
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
// 服务器返回了错误响应
|
const { data } = error.response;
|
||||||
errorMessage = error.response.data.message || error.response.data.error || errorMessage;
|
if (data && data.errors && Array.isArray(data.errors)) {
|
||||||
errorDetails = error.response.data.details;
|
errorDetails = data.errors.map((err, index) => ({
|
||||||
} else if (error.request) {
|
row: err.row || index + 1,
|
||||||
// 请求已发送但没有收到响应
|
error: err.error || err.message || '未知错误'
|
||||||
errorMessage = '网络错误,服务器没有响应';
|
}));
|
||||||
} else {
|
errorMessage = `导入失败,共发现 ${errorDetails.length} 处数据错误`;
|
||||||
// 请求配置错误
|
} else if (data && data.message) {
|
||||||
errorMessage = `请求错误: ${error.message}`;
|
errorMessage = data.message;
|
||||||
|
} else if (data && data.details && Array.isArray(data.details)) {
|
||||||
|
errorDetails = data.details.map((err, index) => ({
|
||||||
|
row: err.row || index + 1,
|
||||||
|
error: err.error || err.message || '未知错误'
|
||||||
|
}));
|
||||||
|
errorMessage = `导入失败,共发现 ${errorDetails.length} 处数据错误`;
|
||||||
|
}
|
||||||
|
} else if (error.message) {
|
||||||
|
errorMessage = error.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
setImportResult({ success: false, message: errorMessage, details: errorDetails });
|
setImportResult({
|
||||||
|
success: false,
|
||||||
|
message: errorMessage,
|
||||||
|
details: errorDetails
|
||||||
|
});
|
||||||
|
|
||||||
message.error(errorMessage);
|
message.error(errorMessage);
|
||||||
console.error('机柜导入失败:', error);
|
console.error('机柜导入失败:', error);
|
||||||
|
|
||||||
// 阻止自动上传
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
@@ -512,11 +532,12 @@ function RackManagement() {
|
|||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setImportModalVisible(false);
|
setImportModalVisible(false);
|
||||||
setImportProgress(0);
|
setImportProgress(0);
|
||||||
|
setImportPhase('');
|
||||||
setImportResult(null);
|
setImportResult(null);
|
||||||
setIsImporting(false);
|
setIsImporting(false);
|
||||||
}}
|
}}
|
||||||
footer={null}
|
footer={null}
|
||||||
width={600}
|
width={650}
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
styles={{
|
styles={{
|
||||||
body: { padding: '24px' },
|
body: { padding: '24px' },
|
||||||
@@ -599,21 +620,39 @@ function RackManagement() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : isImporting ? (
|
||||||
<div style={{ textAlign: 'center', padding: '20px 0' }}>
|
<div>
|
||||||
<p style={{ marginBottom: '20px', fontWeight: '500' }}>
|
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '16px' }}>
|
||||||
{isImporting ? '正在导入数据...' : '导入完成'}
|
<div style={{
|
||||||
</p>
|
width: '48px',
|
||||||
|
height: '48px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: '16px',
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: '20px'
|
||||||
|
}}>
|
||||||
|
<UploadOutlined spin />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style={{ margin: '0 0 4px 0', fontWeight: '600', color: '#333', fontSize: '16px' }}>正在导入机柜数据</p>
|
||||||
|
<p style={{ margin: 0, color: '#667eea', fontSize: '14px' }}>{importPhase}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Progress
|
<Progress
|
||||||
percent={importProgress}
|
percent={importProgress}
|
||||||
status={isImporting ? "active" : "success"}
|
status="active"
|
||||||
style={{ marginBottom: '24px' }}
|
|
||||||
strokeColor={{
|
strokeColor={{
|
||||||
'0%': '#667eea',
|
'0%': '#667eea',
|
||||||
'100%': '#764ba2'
|
'100%': '#764ba2'
|
||||||
}}
|
}}
|
||||||
|
format={() => `${importProgress}%`}
|
||||||
/>
|
/>
|
||||||
{importResult && (
|
</div>
|
||||||
|
) : importResult && (
|
||||||
<div style={{
|
<div style={{
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
background: '#f6f6f6',
|
background: '#f6f6f6',
|
||||||
@@ -658,23 +697,21 @@ function RackManagement() {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setImportModalVisible(false);
|
||||||
|
setImportProgress(0);
|
||||||
|
setImportResult(null);
|
||||||
|
setIsImporting(false);
|
||||||
|
fetchRacks();
|
||||||
|
}}
|
||||||
|
style={primaryButtonStyle}
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => {
|
|
||||||
setImportModalVisible(false);
|
|
||||||
setImportProgress(0);
|
|
||||||
setImportResult(null);
|
|
||||||
setIsImporting(false);
|
|
||||||
fetchRacks();
|
|
||||||
}}
|
|
||||||
style={primaryButtonStyle}
|
|
||||||
>
|
|
||||||
确定
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user