diff --git a/backend/routes/racks.js b/backend/routes/racks.js index eadbaf8..8e59432 100644 --- a/backend/routes/racks.js +++ b/backend/routes/racks.js @@ -124,7 +124,7 @@ router.get('/export', async (req, res) => { '当前功耗(W)': rack.currentPower || 0, '设备数量': deviceCount, '设备总功耗(W)': totalPower, - '状态': rack.status === 'active' ? '启用' : '停用', + '状态': rack.status === 'active' ? '启用' : rack.status === 'maintenance' ? '维护中' : '停用', '创建时间': rack.createdAt ? new Date(rack.createdAt).toLocaleString() : '' }; }); @@ -357,12 +357,62 @@ router.post('/import', async (req, res) => { const worksheet = workbook.Sheets[workbook.SheetNames[0]]; - // 转换为JSON格式 - const jsonData = XLSX.utils.sheet_to_json(worksheet, { - header: ['rackId', 'name', 'roomName', 'height', 'maxPower', 'status'], + // 读取第一行作为列头 + const headerRow = XLSX.utils.sheet_to_json(worksheet, { header: 1, range: 0, limit: 1 })[0] || []; + + // 定义列名映射(支持导入模板格式和导出文件格式) + const columnMapping = { + // 机柜ID - 支持多种列名 + rackId: ['机柜ID(留空自动生成)', '机柜ID'], + // 机柜名称 + name: ['机柜名称'], + // 所属机房名称 + roomName: ['所属机房名称', '所属机房'], + // 高度(U) + height: ['高度(U)', '机柜高度(U)'], + // 最大功率/最大功耗 + maxPower: ['最大功率(W)', '最大功耗(W)'], + // 状态 + status: ['状态'] + }; + + // 根据列头自动检测列索引映射 + const columnIndexMap = {}; + Object.keys(columnMapping).forEach(field => { + const possibleNames = columnMapping[field]; + const index = headerRow.findIndex(h => possibleNames.includes(String(h).trim())); + if (index !== -1) { + columnIndexMap[field] = index; + } + }); + + // 检查必需的列是否存在 + const requiredColumns = ['name', 'roomName', 'height', 'maxPower', 'status']; + const missingColumns = requiredColumns.filter(col => columnIndexMap[col] === undefined); + if (missingColumns.length > 0) { + return res.status(400).json({ + success: false, + message: 'Excel列名格式不正确', + error: `缺少必需的列: ${missingColumns.join(', ')},请使用系统导出的文件或下载导入模板` + }); + } + + // 转换为JSON格式(使用检测到的列索引) + const rawData = XLSX.utils.sheet_to_json(worksheet, { + header: headerRow.map((h, i) => `col_${i}`), range: 1, // 从第2行开始读取数据 blankrows: false // 跳过空行 }); + + // 根据列映射转换数据 + const jsonData = rawData.map(row => { + const item = {}; + Object.keys(columnIndexMap).forEach(field => { + const colIndex = columnIndexMap[field]; + item[field] = row[`col_${colIndex}`]; + }); + return item; + }); if (jsonData.length === 0) { return res.status(400).json({ @@ -372,7 +422,18 @@ router.post('/import', async (req, res) => { }); } - // 验证数据 + // 状态值转换映射(中文 → 英文) + const statusMapping = { + '启用': 'active', + '在用': 'active', + '停用': 'inactive', + '禁用': 'inactive', + '维护中': 'maintenance', + 'active': 'active', + 'inactive': 'inactive', + 'maintenance': 'maintenance' + }; + const validStatuses = ['active', 'maintenance', 'inactive']; const validationResults = []; @@ -381,38 +442,54 @@ router.post('/import', async (req, res) => { const roomNameToIdMap = new Map(allRooms.map(room => [room.name, room.roomId])); const validRoomNames = new Set(roomNameToIdMap.keys()); - // 为没有rackId的记录自动生成 - let autoGeneratedIdIndex = 0; + // 处理数据(转换状态值、处理空rackId) const processedData = jsonData.map((item, index) => { const rowNumber = index + 2; - // 如果rackId为空,先标记为null,后续统一生成 - if (!item.rackId || item.rackId.trim() === '') { - return { ...item, rackId: null, rowNumber, _autoGenerate: true }; + // 转换状态值(中文转英文) + const rawStatus = String(item.status || '').trim(); + const normalizedStatus = statusMapping[rawStatus] || rawStatus.toLowerCase(); + + // 如果rackId为空或列不存在,标记为需要自动生成 + const rawRackId = item.rackId ? String(item.rackId).trim() : ''; + if (!rawRackId || rawRackId === '') { + return { + ...item, + rackId: null, + status: normalizedStatus, + rowNumber, + _autoGenerate: true + }; } - return { ...item, rowNumber, _autoGenerate: false }; + return { + ...item, + rackId: rawRackId, + status: normalizedStatus, + rowNumber, + _autoGenerate: false + }; }); // 验证数据 processedData.forEach((item) => { const errors = []; - // rackId为null表示需要自动生成,跳过验证 + // rackId为null表示需要自动生成,跳过格式验证 if (item.rackId !== null) { // 验证机柜ID格式 - if (!/^RACK\d+$/.test(item.rackId.trim())) { + if (!/^RACK\d+$/.test(item.rackId)) { errors.push('机柜ID格式应为RACK+数字,如RACK001'); } } - if (!item.name || item.name.trim() === '') { + if (!item.name || String(item.name).trim() === '') { errors.push('机柜名称不能为空'); } - if (!item.roomName || item.roomName.trim() === '') { + if (!item.roomName || String(item.roomName).trim() === '') { errors.push('所属机房名称不能为空'); - } else if (!validRoomNames.has(item.roomName.trim())) { + } else if (!validRoomNames.has(String(item.roomName).trim())) { errors.push(`所属机房名称不存在: ${item.roomName}`); } @@ -425,7 +502,7 @@ router.post('/import', async (req, res) => { } if (!item.status || !validStatuses.includes(item.status)) { - errors.push(`状态必须是以下值之一: ${validStatuses.join(', ')}`); + errors.push(`状态必须是以下值之一: ${validStatuses.join(', ')} (或对应中文: 启用/在用、停用/禁用、维护中)`); } if (errors.length > 0) {