feat(floorplan): 新增机房平面图功能模块
refactor(models): 为Room和Rack模型添加布局相关字段 feat(routes): 实现机房平面图相关API接口 feat(components): 添加平面图编辑器、画布渲染及交互组件 feat(hooks): 实现平面图数据管理和状态管理 feat(pages): 新增机房平面图页面入口 style(components): 优化平面图组件样式和交互体验
This commit is contained in:
+169
-3
@@ -1,7 +1,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const Room = require('../models/Room');
|
||||
const Rack = require('../models/Rack');
|
||||
const { Room, Rack, Device } = require('../models');
|
||||
const { sequelize } = require('../db');
|
||||
const { validateBody } = require('../middleware/validation');
|
||||
const { createRoomSchema, updateRoomSchema } = require('../validation/roomSchema');
|
||||
|
||||
@@ -72,7 +72,6 @@ router.put('/:roomId', validateBody(updateRoomSchema), async (req, res) => {
|
||||
// 删除机房
|
||||
router.delete('/:roomId', async (req, res) => {
|
||||
try {
|
||||
// 检查是否有机柜关联
|
||||
const racks = await Rack.findAll({ where: { roomId: req.params.roomId } });
|
||||
if (racks.length > 0) {
|
||||
return res.status(400).json({ error: '该机房下有机柜,无法删除' });
|
||||
@@ -91,4 +90,171 @@ router.delete('/:roomId', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 获取机房平面图布局数据
|
||||
router.get('/:roomId/layout', async (req, res) => {
|
||||
try {
|
||||
const room = await Room.findByPk(req.params.roomId);
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: '机房不存在' });
|
||||
}
|
||||
|
||||
const racks = await Rack.findAll({
|
||||
where: { roomId: req.params.roomId },
|
||||
include: [{
|
||||
model: Device,
|
||||
as: 'Devices',
|
||||
attributes: ['deviceId', 'name', 'type', 'status', 'position', 'height', 'powerConsumption', 'ipAddress', 'model'],
|
||||
}],
|
||||
});
|
||||
|
||||
const rackData = racks.map(rack => {
|
||||
const rackJson = rack.toJSON();
|
||||
const devices = rackJson.Devices || [];
|
||||
const usedU = devices.reduce((sum, d) => sum + (d.height || 1), 0);
|
||||
const totalPower = devices.reduce((sum, d) => sum + (d.powerConsumption || 0), 0);
|
||||
return {
|
||||
rackId: rackJson.rackId,
|
||||
name: rackJson.name,
|
||||
rowPos: rackJson.rowPos,
|
||||
colPos: rackJson.colPos,
|
||||
facing: rackJson.facing,
|
||||
status: rackJson.status,
|
||||
height: rackJson.height,
|
||||
maxPower: rackJson.maxPower,
|
||||
currentPower: rackJson.currentPower,
|
||||
deviceCount: devices.length,
|
||||
usedU,
|
||||
totalU: rackJson.height,
|
||||
utilization: rackJson.height > 0 ? usedU / rackJson.height : 0,
|
||||
totalPower,
|
||||
Devices: devices,
|
||||
};
|
||||
});
|
||||
|
||||
const activeRacks = rackData.filter(r => r.status === 'active').length;
|
||||
const maintenanceRacks = rackData.filter(r => r.status === 'maintenance').length;
|
||||
const inactiveRacks = rackData.filter(r => r.status === 'inactive').length;
|
||||
const totalMaxPower = rackData.reduce((sum, r) => sum + (r.maxPower || 0), 0);
|
||||
const totalCurrentPower = rackData.reduce((sum, r) => sum + (r.currentPower || 0), 0);
|
||||
const avgUtilization = rackData.length > 0
|
||||
? rackData.reduce((sum, r) => sum + r.utilization, 0) / rackData.length
|
||||
: 0;
|
||||
|
||||
res.json({
|
||||
room: {
|
||||
roomId: room.roomId,
|
||||
name: room.name,
|
||||
location: room.location,
|
||||
area: room.area,
|
||||
capacity: room.capacity,
|
||||
gridRows: room.gridRows,
|
||||
gridCols: room.gridCols,
|
||||
layoutConfig: room.layoutConfig,
|
||||
},
|
||||
racks: rackData,
|
||||
stats: {
|
||||
totalRacks: rackData.length,
|
||||
activeRacks,
|
||||
maintenanceRacks,
|
||||
inactiveRacks,
|
||||
avgUtilization: Math.round(avgUtilization * 100) / 100,
|
||||
totalCurrentPower,
|
||||
totalMaxPower,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 更新机房布局参数
|
||||
router.put('/:roomId/layout', async (req, res) => {
|
||||
try {
|
||||
const { gridRows, gridCols, layoutConfig } = req.body;
|
||||
const room = await Room.findByPk(req.params.roomId);
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: '机房不存在' });
|
||||
}
|
||||
|
||||
const updateData = {};
|
||||
if (gridRows !== undefined) updateData.gridRows = gridRows;
|
||||
if (gridCols !== undefined) updateData.gridCols = gridCols;
|
||||
if (layoutConfig !== undefined) updateData.layoutConfig = layoutConfig;
|
||||
|
||||
await Room.update(updateData, { where: { roomId: req.params.roomId } });
|
||||
const updatedRoom = await Room.findByPk(req.params.roomId);
|
||||
res.json(updatedRoom);
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 批量更新机房内机柜位置
|
||||
router.put('/:roomId/racks-positions', async (req, res) => {
|
||||
try {
|
||||
const { positions } = req.body;
|
||||
if (!Array.isArray(positions)) {
|
||||
return res.status(400).json({ error: 'positions 必须是数组' });
|
||||
}
|
||||
|
||||
const results = [];
|
||||
for (const pos of positions) {
|
||||
const { rackId, rowPos, colPos, facing } = pos;
|
||||
if (!rackId) continue;
|
||||
|
||||
const rack = await Rack.findByPk(rackId);
|
||||
if (!rack || rack.roomId !== req.params.roomId) continue;
|
||||
|
||||
await Rack.update(
|
||||
{ rowPos, colPos, facing },
|
||||
{ where: { rackId } }
|
||||
);
|
||||
results.push(rackId);
|
||||
}
|
||||
|
||||
res.json({ success: true, updated: results.length });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化机房布局(自动分配所有机柜位置)
|
||||
router.post('/:roomId/init-layout', async (req, res) => {
|
||||
try {
|
||||
const { gridRows, gridCols } = req.body;
|
||||
const rows = gridRows || 10;
|
||||
const cols = gridCols || 10;
|
||||
|
||||
const racks = await Rack.findAll({
|
||||
where: { roomId: req.params.roomId },
|
||||
order: [['name', 'ASC']],
|
||||
});
|
||||
|
||||
const positions = [];
|
||||
let index = 0;
|
||||
for (let row = 0; row < rows && index < racks.length; row++) {
|
||||
for (let col = 0; col < cols && index < racks.length; col++) {
|
||||
positions.push({
|
||||
rackId: racks[index].rackId,
|
||||
rowPos: row,
|
||||
colPos: col,
|
||||
facing: 'front',
|
||||
});
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const pos of positions) {
|
||||
await Rack.update(
|
||||
{ rowPos: pos.rowPos, colPos: pos.colPos, facing: pos.facing },
|
||||
{ where: { rackId: pos.rackId } }
|
||||
);
|
||||
}
|
||||
|
||||
res.json({ success: true, assigned: positions.length });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user