From fd3c6f5225d96279e5656a445b8b3c0edaec846b Mon Sep 17 00:00:00 2001
From: zhang1106 <849185023@qq.com>
Date: Tue, 23 Dec 2025 16:11:07 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E6=9C=BA=E6=9F=9C?=
=?UTF-8?q?=E5=8F=AF=E8=A7=86=E5=8C=96=E9=A1=B5=E9=9D=A2=EF=BC=8C=E9=85=8D?=
=?UTF-8?q?=E7=BD=AE=E5=8F=AF=E8=A7=86=E5=8C=96=E7=95=8C=E9=9D=A2=E4=B8=AD?=
=?UTF-8?q?=E8=AE=BE=E5=A4=87=E8=AF=A6=E6=83=85=E7=9A=84=E5=AD=97=E6=AE=B5?=
=?UTF-8?q?=E5=8F=AF=E9=80=89=E6=8B=A9=E5=86=85=E5=AE=B9=EF=BC=8CU?=
=?UTF-8?q?=E6=95=B0=E5=92=8C=E6=9C=BA=E6=9F=9C=E4=BD=8D=E7=BD=AE=E5=8C=B9?=
=?UTF-8?q?=E9=85=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/pages/RackVisualization.jsx | 1077 ++++++++++++++--------
1 file changed, 670 insertions(+), 407 deletions(-)
diff --git a/frontend/src/pages/RackVisualization.jsx b/frontend/src/pages/RackVisualization.jsx
index 8cd9946..ed1ee0b 100644
--- a/frontend/src/pages/RackVisualization.jsx
+++ b/frontend/src/pages/RackVisualization.jsx
@@ -1,9 +1,9 @@
import React, { useState, useEffect } from 'react';
-import { Card, Select, Button, Space, message, Tooltip } from 'antd';
+import { Card, Select, Button, Space, message, Tooltip, Modal, Form, Switch, Checkbox } from 'antd';
import {
ReloadOutlined,
ZoomInOutlined,
- ZoomOutOutlined,
+ ZoomOutOutlined,
RotateRightOutlined,
CloudServerOutlined,
SwitcherOutlined,
@@ -78,8 +78,9 @@ style.textContent = `
/* 悬停提亮效果 */
.device-hover {
- background: linear-gradient(145deg, #e8f5e8, #d4edda) !important;
- box-shadow: 0 6px 16px rgba(0,0,0,0.3), 0 0 8px rgba(129, 199, 132, 0.4) !important;
+ background: linear-gradient(145deg, #1e293b, #0f172a) !important;
+ box-shadow: 0 6px 16px rgba(56, 189, 248, 0.3), 0 0 12px rgba(56, 189, 248, 0.2) !important;
+ border-color: #38bdf8 !important;
}
/* Tooltip样式 */
@@ -97,28 +98,21 @@ style.textContent = `
/* 设备数量badge */
.device-count-badge {
- background: linear-gradient(145deg, #a5d6a7, #81c784);
- color: #2e7d32;
- padding: 4px 10px;
- border-radius: 12px;
- font-size: 12px;
- font-weight: 500;
- box-shadow: 0 2px 6px rgba(129, 199, 132, 0.3);
- border: 1px solid rgba(129, 199, 132, 0.5);
- backdrop-filter: blur(4px);
- transition: all 0.2s ease;
- white-space: 'nowrap';
+ background: linear-gradient(135deg, rgba(56, 189, 248, 0.2), rgba(14, 165, 233, 0.2));
+ color: #38bdf8;
+ padding: 6px 14px;
+ border-radius: 20px;
+ font-size: 13px;
+ font-weight: 600;
+ box-shadow: 0 4px 15px rgba(56, 189, 248, 0.2);
+ border: 1px solid rgba(56, 189, 248, 0.3);
+ backdrop-filter: blur(10px);
+ transition: all 0.3s ease;
+ white-space: nowrap;
+ font-family: 'JetBrains Mono', 'Roboto Mono', monospace;
}
-
- .device-count-badge:hover {
- transform: translateY(-1px);
- box-shadow: 0 3px 10px rgba(129, 199, 132, 0.4);
- }
-
-
`;
document.head.appendChild(style);
-
// 错误边界组件
class ErrorBoundary extends React.Component {
constructor(props) {
@@ -196,27 +190,55 @@ function RackVisualization() {
// 设备详情tooltip字段配置
const [tooltipFields, setTooltipFields] = useState({});
+ // 默认设备字段配置
+ const defaultTooltipFields = {
+ name: { label: '设备名称', enabled: true, field: 'name', fieldType: 'string' },
+ deviceId: { label: '设备ID', enabled: true, field: 'deviceId', fieldType: 'string' },
+ type: { label: '设备类型', enabled: true, field: 'type', fieldType: 'string' },
+ brand: { label: '品牌', enabled: true, field: 'brand', fieldType: 'string' },
+ model: { label: '型号', enabled: true, field: 'model', fieldType: 'string' },
+ status: { label: '状态', enabled: true, field: 'status', fieldType: 'string' },
+ position: { label: '位置', enabled: true, field: 'position', fieldType: 'number' },
+ height: { label: '高度', enabled: true, field: 'height', fieldType: 'number' },
+ ipAddress: { label: 'IP地址', enabled: true, field: 'ipAddress', fieldType: 'string' },
+ power: { label: '功率', enabled: true, field: 'power', fieldType: 'number' }
+ };
+
// 获取设备字段配置
const fetchTooltipDeviceFields = async () => {
try {
setLoadingTooltipFields(true);
+ console.log('开始获取设备字段配置...');
const response = await axios.get('/api/deviceFields');
- const sortedFields = response.data.sort((a, b) => a.order - b.order);
- setTooltipDeviceFields(sortedFields);
+ console.log('API响应:', response.data);
+ const fieldsData = response.data;
+
+ if (Array.isArray(fieldsData) && fieldsData.length > 0) {
+ console.log('使用API返回的字段配置');
+ const sortedFields = fieldsData.sort((a, b) => a.order - b.order);
+ setTooltipDeviceFields(sortedFields);
- // 根据字段配置初始化tooltipFields
- const initialFields = {};
- sortedFields.forEach(field => {
- initialFields[field.fieldName] = {
- label: field.displayName,
- enabled: field.visible,
- field: field.fieldName,
- fieldType: field.fieldType
- };
- });
- setTooltipFields(initialFields);
+ // 根据字段配置初始化tooltipFields
+ const initialFields = {};
+ sortedFields.forEach(field => {
+ console.log('字段:', field.fieldName, field.displayName, field.visible);
+ initialFields[field.fieldName] = {
+ label: field.displayName,
+ enabled: field.visible,
+ field: field.fieldName,
+ fieldType: field.fieldType
+ };
+ });
+ setTooltipFields(initialFields);
+ } else {
+ console.log('使用默认字段配置');
+ // 使用默认字段配置
+ setTooltipFields(defaultTooltipFields);
+ }
} catch (error) {
- console.error('获取设备字段配置失败:', error);
+ console.error('获取设备字段配置失败,使用默认配置:', error);
+ // 使用默认字段配置
+ setTooltipFields(defaultTooltipFields);
} finally {
setLoadingTooltipFields(false);
}
@@ -226,14 +248,20 @@ function RackVisualization() {
const saveTooltipConfig = async () => {
try {
setSavingTooltipConfig(true);
- const fieldConfigs = Object.values(tooltipFields).map(field => ({
+
+ const fieldConfigs = Object.entries(tooltipFields).map(([key, field]) => ({
fieldName: field.field,
- visible: field.enabled
+ visible: field.enabled,
+ displayName: field.label,
+ fieldType: field.fieldType
}));
await axios.post('/api/deviceFields/config', fieldConfigs);
message.success('字段配置已保存');
setShowTooltipConfig(false);
+
+ // 重新获取配置以确保数据一致
+ await fetchTooltipDeviceFields();
} catch (error) {
console.error('保存字段配置失败:', error);
message.error('保存字段配置失败');
@@ -325,9 +353,24 @@ function RackVisualization() {
console.log('=== 处理后的设备数据 ===');
console.log('设备数组长度:', devicesData.length);
console.log('设备数据:', devicesData);
+
+ // 展开customFields中的字段到设备对象的直接属性
+ const processedDevices = devicesData.map(device => {
+ const deviceWithFields = { ...device };
+
+ if (device.customFields && typeof device.customFields === 'object') {
+ Object.entries(device.customFields).forEach(([fieldName, value]) => {
+ deviceWithFields[fieldName] = value;
+ });
+ }
+
+ return deviceWithFields;
+ });
+
+ console.log('展开customFields后的设备数据:', processedDevices);
// 如果没有数据,显示警告
- if (devicesData.length === 0) {
+ if (processedDevices.length === 0) {
console.warn('=== 没有找到设备数据 ===');
console.warn('可能的原因:');
console.warn('1. 数据库中该机柜确实没有设备');
@@ -340,8 +383,8 @@ function RackVisualization() {
// 显示原始数据结构以供调试
console.log('=== 数据结构分析 ===');
- if (devicesData.length > 0) {
- const firstDevice = devicesData[0];
+ if (processedDevices.length > 0) {
+ const firstDevice = processedDevices[0];
console.log('第一个设备完整数据:', JSON.stringify(firstDevice, null, 2));
console.log('第一个设备的字段:', Object.keys(firstDevice));
console.log('第一个设备的字段类型:');
@@ -351,7 +394,7 @@ function RackVisualization() {
}
// 过滤有效的设备数据 - 放宽验证条件
- const validDevices = devicesData.filter(device => {
+ const validDevices = processedDevices.filter(device => {
if (!device) {
console.log('跳过空设备');
return false;
@@ -427,6 +470,14 @@ function RackVisualization() {
fetchTooltipDeviceFields();
}, []);
+ // 打开字段配置模态框时获取数据
+ const handleOpenTooltipConfig = () => {
+ if (Object.keys(tooltipFields).length === 0) {
+ fetchTooltipDeviceFields();
+ }
+ setShowTooltipConfig(true);
+ };
+
// 根据设备类型获取图标
const getDeviceIcon = (deviceType) => {
try {
@@ -478,6 +529,69 @@ function RackVisualization() {
return statusColorMap[status] || '#3b82f6';
};
+ // 获取设备类型对应的颜色主题
+ const getDeviceTypeTheme = (type) => {
+ const themeMap = {
+ 'server': {
+ borderColor: '#38bdf8',
+ accentColor: '#0ea5e9',
+ glowColor: 'rgba(56, 189, 248, 0.3)',
+ iconColor: '#38bdf8',
+ label: '服务器'
+ },
+ 'switch': {
+ borderColor: '#22c55e',
+ accentColor: '#16a34a',
+ glowColor: 'rgba(34, 197, 94, 0.3)',
+ iconColor: '#22c55e',
+ label: '交换机'
+ },
+ 'router': {
+ borderColor: '#f59e0b',
+ accentColor: '#d97706',
+ glowColor: 'rgba(245, 158, 11, 0.3)',
+ iconColor: '#f59e0b',
+ label: '路由器'
+ },
+ 'storage': {
+ borderColor: '#8b5cf6',
+ accentColor: '#7c3aed',
+ glowColor: 'rgba(139, 92, 246, 0.3)',
+ iconColor: '#8b5cf6',
+ label: '存储'
+ },
+ 'firewall': {
+ borderColor: '#ef4444',
+ accentColor: '#dc2626',
+ glowColor: 'rgba(239, 68, 68, 0.3)',
+ iconColor: '#ef4444',
+ label: '防火墙'
+ },
+ 'ups': {
+ borderColor: '#14b8a6',
+ accentColor: '#0d9488',
+ glowColor: 'rgba(20, 184, 166, 0.3)',
+ iconColor: '#14b8a6',
+ label: 'UPS'
+ },
+ 'pdus': {
+ borderColor: '#64748b',
+ accentColor: '#475569',
+ glowColor: 'rgba(100, 116, 139, 0.3)',
+ iconColor: '#64748b',
+ label: 'PDU'
+ },
+ 'other': {
+ borderColor: '#94a3b8',
+ accentColor: '#64748b',
+ glowColor: 'rgba(148, 163, 184, 0.3)',
+ iconColor: '#94a3b8',
+ label: '其他'
+ }
+ };
+ return themeMap[type?.toLowerCase()] || themeMap['other'];
+ };
+
// 生成模拟监控数据
const generateMonitoringData = (device) => {
const baseTemp = device.type?.includes('服务器') ? 65 :
@@ -596,35 +710,32 @@ function RackVisualization() {
// 生成左侧U数标记(从U1开始显示)
const generateLeftUMarks = (height) => {
const marks = [];
- const uHeight = 25; // 调整为更小的U高度以适应屏幕显示
+ const uHeight = 25;
for (let u = 1; u <= height; u++) {
- // 使用与设备位置计算一致的逻辑
- // U1在最底部,计算U位标记的顶部位置(从机柜顶部算起)
const topPosition = (height - u) * uHeight;
marks.push(
- {/* U位序号 */}
{u}
@@ -639,35 +750,32 @@ function RackVisualization() {
// 生成右侧U数标记(从U1开始显示)
const generateRightUMarks = (height) => {
const marks = [];
- const uHeight = 25; // 调整为更小的U高度以适应屏幕显示
+ const uHeight = 25;
for (let u = 1; u <= height; u++) {
- // 使用与设备位置计算一致的逻辑
- // U1在最底部,计算U位标记的顶部位置(从机柜顶部算起)
const topPosition = (height - u) * uHeight;
marks.push(
- {/* U位序号 */}
{u}
@@ -832,7 +940,7 @@ function RackVisualization() {
}
- onClick={() => setShowTooltipConfig(true)}
+ onClick={handleOpenTooltipConfig}
type={showTooltipConfig ? 'primary' : 'default'}
>
字段配置
@@ -1009,18 +1117,39 @@ function RackVisualization() {
position: 'relative',
width: '600px',
height: `${selectedRack.height * 25}px`,
- background: 'linear-gradient(145deg, #e0f2fe, #b3e5fc)',
- border: '2px solid #81c784',
- borderRadius: '8px',
- boxShadow: '0 8px 32px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.2)',
- overflow: 'visible'
+ background: 'linear-gradient(145deg, #1e293b 0%, #0f172a 50%, #1e293b 100%)',
+ border: '2px solid #38bdf8',
+ borderRadius: '12px',
+ boxShadow: `
+ 0 0 40px rgba(56, 189, 248, 0.2),
+ 0 0 80px rgba(56, 189, 248, 0.1),
+ 0 20px 60px rgba(0, 0, 0, 0.5),
+ inset 0 1px 0 rgba(255, 255, 255, 0.05),
+ inset 0 0 60px rgba(56, 189, 248, 0.05)
+ `,
+ overflow: 'visible',
+ backdropFilter: 'blur(10px)'
}}>
- {/* 左侧U数标记 */}
+ {/* 扫描线效果 */}
+
+
+ {/* 玻璃光泽效果 */}
+ {/* 左侧U数标记 - 贴紧机架边缘 */}
+
- {/* 右侧U数标记 */}
+ {/* 右侧U数标记 - 贴紧机架边缘 */}
{/* 导轨安装孔 - 左侧 */}
{Array.from({ length: Math.max(1, Math.floor((selectedRack.height * 25 - 6) / 25)) }).map((_, index) => (
@@ -1062,7 +1191,7 @@ function RackVisualization() {
left: '2px',
width: '11px',
height: '20px',
- top: `${index * 25 + 2}px`,
+ top: `${(selectedRack.height - index - 1) * 25 + 2}px`,
background: 'linear-gradient(180deg, #2d3436 0%, #636e72 50%, #2d3436 100%)',
borderRadius: '1px',
boxShadow: 'inset 0 1px 2px rgba(255,255,255,0.1), 0 1px 1px rgba(0,0,0,0.3)'
@@ -1102,7 +1231,7 @@ function RackVisualization() {
right: '2px',
width: '11px',
height: '20px',
- top: `${index * 25 + 2}px`,
+ top: `${(selectedRack.height - index - 1) * 25 + 2}px`,
background: 'linear-gradient(180deg, #2d3436 0%, #636e72 50%, #2d3436 100%)',
borderRadius: '1px',
boxShadow: 'inset 0 1px 2px rgba(255,255,255,0.1), 0 1px 1px rgba(0,0,0,0.3)'
@@ -1152,6 +1281,31 @@ function RackVisualization() {
pointerEvents: 'none'
}} />
+ {/* U位网格线 */}
+
+ {Array.from({ length: Math.max(0, selectedRack.height - 1) }).map((_, index) => (
+
+ ))}
+
+
{/* 移除了内侧U数标记,因为外侧已有U数显示 */}
{/* 设备 */}
@@ -1172,16 +1326,22 @@ function RackVisualization() {
style={{
...getDeviceStyle(device, selectedRack.height),
position: 'absolute',
- left: '2px',
- right: '2px',
- width: 'calc(100% - 4px)',
- borderRadius: '2px',
+ left: '3px',
+ right: '3px',
+ width: 'calc(100% - 6px)',
+ borderRadius: '3px',
display: 'flex',
cursor: 'pointer',
transition: 'all 0.2s ease',
- boxShadow: '0 1px 3px rgba(0,0,0,0.3), 0 4px 6px rgba(0,0,0,0.2)',
- background: 'linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%)',
- border: '1px solid #cbd5e1',
+ boxShadow: `
+ 0 1px 2px rgba(0,0,0,0.3),
+ 0 2px 4px rgba(0,0,0,0.2),
+ inset 0 1px 0 rgba(255,255,255,0.1),
+ inset 0 -1px 0 rgba(0,0,0,0.1)
+ `,
+ background: 'linear-gradient(180deg, #3d4451 0%, #2d3139 50%, #252930 100%)',
+ border: '1px solid #4a5568',
+ borderTop: '1px solid #565c6b',
overflow: 'hidden',
margin: 0,
padding: 0,
@@ -1189,172 +1349,341 @@ function RackVisualization() {
pointerEvents: 'auto'
}}
onMouseEnter={(e) => {
- e.currentTarget.style.transform = 'scale(1.01)';
- e.currentTarget.classList.add('device-hover');
+ const isOneU = (device?.height || 1) === 1;
+ if (isOneU) {
+ e.currentTarget.style.height = '33px';
+ e.currentTarget.style.zIndex = '150';
+ }
+ e.currentTarget.style.transform = 'scale(1.008)';
+ e.currentTarget.style.boxShadow = `
+ 0 4px 12px rgba(56, 189, 248, 0.3),
+ 0 2px 6px rgba(0,0,0,0.3),
+ inset 0 1px 0 rgba(255,255,255,0.15)
+ `;
+ e.currentTarget.style.borderColor = '#38bdf8';
- // 计算tooltip位置(机柜右侧)
const rect = e.currentTarget.getBoundingClientRect();
- const tooltipX = rect.right + 5; // 设备右侧
- const tooltipY = rect.top + rect.height / 2; // 设备中间高度
-
- setTooltipPosition({ x: tooltipX, y: tooltipY });
-
- // 设置设备详情tooltip
- setDeviceTooltip({
- device: device
- });
+ setTooltipPosition({ x: rect.right + 5, y: rect.top + rect.height / 2 });
+ setDeviceTooltip({ device: device });
}}
onMouseLeave={(e) => {
+ const isOneU = (device?.height || 1) === 1;
+ if (isOneU) {
+ const originalHeight = (device?.height || 1) * 25;
+ e.currentTarget.style.height = `${originalHeight}px`;
+ e.currentTarget.style.zIndex = '100';
+ }
e.currentTarget.style.transform = 'scale(1)';
- e.currentTarget.classList.remove('device-hover');
-
- // 清除tooltip
+ e.currentTarget.style.boxShadow = `
+ 0 1px 2px rgba(0,0,0,0.3),
+ 0 2px 4px rgba(0,0,0,0.2),
+ inset 0 1px 0 rgba(255,255,255,0.1),
+ inset 0 -1px 0 rgba(0,0,0,0.1)
+ `;
+ e.currentTarget.style.borderColor = '#4a5568';
setDeviceTooltip(null);
}}
>
- {/* 左侧状态指示区域 */}
+
+
+ {/* 左侧状态指示区域 - 增强版 */}
- {/* 状态指示灯 */}
+ {/* 设备类型标识 */}
+ width: '100%',
+ height: '8px',
+ background: 'linear-gradient(180deg, #334155 0%, #1e293b 100%)',
+ borderRadius: '2px',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ border: '1px solid rgba(56, 189, 248, 0.2)'
+ }}>
+
+ {device.type?.toUpperCase() || 'DEV'}
+
+
+
+ {/* LED状态指示灯组 */}
- {/* 电源指示 */}
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '3px',
+ alignItems: 'center'
+ }}>
+ {/* 主状态灯 */}
+
+
+ {/* 电源指示灯 */}
+
+
+
+ {/* 设备序列号标签 */}
+ width: '100%',
+ textAlign: 'center',
+ borderTop: '1px solid rgba(148, 163, 184, 0.2)',
+ paddingTop: '2px'
+ }}>
+
+ SN:{device.serial?.slice(-4) || '0000'}
+
+
- {/* 中间设备信息区域 */}
+ {/* 中间设备信息区域 - 增强版 */}
- {/* 设备图标 */}
+ {/* 设备品牌/厂商标识 */}
- {getDeviceIcon(device.type)}
+
+ {getDeviceIcon(device.type)}
+
+
+ {device.brand || 'ENTERPRISE'}
+
- {/* 分隔符 */}
-
-
{/* 设备名称 */}
{deviceName}
- {/* 型号/类型 */}
+ {/* 型号和规格 */}
- {device.model || device.type || ''}
+
+ {device.model || device.type?.toUpperCase() || 'STD'}
+
+ {device.ip && (
+
+ {device.ip}
+
+ )}
+
+ {/* 散热/通风口装饰 - 根据设备类型显示 */}
+ {(device.type === 'server' || device.type === 'storage') && (
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+ ))}
+
+ )}
- {/* 右侧功能区域 */}
+ {/* 右侧端口/功能区域 - 增强版 */}
- {/* 端口指示 */}
+ {/* 端口指示灯阵列 */}
+ display: 'grid',
+ gridTemplateColumns: 'repeat(2, 1fr)',
+ gap: '2px'
+ }}>
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+
+ {/* 管理接口标识 */}
+ display: 'flex',
+ alignItems: 'center',
+ gap: '2px',
+ marginTop: '2px'
+ }}>
+
+
+ MGMT
+
+
+
+ {/* 设备高度U数标识 */}
+ fontSize: '5px',
+ color: '#38bdf8',
+ fontWeight: '700',
+ fontFamily: '"Roboto Mono", monospace',
+ background: 'rgba(56, 189, 248, 0.15)',
+ padding: '1px 4px',
+ borderRadius: '2px',
+ border: '1px solid rgba(56, 189, 248, 0.3)'
+ }}>
+ {device.height}U
+
);
@@ -1448,13 +1777,13 @@ function RackVisualization() {
- 设备数量: {devices.length}
+ {devices.length}
+ 设备
@@ -1488,185 +1818,84 @@ function RackVisualization() {
)}
- {/* Tooltip字段配置面板 */}
- {showTooltipConfig && (
-
-
-
-
- 设备详情字段配置
+ {/* 设备详情字段配置模态框 */}
+
setShowTooltipConfig(false)}
+ footer={null}
+ width={500}
+ >
+ {loadingTooltipFields ? (
+
+ 加载字段配置中...
+
+ ) : Object.keys(tooltipFields).length === 0 ? (
+
+ 暂无字段配置,请先在设备字段管理中添加字段
+
+ ) : (
+
+
+
+ 选择要在设备详情中显示的字段: ({Object.keys(tooltipFields).length} 个字段)
+
+
+ {Object.entries(tooltipFields).map(([key, field]) => (
+
{
+ setTooltipFields(prev => ({
+ ...prev,
+ [key]: { ...prev[key], enabled: !prev[key].enabled }
+ }));
+ }}
+ >
+
+ {field.label}
+
+ ))}
-
- {loadingTooltipFields ? (
-
- 加载字段配置中...
-
- ) : Object.keys(tooltipFields).length === 0 ? (
-
- 暂无字段配置,请先在设备字段管理中添加字段
-
- ) : (
- <>
-
-
-
-
-
-
-
- >
- )}
+ 全选/取消全选
+
+
+
+
+
-
- )}
+ )}
+
{/* 设备详情tooltip - 放在容器外部确保不被遮挡 */}
{deviceTooltip && (
@@ -1686,36 +1915,70 @@ function RackVisualization() {
设备详情
- {tooltipFields.name?.enabled && (
-
名称: {deviceTooltip.device.name || '未知设备'}
- )}
- {tooltipFields.deviceId?.enabled && (
-
设备ID: {deviceTooltip.device.deviceId || deviceTooltip.device.id || '-'}
- )}
- {tooltipFields.type?.enabled && (
-
类型: {deviceTooltip.device.type || deviceTooltip.device.device_type || '-'}
- )}
- {tooltipFields.brand?.enabled && (
-
品牌: {deviceTooltip.device.brand || '-'}
- )}
- {tooltipFields.model?.enabled && (
-
型号: {deviceTooltip.device.model || '-'}
- )}
- {tooltipFields.status?.enabled && (
-
状态: {deviceTooltip.device.status === 'running' ? '运行中' : deviceTooltip.device.status === 'warning' ? '警告' : deviceTooltip.device.status === 'error' ? '故障' : deviceTooltip.device.status || '未知'}
- )}
- {tooltipFields.position?.enabled && (
-
位置: U{deviceTooltip.device.position} ({deviceTooltip.device.height}U)
- )}
- {tooltipFields.height?.enabled && (
-
高度: {deviceTooltip.device.height || '-'}U
- )}
- {tooltipFields.ipAddress?.enabled && (
-
IP地址: {deviceTooltip.device.ipAddress || deviceTooltip.device.ip_address || '-'}
- )}
- {tooltipFields.power?.enabled && (
-
功率: {deviceTooltip.device.power ? `${deviceTooltip.device.power}W` : '-'}
- )}
+ {console.log('tooltipFields:', tooltipFields)}
+ {Object.entries(tooltipFields).map(([key, field]) => {
+ if (!field.enabled) return null;
+
+ let value = deviceTooltip.device[field.field];
+ let displayValue = '-';
+
+ if (value !== undefined && value !== null && value !== '') {
+ // 特殊字段格式化
+ if (field.field === 'status') {
+ const statusMap = {
+ 'running': '运行中',
+ 'maintenance': '维护中',
+ 'offline': '离线',
+ 'fault': '故障',
+ 'warning': '警告',
+ 'error': '故障'
+ };
+ displayValue = statusMap[value] || value;
+ } else if (field.field === 'type') {
+ const typeMap = {
+ 'server': '服务器',
+ 'switch': '交换机',
+ 'router': '路由器',
+ 'storage': '存储设备',
+ 'other': '其他设备'
+ };
+ displayValue = typeMap[value] || value;
+ } else if (field.field === 'position') {
+ displayValue = `U${value}`;
+ } else if (field.field === 'height') {
+ displayValue = `${value}U`;
+ } else if (field.field === 'powerConsumption' || field.field === 'power') {
+ displayValue = `${value}W`;
+ } else if (field.fieldType === 'date') {
+ try {
+ displayValue = new Date(value).toLocaleDateString('zh-CN');
+ } catch (e) {
+ displayValue = value;
+ }
+ } else if (field.field === 'rackId') {
+ displayValue = value;
+ } else {
+ displayValue = value;
+ }
+ }
+
+ // 获取字段标签(优先使用配置中的label,否则使用fieldName)
+ const label = field.label || field.fieldName;
+
+ // 状态字段使用特殊颜色
+ let valueColor = '#e2e8f0';
+ if (field.field === 'status') {
+ if (value === 'running') valueColor = '#4ade80';
+ else if (value === 'warning') valueColor = '#fbbf24';
+ else if (value === 'error' || value === 'fault') valueColor = '#f87171';
+ }
+
+ return (
+
+ {label}: {displayValue}
+
+ );
+ })}
)}