feat: 优化3D可视化性能并更新文档
- 实现3D可视化性能优化(降低DPR、阴影贴图、简化光源) - 实现LOD多级细节系统,根据相机距离自动切换 - 添加设备弹出动画开关,默认关闭 - 修复设备缩放时位置偏移问题 - 更新README添加项目截图占位符 - 精简DEPLOYMENT.md文档,添加Gitee仓库支持 - 更新CHANGELOG.md记录v1.1.0版本
This commit is contained in:
@@ -127,6 +127,38 @@ const defaultDeviceFields = [
|
||||
required: false,
|
||||
order: 14,
|
||||
visible: true
|
||||
},
|
||||
{
|
||||
fieldName: 'owner',
|
||||
displayName: '责任人',
|
||||
fieldType: 'string',
|
||||
required: false,
|
||||
order: 15,
|
||||
visible: true
|
||||
},
|
||||
{
|
||||
fieldName: 'department',
|
||||
displayName: '所属部门',
|
||||
fieldType: 'string',
|
||||
required: false,
|
||||
order: 16,
|
||||
visible: true
|
||||
},
|
||||
{
|
||||
fieldName: 'assetId',
|
||||
displayName: '资产编号',
|
||||
fieldType: 'string',
|
||||
required: false,
|
||||
order: 17,
|
||||
visible: true
|
||||
},
|
||||
{
|
||||
fieldName: 'brand',
|
||||
displayName: '品牌',
|
||||
fieldType: 'string',
|
||||
required: false,
|
||||
order: 18,
|
||||
visible: true
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const Rack = sequelize.define('Rack', {
|
||||
height: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 42 // 标准机柜高度(U数)
|
||||
defaultValue: 45 // 标准机柜高度(U数)
|
||||
},
|
||||
maxPower: {
|
||||
type: DataTypes.FLOAT,
|
||||
|
||||
@@ -90,6 +90,52 @@ router.get('/device/:deviceId', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 获取指定机柜内所有设备的接线
|
||||
router.get('/rack/:rackId', async (req, res) => {
|
||||
try {
|
||||
const { rackId } = req.params;
|
||||
|
||||
// 1. 找出该机柜下的所有设备ID
|
||||
const devices = await Device.findAll({
|
||||
where: { rackId: rackId },
|
||||
attributes: ['deviceId']
|
||||
});
|
||||
|
||||
const deviceIds = devices.map(d => d.deviceId);
|
||||
|
||||
if (deviceIds.length === 0) {
|
||||
return res.json([]);
|
||||
}
|
||||
|
||||
// 2. 查找这些设备参与的所有接线
|
||||
const cables = await Cable.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ sourceDeviceId: { [Op.in]: deviceIds } },
|
||||
{ targetDeviceId: { [Op.in]: deviceIds } }
|
||||
]
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Device,
|
||||
as: 'sourceDevice',
|
||||
attributes: ['deviceId', 'name', 'type', 'rackId']
|
||||
},
|
||||
{
|
||||
model: Device,
|
||||
as: 'targetDevice',
|
||||
attributes: ['deviceId', 'name', 'type', 'rackId']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
res.json(cables);
|
||||
} catch (error) {
|
||||
console.error('获取机柜接线失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const { cableId, sourceDeviceId, sourcePort, targetDeviceId, targetPort, cableType, cableLength, status, description } = req.body;
|
||||
|
||||
@@ -83,15 +83,27 @@ router.post('/config', async (req, res) => {
|
||||
// 批量更新字段配置
|
||||
const updatedFields = [];
|
||||
for (const config of fieldConfigs) {
|
||||
// 使用fieldName作为更新条件,因为这是唯一的
|
||||
const [updated] = await DeviceField.update(
|
||||
{ visible: config.visible },
|
||||
{ where: { fieldName: config.fieldName } }
|
||||
);
|
||||
// 检查字段是否存在
|
||||
const existingField = await DeviceField.findOne({ where: { fieldName: config.fieldName } });
|
||||
|
||||
if (updated) {
|
||||
const updatedField = await DeviceField.findOne({ where: { fieldName: config.fieldName } });
|
||||
updatedFields.push(updatedField);
|
||||
if (existingField) {
|
||||
// 更新现有字段
|
||||
await existingField.update({
|
||||
visible: config.visible,
|
||||
displayName: config.displayName // 同时更新显示名称,以防变化
|
||||
});
|
||||
updatedFields.push(existingField);
|
||||
} else {
|
||||
// 创建新字段
|
||||
const newField = await DeviceField.create({
|
||||
fieldName: config.fieldName,
|
||||
visible: config.visible,
|
||||
displayName: config.displayName,
|
||||
fieldType: config.fieldType || 'text',
|
||||
required: false, // 默认为非必填
|
||||
order: 0 // 默认顺序
|
||||
});
|
||||
updatedFields.push(newField);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ router.get('/device/:deviceId', async (req, res) => {
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const { portId, deviceId, portName, portType, portSpeed, status, vlanId, description } = req.body;
|
||||
const { portId, deviceId, nicId, portName, portType, portSpeed, status, vlanId, description } = req.body;
|
||||
|
||||
if (!deviceId || !portName) {
|
||||
return res.status(400).json({ error: '缺少必填字段' });
|
||||
@@ -97,6 +97,7 @@ router.post('/', async (req, res) => {
|
||||
const port = await DevicePort.create({
|
||||
portId: autoPortId,
|
||||
deviceId,
|
||||
nicId: nicId || null,
|
||||
portName,
|
||||
portType: portType || 'RJ45',
|
||||
portSpeed: portSpeed || '1G',
|
||||
@@ -156,6 +157,7 @@ router.post('/batch', async (req, res) => {
|
||||
await DevicePort.create({
|
||||
portId: portData.portId,
|
||||
deviceId: portData.deviceId,
|
||||
nicId: portData.nicId || null,
|
||||
portName: portData.portName,
|
||||
portType: portData.portType || 'RJ45',
|
||||
portSpeed: portData.portSpeed || '1G',
|
||||
|
||||
+98
-36
@@ -1,6 +1,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Op } = require('sequelize');
|
||||
const { sequelize } = require('../db'); // Import sequelize for transactions
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const csv = require('csv-parser');
|
||||
@@ -11,6 +12,8 @@ const Rack = require('../models/Rack');
|
||||
const Room = require('../models/Room');
|
||||
const DeviceField = require('../models/DeviceField');
|
||||
const Ticket = require('../models/Ticket');
|
||||
const DevicePort = require('../models/DevicePort'); // Import DevicePort
|
||||
const Cable = require('../models/Cable'); // Import Cable
|
||||
|
||||
// 获取所有设备(支持搜索和筛选)
|
||||
router.get('/', async (req, res) => {
|
||||
@@ -841,91 +844,150 @@ router.put('/batch-offline', async (req, res) => {
|
||||
|
||||
// 批量删除设备
|
||||
router.delete('/batch-delete', async (req, res) => {
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
const { deviceIds } = req.body;
|
||||
|
||||
if (!deviceIds || !Array.isArray(deviceIds) || deviceIds.length === 0) {
|
||||
await t.rollback();
|
||||
return res.status(400).json({ error: '请提供有效的设备ID列表' });
|
||||
}
|
||||
|
||||
const devices = await Device.findAll({
|
||||
where: { deviceId: { [Op.in]: deviceIds } }
|
||||
where: { deviceId: { [Op.in]: deviceIds } },
|
||||
transaction: t
|
||||
});
|
||||
|
||||
// 1. 删除相关接线 (Delete associated Cables)
|
||||
await Cable.destroy({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ sourceDeviceId: { [Op.in]: deviceIds } },
|
||||
{ targetDeviceId: { [Op.in]: deviceIds } }
|
||||
]
|
||||
},
|
||||
transaction: t
|
||||
});
|
||||
|
||||
// 2. 删除相关端口 (Delete associated DevicePorts)
|
||||
await DevicePort.destroy({
|
||||
where: { deviceId: { [Op.in]: deviceIds } },
|
||||
transaction: t
|
||||
});
|
||||
|
||||
// 3. 解除工单关联
|
||||
await Ticket.update(
|
||||
{ deviceId: null },
|
||||
{ where: { deviceId: { [Op.in]: deviceIds } } }
|
||||
{ where: { deviceId: { [Op.in]: deviceIds } }, transaction: t }
|
||||
);
|
||||
|
||||
// 4. 删除设备
|
||||
const deletedCount = await Device.destroy({
|
||||
where: { deviceId: { [Op.in]: deviceIds } }
|
||||
where: { deviceId: { [Op.in]: deviceIds } },
|
||||
transaction: t
|
||||
});
|
||||
|
||||
// 更新机柜功率
|
||||
for (const device of devices) {
|
||||
const rack = await Rack.findByPk(device.rackId);
|
||||
if (rack) {
|
||||
await rack.update({
|
||||
currentPower: Math.max(0, rack.currentPower - device.powerConsumption)
|
||||
});
|
||||
if (device.rackId) {
|
||||
const rack = await Rack.findByPk(device.rackId, { transaction: t });
|
||||
if (rack) {
|
||||
await rack.update({
|
||||
currentPower: Math.max(0, rack.currentPower - device.powerConsumption)
|
||||
}, { transaction: t });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await t.commit();
|
||||
|
||||
res.json({
|
||||
message: `批量删除成功,已删除 ${deletedCount} 个设备`,
|
||||
deletedCount
|
||||
});
|
||||
} catch (error) {
|
||||
await t.rollback();
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 删除设备
|
||||
router.delete('/:deviceId', async (req, res) => {
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
const { deviceId } = req.params;
|
||||
|
||||
// 获取设备信息以更新功率
|
||||
const device = await Device.findByPk(req.params.deviceId);
|
||||
const device = await Device.findByPk(deviceId, { transaction: t });
|
||||
if (!device) {
|
||||
await t.rollback();
|
||||
return res.status(404).json({ error: '设备不存在' });
|
||||
}
|
||||
|
||||
const deleted = await Device.destroy({
|
||||
where: { deviceId: req.params.deviceId }
|
||||
// 1. 删除相关接线 (Delete associated Cables)
|
||||
// 必须在删除设备之前删除,否则可能触发外键约束错误
|
||||
const deletedCables = await Cable.destroy({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ sourceDeviceId: deviceId },
|
||||
{ targetDeviceId: deviceId }
|
||||
]
|
||||
},
|
||||
transaction: t
|
||||
});
|
||||
|
||||
if (deleted) {
|
||||
const Cable = require('../models/Cable');
|
||||
|
||||
const deletedCables = await Cable.destroy({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ sourceDeviceId: req.params.deviceId },
|
||||
{ targetDeviceId: req.params.deviceId }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
if (deletedCables > 0) {
|
||||
console.log(`已删除 ${deletedCables} 条相关接线`);
|
||||
}
|
||||
|
||||
if (device.rackId) {
|
||||
// 2. 删除相关端口 (Delete associated DevicePorts)
|
||||
// 必须在删除设备之前删除,否则触发外键约束错误
|
||||
const deletedPorts = await DevicePort.destroy({
|
||||
where: { deviceId: deviceId },
|
||||
transaction: t
|
||||
});
|
||||
|
||||
// 3. 解除工单关联 (Unlink Tickets)
|
||||
await Ticket.update(
|
||||
{ deviceId: null },
|
||||
{ where: { deviceId: deviceId }, transaction: t }
|
||||
);
|
||||
|
||||
// 4. 删除设备 (Delete Device)
|
||||
await Device.destroy({
|
||||
where: { deviceId: deviceId },
|
||||
transaction: t
|
||||
});
|
||||
|
||||
// 提交事务
|
||||
await t.commit();
|
||||
|
||||
if (deletedCables > 0) {
|
||||
console.log(`已删除 ${deletedCables} 条相关接线`);
|
||||
}
|
||||
|
||||
// 更新机柜功率 (Update Rack power)
|
||||
// 注意:设备已删除,不需要再减去功率?或者需要?
|
||||
// 原逻辑是:rack.currentPower - device.powerConsumption
|
||||
// 既然设备已经物理删除了,机柜的当前功率确实应该减少。
|
||||
if (device.rackId) {
|
||||
try {
|
||||
const rack = await Rack.findByPk(device.rackId);
|
||||
if (rack) {
|
||||
await rack.update({
|
||||
currentPower: Math.max(0, rack.currentPower - device.powerConsumption)
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('更新机柜功率失败:', err);
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
message: '删除成功',
|
||||
deviceId: req.params.deviceId,
|
||||
deletedCablesCount: deletedCables
|
||||
});
|
||||
} else {
|
||||
res.status(404).json({ error: '设备不存在' });
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
message: '删除成功',
|
||||
deviceId: deviceId,
|
||||
deletedCablesCount: deletedCables,
|
||||
deletedPortsCount: deletedPorts
|
||||
});
|
||||
} catch (error) {
|
||||
await t.rollback();
|
||||
console.error('删除设备失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
const { sequelize } = require('../db');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
|
||||
async function ensureSchema() {
|
||||
try {
|
||||
const dbType = sequelize.getDialect();
|
||||
console.log(`Checking schema for ${dbType}...`);
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
// Check if nicId column exists in device_ports
|
||||
const [columns] = await sequelize.query("PRAGMA table_info(device_ports)");
|
||||
const hasNicId = columns.some(col => col.name === 'nicId');
|
||||
|
||||
if (!hasNicId) {
|
||||
console.log('Adding nicId column to device_ports...');
|
||||
await sequelize.query('ALTER TABLE device_ports ADD COLUMN nicId VARCHAR(255) NULL REFERENCES network_cards(nicId)');
|
||||
console.log('Added nicId column.');
|
||||
} else {
|
||||
console.log('nicId column already exists in device_ports.');
|
||||
}
|
||||
} else if (dbType === 'mysql') {
|
||||
const [columns] = await sequelize.query(
|
||||
"SHOW COLUMNS FROM `device_ports` LIKE 'nicId'",
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
if (columns.length === 0) {
|
||||
console.log('Adding nicId column to device_ports...');
|
||||
await sequelize.query(
|
||||
'ALTER TABLE `device_ports` ADD COLUMN `nicId` VARCHAR(255) NULL AFTER `deviceId`'
|
||||
);
|
||||
console.log('Added nicId column.');
|
||||
} else {
|
||||
console.log('nicId column already exists.');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Schema check complete.');
|
||||
} catch (error) {
|
||||
console.error('Schema check failed:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
ensureSchema();
|
||||
@@ -64,6 +64,7 @@ const ticketFieldRoutes = require('./routes/ticketFields');
|
||||
const systemSettingsRoutes = require('./routes/systemSettings');
|
||||
const cableRoutes = require('./routes/cables');
|
||||
const devicePortRoutes = require('./routes/devicePorts');
|
||||
const networkCardRoutes = require('./routes/networkCards');
|
||||
|
||||
// 使用路由
|
||||
app.use('/api/devices', deviceRoutes);
|
||||
@@ -83,6 +84,7 @@ app.use('/api/ticket-fields', ticketFieldRoutes);
|
||||
app.use('/api/system-settings', systemSettingsRoutes);
|
||||
app.use('/api/cables', cableRoutes);
|
||||
app.use('/api/device-ports', devicePortRoutes);
|
||||
app.use('/api/network-cards', networkCardRoutes);
|
||||
|
||||
// 静态文件服务
|
||||
app.use('/uploads', express.static('uploads'));
|
||||
|
||||
Reference in New Issue
Block a user