import React, { useState } from 'react'; import { Modal, Upload, Button, Progress, message, Table, Alert, Space, Spin } from 'antd'; import { UploadOutlined, DownloadOutlined, CheckCircleOutlined, WarningOutlined, FileTextOutlined } from '@ant-design/icons'; import axios from 'axios'; import { designTokens } from '../../config/theme'; const modalHeaderStyle = { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '18px', fontWeight: 600, }; const ImportModal = ({ visible, deviceFields, onImport, onCancel, }) => { const [step, setStep] = useState('upload'); const [isPreviewing, setIsPreviewing] = useState(false); const [isConfirming, setIsConfirming] = useState(false); const [previewLoading, setPreviewLoading] = useState(false); const [previewData, setPreviewData] = useState(null); const [selectedFile, setSelectedFile] = useState(null); const [importProgress, setImportProgress] = useState(0); const [importPhase, setImportPhase] = useState(''); const [importResult, setImportResult] = useState(null); const api = axios.create({ baseURL: '/api', }); api.interceptors.request.use((config) => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); const resetState = () => { setStep('upload'); setIsPreviewing(false); setIsConfirming(false); setPreviewLoading(false); setPreviewData(null); setSelectedFile(null); setImportProgress(0); setImportPhase(''); setImportResult(null); }; const handleClose = () => { resetState(); onCancel(); }; const handlePreview = async (file) => { const actualFile = file.originFileObj || file; setSelectedFile(actualFile); setPreviewLoading(true); setIsPreviewing(true); try { const formData = new FormData(); formData.append('csvFile', actualFile); const response = await api.post('/devices/import-preview', formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); if (response.data.success) { setPreviewData(response.data.data); setStep('preview'); } else { message.error(response.data.error || '预览失败'); resetState(); } } catch (error) { console.error('预览失败:', error); message.error(error.response?.data?.error || '预览失败,请检查文件格式'); resetState(); } finally { setPreviewLoading(false); } return false; }; const handleConfirmImport = async () => { if (!selectedFile) { message.error('请先选择文件'); return; } setIsConfirming(true); setImportProgress(0); setImportPhase('正在上传文件...'); try { await onImport(selectedFile, { onProgress: (progress, phase) => { setImportProgress(progress); setImportPhase(phase); }, onSuccess: (result) => { setImportResult(result); setImportProgress(100); setImportPhase('导入完成'); setIsConfirming(false); setStep('result'); }, onError: (error) => { setImportResult({ success: false, statistics: { total: 0, success: 0, failed: 1, errors: [{ row: 0, error: error.message || '导入失败' }], }, }); setIsConfirming(false); setStep('result'); }, }); } catch (error) { setIsConfirming(false); message.error('导入失败'); resetState(); } }; const requiredFields = deviceFields.filter((f) => f.visible && f.required); const optionalFields = deviceFields.filter((f) => f.visible && !f.required); const previewColumns = previewData?.fieldList ? previewData.fieldList.map((field) => ({ title: field.displayName + (field.required ? ' *' : ''), dataIndex: field.fieldName, key: field.fieldName, width: 120, ellipsis: true, })) : [ { title: '行号', dataIndex: '_rowNum', key: '_rowNum', width: 60 }, { title: '设备名称', dataIndex: 'name', key: 'name', width: 120 }, { title: '设备类型', dataIndex: 'type', key: 'type', width: 80 }, { title: '品牌', dataIndex: 'model', key: 'model', width: 100 }, { title: '序列号', dataIndex: 'serialNumber', key: 'serialNumber', width: 140 }, { title: '所在机房', dataIndex: 'roomName', key: 'roomName', width: 100 }, { title: '所在机柜', dataIndex: 'rackName', key: 'rackName', width: 100 }, { title: '状态', dataIndex: 'status', key: 'status', width: 80 }, ]; const getRowClassName = (record) => { if (record._hasError) { return 'ant-table-row-error'; } return ''; }; const renderUploadStep = () => (
请上传CSV格式的设备数据文件
支持的编码格式:GBK
CSV文件格式要求:
| 行号 | 失败原因 |
|---|---|
| {item.row || index + 1} | {item.error || '未知错误'} |