feat(机柜管理): 增强Excel导入功能并完善状态显示

- 在导出功能中增加"维护中"状态的中文显示
- 重构导入功能以支持多种Excel格式和列名映射
- 添加状态值的中英文自动转换
- 改进数据验证逻辑和错误提示信息
This commit is contained in:
zhang1106
2026-02-09 08:37:19 +08:00
parent c83cf53413
commit fe6bc0adcc
+94 -17
View File
@@ -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) {