import React, { useState, useCallback } from 'react'; import { Modal, Upload, Table, Button, Space, Checkbox, Alert, Tag, Typography, Progress, Spin, message, } from 'antd'; import { UploadOutlined, DownloadOutlined, ImportOutlined, CloudServerOutlined, CheckCircleOutlined, CloseCircleOutlined, } from '@ant-design/icons'; import * as XLSX from 'xlsx'; import Papa from 'papaparse'; import api from '../api'; import { designTokens } from '../config/theme'; import CloseButton from './CloseButton'; const { Text, Title } = Typography; function NetworkCardImportModal({ visible, onClose, onSuccess }) { const [importPreview, setImportPreview] = useState([]); const [importErrors, setImportErrors] = useState([]); const [importProgress, setImportProgress] = useState({ current: 0, total: 0 }); const [importing, setImporting] = useState(false); const [skipExisting, setSkipExisting] = useState(false); const [updateExisting, setUpdateExisting] = useState(false); const generateUniqueNicId = () => { return `NIC-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; }; const handleFileUpload = (file, onSuccess) => { const reader = new FileReader(); reader.onload = async e => { try { const data = e.target.result; let parsedData = []; if (file.name.endsWith('.xlsx') || file.name.endsWith('.xls')) { const workbook = XLSX.read(data, { type: 'binary' }); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; parsedData = XLSX.utils.sheet_to_json(worksheet); } else if (file.name.endsWith('.csv')) { parsedData = Papa.parse(data, { header: true, skipEmptyLines: true, }).data; } else { message.error('不支持的文件格式,请上传 .xlsx 或 .csv 文件'); return; } if (parsedData.length === 0) { message.warning('文件内容为空,请检查文件内容'); return; } const validatedResult = await validateImportData(parsedData); setImportPreview(validatedResult.validData); setImportErrors(validatedResult.errors); setImportProgress({ current: 0, total: validatedResult.validData.length }); if (onSuccess) onSuccess(); } catch (error) { message.error('文件解析失败'); console.error('文件解析失败:', error); } }; if (file.name.endsWith('.csv')) { reader.readAsText(file); } else { reader.readAsBinaryString(file); } }; const validateImportData = async data => { const validData = []; const allErrors = []; for (let i = 0; i < data.length; i++) { const row = data[i]; const result = await validateNetworkCardRow(row, i); if (result.valid) { validData.push(row); } else { allErrors.push(...result.errors.map(err => ({ ...err, originalRow: row }))); } } if (allErrors.length > 0) { message.warning({ content: `发现 ${allErrors.length} 个数据错误,请查看错误详情并修正后重新导入`, duration: 5, }); } return { validData, errors: allErrors }; }; const validateNetworkCardRow = async (row, index) => { const rowNum = index + 2; const errors = []; if (!row['设备SN']) { errors.push({ row: rowNum, field: '设备SN', value: row['设备SN'] || '(空)', error: '缺少必填字段', suggestion: '请填写设备SN,格式如:SNAA0001', }); } if (!row['网卡名称']) { errors.push({ row: rowNum, field: '网卡名称', value: row['网卡名称'] || '(空)', error: '缺少必填字段', suggestion: '请填写网卡名称,格式如:网卡1、eth0', }); } if (row['插槽编号']) { const slotPattern = /^\d+$/; if (!slotPattern.test(row['插槽编号'])) { errors.push({ row: rowNum, field: '插槽编号', value: row['插槽编号'], error: '插槽编号格式错误', suggestion: '插槽编号必须为数字,如:1、2', }); } } if (errors.length > 0) { return { valid: false, errors }; } return { valid: true }; }; const handleBatchImport = async () => { if (importPreview.length === 0) { message.warning('请先选择要导入的数据'); return; } setImporting(true); setImportProgress({ current: 0, total: importPreview.length }); let progressInterval; let currentProgress = 0; try { const networkCardsData = importPreview.map((row, index) => ({ nicId: generateUniqueNicId(), deviceSn: row['设备SN'], name: row['网卡名称'], slotNumber: row['插槽编号'] ? parseInt(row['插槽编号']) : null, model: row['网卡型号'] || null, manufacturer: row['制造商'] || null, description: row['描述'] || null, })); progressInterval = setInterval(() => { currentProgress = Math.min(currentProgress + Math.random() * 15, 85); setImportProgress(prev => ({ ...prev, current: Math.floor((currentProgress / 100) * importPreview.length), })); }, 200); const response = await api.post('/network-cards/batch', { networkCards: networkCardsData, skipExisting, updateExisting, }); const { total, success, failed, skipped = 0, updated = 0, errors } = response; clearInterval(progressInterval); setImportProgress({ current: importPreview.length, total: importPreview.length }); let msgContent = ''; if (updated > 0) { msgContent += `更新 ${updated} 个,`; } if (skipped > 0) { msgContent += `跳过 ${skipped} 个,`; } if (success > 0) { msgContent += `新增 ${success - updated} 个,`; } if (failed > 0) { msgContent += `失败 ${failed} 个`; Modal.error({ title: '导入失败详情', content: (
点击或拖拽文件到此处上传
支持 .xlsx, .xls, .csv 格式文件