feat: 优化3D可视化性能并更新文档

- 实现3D可视化性能优化(降低DPR、阴影贴图、简化光源)
- 实现LOD多级细节系统,根据相机距离自动切换
- 添加设备弹出动画开关,默认关闭
- 修复设备缩放时位置偏移问题
- 更新README添加项目截图占位符
- 精简DEPLOYMENT.md文档,添加Gitee仓库支持
- 更新CHANGELOG.md记录v1.1.0版本
This commit is contained in:
zhang1106
2026-01-26 17:21:20 +08:00
parent 6e57e0b74c
commit a0ad9ad604
44 changed files with 4212 additions and 5448 deletions
+32
View File
@@ -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
}
];
+1 -1
View File
@@ -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,
+46
View File
@@ -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;
+20 -8
View File
@@ -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);
}
}
+3 -1
View File
@@ -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
View File
@@ -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 });
}
});
+46
View File
@@ -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();
+2
View File
@@ -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'));