feat(机柜管理): 添加机柜数据导出功能并优化设备删除逻辑

添加机柜数据导出功能,支持Excel格式下载
在设备删除逻辑中增加网卡删除处理
调整设备ID字段为非必填且隐藏
This commit is contained in:
zhang1106
2026-02-06 16:23:56 +08:00
parent aa5ab5fc17
commit e525fa4f11
4 changed files with 169 additions and 9 deletions
+2 -2
View File
@@ -8,9 +8,9 @@ const defaultDeviceFields = [
fieldName: 'deviceId',
displayName: '设备ID',
fieldType: 'string',
required: true,
required: false,
order: 1,
visible: true,
visible: false,
isSystem: true
},
{
+21 -7
View File
@@ -14,6 +14,7 @@ const DeviceField = require('../models/DeviceField');
const Ticket = require('../models/Ticket');
const DevicePort = require('../models/DevicePort'); // Import DevicePort
const Cable = require('../models/Cable'); // Import Cable
const NetworkCard = require('../models/NetworkCard'); // Import NetworkCard
const { validateBody, validateQuery } = require('../middleware/validation');
const {
createDeviceSchema,
@@ -1078,19 +1079,25 @@ router.delete('/batch-delete', validateBody(batchDeviceIdsSchema), async (req, r
transaction: t
});
// 2. 删除相关端口 (Delete associated DevicePorts)
// 2. 删除相关网卡 (Delete associated NetworkCards)
await NetworkCard.destroy({
where: { deviceId: { [Op.in]: deviceIds } },
transaction: t
});
// 3. 删除相关端口 (Delete associated DevicePorts)
await DevicePort.destroy({
where: { deviceId: { [Op.in]: deviceIds } },
transaction: t
});
// 3. 解除工单关联
// 4. 解除工单关联
await Ticket.update(
{ deviceId: null },
{ where: { deviceId: { [Op.in]: deviceIds } }, transaction: t }
);
// 4. 删除设备
// 5. 删除设备
const deletedCount = await Device.destroy({
where: { deviceId: { [Op.in]: deviceIds } },
transaction: t
@@ -1116,7 +1123,8 @@ router.delete('/batch-delete', validateBody(batchDeviceIdsSchema), async (req, r
});
} catch (error) {
await t.rollback();
res.status(500).json({ error: error.message });
console.error('批量删除设备错误:', error);
res.status(500).json({ error: error.message, stack: error.stack });
}
});
@@ -1145,20 +1153,26 @@ router.delete('/:deviceId', async (req, res) => {
transaction: t
});
// 2. 删除相关端口 (Delete associated DevicePorts)
// 2. 删除相关网卡 (Delete associated NetworkCards)
const deletedNetworkCards = await NetworkCard.destroy({
where: { deviceId: deviceId },
transaction: t
});
// 3. 删除相关端口 (Delete associated DevicePorts)
// 必须在删除设备之前删除,否则触发外键约束错误
const deletedPorts = await DevicePort.destroy({
where: { deviceId: deviceId },
transaction: t
});
// 3. 解除工单关联 (Unlink Tickets)
// 4. 解除工单关联 (Unlink Tickets)
await Ticket.update(
{ deviceId: null },
{ where: { deviceId: deviceId }, transaction: t }
);
// 4. 删除设备 (Delete Device)
// 5. 删除设备 (Delete Device)
await Device.destroy({
where: { deviceId: deviceId },
transaction: t
+97
View File
@@ -452,4 +452,101 @@ router.post('/import', async (req, res) => {
}
});
// 导出租机柜数据
router.get('/export', async (req, res) => {
try {
// 获取所有机柜数据(包含机房信息)
const racks = await Rack.findAll({
include: [
{ model: Room, attributes: ['name'] },
{ model: Device, attributes: ['deviceId', 'name', 'powerConsumption'] }
],
order: [['rackId', 'ASC']]
});
// 准备导出数据
const exportData = racks.map(rack => {
const deviceCount = rack.Devices ? rack.Devices.length : 0;
const totalPower = rack.Devices ? rack.Devices.reduce((sum, d) => sum + (d.powerConsumption || 0), 0) : 0;
return {
'机柜ID': rack.rackId,
'机柜名称': rack.name,
'所属机房': rack.Room ? rack.Room.name : '',
'机柜高度(U)': rack.height,
'最大功耗(W)': rack.maxPower,
'当前功耗(W)': rack.currentPower || 0,
'设备数量': deviceCount,
'设备总功耗(W)': totalPower,
'状态': rack.status === 'active' ? '启用' : '停用',
'创建时间': rack.createdAt ? new Date(rack.createdAt).toLocaleString() : ''
};
});
// 创建工作簿
const wb = XLSX.utils.book_new();
const ws = XLSX.utils.json_to_sheet(exportData);
// 设置列宽
ws['!cols'] = [
{ wch: 15 }, // 机柜ID
{ wch: 20 }, // 机柜名称
{ wch: 20 }, // 所属机房
{ wch: 12 }, // 机柜高度
{ wch: 15 }, // 最大功耗
{ wch: 15 }, // 当前功耗
{ wch: 12 }, // 设备数量
{ wch: 15 }, // 设备总功耗
{ wch: 10 }, // 状态
{ wch: 20 } // 创建时间
];
XLSX.utils.book_append_sheet(wb, ws, '机柜列表');
// 生成文件名
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
const fileName = `机柜导出_${timestamp}.xlsx`;
// 确保temp目录存在
const tempDir = path.join(__dirname, '../temp');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const filePath = path.join(tempDir, fileName);
// 写入文件
XLSX.writeFile(wb, filePath);
// 发送文件
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`);
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
// 发送完成后删除临时文件
fileStream.on('close', () => {
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
});
fileStream.on('error', (err) => {
console.error('文件流错误:', err);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
});
} catch (error) {
console.error('导出租机柜数据失败:', error);
res.status(500).json({
success: false,
message: '导出失败',
error: error.message
});
}
});
module.exports = router;