import React, { useState, useRef, useMemo, useEffect, useCallback } from 'react'; import { useFrame, useThree } from '@react-three/fiber'; import * as THREE from 'three'; const PERFORMANCE_MODE = true; // 背板渲染控制常量 const BACK_PANEL_CONFIG = { // 相机在正面时不渲染背板(z > threshold) visibilityThreshold: -0.3, // 距离阈值:太远时不渲染详细背板 detailDistanceThreshold: 3.0, // 简化背板距离:超过此距离只显示基础背板 simplifiedDistanceThreshold: 1.5, }; // ==================== 全局共享几何体和材质缓存 ==================== // 这些资源只创建一次,所有设备组件共享,大幅减少显存占用 // 基础几何体(使用 scale 调整大小,避免重复创建) const SHARED_GEOMETRIES = { // 1x1x1 单位立方体,使用时通过 scale 调整尺寸 box: new THREE.BoxGeometry(1, 1, 1), // 圆形几何体 circle: new THREE.CircleGeometry(1, 16), // 圆柱体 cylinder: new THREE.CylinderGeometry(1, 1, 1, 16), }; // 共享材质(按类型分类,避免重复创建相同材质) const SHARED_MATERIALS = { // 机身材质 - 深灰色哑光金属 chassis: new THREE.MeshStandardMaterial({ color: "#333333", roughness: 0.9, metalness: 0.3 }), // 面板材质 - 灰色塑料 panel: new THREE.MeshStandardMaterial({ color: "#555555", roughness: 0.8, metalness: 0.1 }), // 面板高亮材质 - 悬停/选中时使用 panelHover: new THREE.MeshStandardMaterial({ color: "#666666", roughness: 0.8, metalness: 0.1 }), // LED 灯材质 led: new THREE.MeshBasicMaterial({ toneMapped: false }), // 状态灯底座 ledBase: new THREE.MeshStandardMaterial({ color: "#333333" }), // 深色面板 darkPanel: new THREE.MeshStandardMaterial({ color: "#1e293b", roughness: 0.7, metalness: 0.5 }), // 硬盘托架 driveBay: new THREE.MeshStandardMaterial({ color: "#334155", roughness: 0.6, metalness: 0.4 }), // 装饰条 accent: new THREE.MeshStandardMaterial({ color: "#000000", roughness: 0.2 }), // 金属细节 metalDetail: new THREE.MeshStandardMaterial({ color: "#cbd5e1" }), // 网口 networkPort: new THREE.MeshStandardMaterial({ color: "#1e293b", roughness: 0.3 }), // 网口发光 networkLed: new THREE.MeshBasicMaterial({ color: "#10b981", toneMapped: false }), // 电源 powerSupply: new THREE.MeshStandardMaterial({ color: "#374151", roughness: 0.5 }), // 风扇 fan: new THREE.MeshStandardMaterial({ color: "#1f2937", roughness: 0.4 }), // 散热孔 vent: new THREE.MeshStandardMaterial({ color: "#111827" }), }; // InstancedMesh 共享几何体 const SHARED_INSTANCED_GEOMETRIES = { // 状态灯 statusLight: new THREE.CircleGeometry(0.0015, 8), // 硬盘托架 driveBay: new THREE.BoxGeometry(0.07, 0.035, 0.004), // 硬盘细节 driveDetail: new THREE.BoxGeometry(0.015, 0.025, 0.002), // 存储托架 storageBay: new THREE.BoxGeometry(0.08, 0.12, 0.004), // 存储细节 storageDetail: new THREE.BoxGeometry(0.06, 0.08, 0.002), // 网口 networkPort: new THREE.BoxGeometry(0.012, 0.008, 0.004), // 网口发光 networkLed: new THREE.BoxGeometry(0.003, 0.002, 0.001), // SFP端口 sfpPort: new THREE.BoxGeometry(0.02, 0.015, 0.005), // SFP内部 sfpInner: new THREE.BoxGeometry(0.016, 0.011, 0.002), // SFP连接器 sfpConnector: new THREE.BoxGeometry(0.01, 0.002, 0.008), // SFP指示灯 sfpLed: new THREE.ConeGeometry(0.002, 0.004, 3), }; // InstancedMesh 共享材质 const SHARED_INSTANCED_MATERIALS = { // 状态灯基础材质 statusLight: new THREE.MeshBasicMaterial({ toneMapped: false }), // 硬盘托架 driveBay: new THREE.MeshStandardMaterial({ color: '#334155', roughness: 0.6, metalness: 0.4 }), // 硬盘细节 driveDetail: new THREE.MeshStandardMaterial({ color: '#1e293b' }), // 存储托架 storageBay: new THREE.MeshStandardMaterial({ color: '#374151', roughness: 0.5 }), // 存储细节 storageDetail: new THREE.MeshStandardMaterial({ color: '#4b5563' }), // 网口 networkPort: new THREE.MeshStandardMaterial({ color: '#1e293b', roughness: 0.3 }), // 网口发光 networkLed: new THREE.MeshBasicMaterial({ color: '#10b981', toneMapped: false }), }; const InstancedStatusLights = ({ count, positions, colors: statusColors, zOffset }) => { const meshRef = useRef(); const dummy = useMemo(() => new THREE.Object3D(), []); useEffect(() => { if (meshRef.current) { for (let i = 0; i < count; i++) { dummy.position.set(positions[i].x, positions[i].y, positions[i].z + zOffset); dummy.updateMatrix(); meshRef.current.setMatrixAt(i, dummy.matrix); } meshRef.current.instanceMatrix.needsUpdate = true; } }, [count, positions, zOffset, dummy]); // 修复:正确更新实例颜色 useEffect(() => { if (meshRef.current) { for (let i = 0; i < count; i++) { const color = new THREE.Color(statusColors[i] || '#22c55e'); meshRef.current.setColorAt(i, color); } if (meshRef.current.instanceColor) { meshRef.current.instanceColor.needsUpdate = true; } } }, [count, statusColors]); // 资源清理 useEffect(() => { return () => { if (meshRef.current) { meshRef.current.geometry?.dispose(); meshRef.current.material?.dispose(); } }; }, []); return ( ); }; const InstancedDriveBays = ({ count, positions, color, hasDetail = true }) => { const meshRef = useRef(); const detailRef = useRef(); const dummy = useMemo(() => new THREE.Object3D(), []); useEffect(() => { if (meshRef.current) { for (let i = 0; i < count; i++) { dummy.position.set(positions[i].x, positions[i].y, positions[i].z); dummy.scale.set(1, 1, 1); dummy.updateMatrix(); meshRef.current.setMatrixAt(i, dummy.matrix); } meshRef.current.instanceMatrix.needsUpdate = true; } if (detailRef.current && hasDetail) { for (let i = 0; i < count; i++) { dummy.position.set(positions[i].x + 0.04, positions[i].y, positions[i].z + 0.003); dummy.scale.set(1, 1, 1); dummy.updateMatrix(); detailRef.current.setMatrixAt(i, dummy.matrix); } detailRef.current.instanceMatrix.needsUpdate = true; } }, [count, positions, hasDetail, dummy]); // 资源清理 useEffect(() => { return () => { if (meshRef.current) { meshRef.current.geometry?.dispose(); meshRef.current.material?.dispose(); } if (detailRef.current) { detailRef.current.geometry?.dispose(); detailRef.current.material?.dispose(); } }; }, []); return ( {hasDetail && ( )} ); }; const InstancedStorageBays = ({ count, positions, color }) => { const meshRef = useRef(); const detailRef = useRef(); const dummy = useMemo(() => new THREE.Object3D(), []); useEffect(() => { if (meshRef.current) { for (let i = 0; i < count; i++) { dummy.position.set(positions[i].x, positions[i].y, positions[i].z); dummy.scale.set(1, 1, 1); dummy.updateMatrix(); meshRef.current.setMatrixAt(i, dummy.matrix); } meshRef.current.instanceMatrix.needsUpdate = true; } if (detailRef.current) { for (let i = 0; i < count; i++) { dummy.position.set(positions[i].x + 0.03, positions[i].y, positions[i].z + 0.003); dummy.scale.set(1, 1, 1); dummy.updateMatrix(); detailRef.current.setMatrixAt(i, dummy.matrix); } detailRef.current.instanceMatrix.needsUpdate = true; } }, [count, positions, dummy]); // 资源清理 useEffect(() => { return () => { if (meshRef.current) { meshRef.current.geometry?.dispose(); meshRef.current.material?.dispose(); } if (detailRef.current) { detailRef.current.geometry?.dispose(); detailRef.current.material?.dispose(); } }; }, []); return ( ); }; const InstancedRJ45Ports = ({ count, positions, statuses, frontZ }) => { const meshRef = useRef(); const innerRef = useRef(); const tabRef = useRef(); const ledRef = useRef(); const dummy = useMemo(() => new THREE.Object3D(), []); useEffect(() => { if (meshRef.current) { for (let i = 0; i < count; i++) { dummy.position.set(positions[i].x, positions[i].y + 0.012, frontZ + 0.006); dummy.scale.set(1, 1, 1); dummy.updateMatrix(); meshRef.current.setMatrixAt(i, dummy.matrix); } meshRef.current.instanceMatrix.needsUpdate = true; } if (innerRef.current) { for (let i = 0; i < count; i++) { dummy.position.set(positions[i].x, positions[i].y + 0.012, frontZ + 0.007); dummy.scale.set(1, 1, 1); dummy.updateMatrix(); innerRef.current.setMatrixAt(i, dummy.matrix); } innerRef.current.instanceMatrix.needsUpdate = true; } if (tabRef.current) { for (let i = 0; i < count; i++) { dummy.position.set(positions[i].x, positions[i].y + 0.015, frontZ + 0.008); dummy.scale.set(1, 1, 1); dummy.updateMatrix(); tabRef.current.setMatrixAt(i, dummy.matrix); } tabRef.current.instanceMatrix.needsUpdate = true; } if (ledRef.current) { for (let i = 0; i < count; i++) { const status = statuses[i] || 'disconnected'; const ledY = positions[i].y + (i % 2 === 0 ? 0.021 : -0.002); dummy.position.set(positions[i].x - 0.004, ledY, frontZ + 0.006); dummy.scale.set(1, 1, 1); dummy.updateMatrix(); ledRef.current.setMatrixAt(i, dummy.matrix); } ledRef.current.instanceMatrix.needsUpdate = true; } }, [count, positions, statuses, frontZ, dummy]); const ledColors = useMemo(() => { return statuses.map(s => s !== 'disconnected' ? (s === 'fault' ? '#ef4444' : '#22c55e') : '#475569'); }, [statuses]); // 资源清理 useEffect(() => { return () => { if (meshRef.current) { meshRef.current.geometry?.dispose(); meshRef.current.material?.dispose(); } if (innerRef.current) { innerRef.current.geometry?.dispose(); innerRef.current.material?.dispose(); } if (tabRef.current) { tabRef.current.geometry?.dispose(); tabRef.current.material?.dispose(); } if (ledRef.current) { ledRef.current.geometry?.dispose(); ledRef.current.material?.dispose(); } }; }, []); return ( ); }; const InstancedSFPports = ({ count, positions, statuses, frontZ }) => { const meshRef = useRef(); const innerRef = useRef(); const connectorRef = useRef(); const ledRef = useRef(); const dummy = useMemo(() => new THREE.Object3D(), []); useEffect(() => { if (meshRef.current) { for (let i = 0; i < count; i++) { dummy.position.set(positions[i].x, positions[i].y, frontZ + 0.006); dummy.scale.set(1, 1, 1); dummy.updateMatrix(); meshRef.current.setMatrixAt(i, dummy.matrix); } meshRef.current.instanceMatrix.needsUpdate = true; } if (innerRef.current) { for (let i = 0; i < count; i++) { dummy.position.set(positions[i].x, positions[i].y, frontZ + 0.009); dummy.scale.set(1, 1, 1); dummy.updateMatrix(); innerRef.current.setMatrixAt(i, dummy.matrix); } innerRef.current.instanceMatrix.needsUpdate = true; } if (connectorRef.current) { for (let i = 0; i < count; i++) { const isConnected = statuses[i] !== 'disconnected'; dummy.position.set(positions[i].x, positions[i].y, frontZ + 0.012); dummy.scale.set(isConnected ? 1 : 0.001, 1, 1); dummy.updateMatrix(); connectorRef.current.setMatrixAt(i, dummy.matrix); } connectorRef.current.instanceMatrix.needsUpdate = true; } if (ledRef.current) { for (let i = 0; i < count; i++) { const isConnected = statuses[i] !== 'disconnected'; dummy.position.set(positions[i].x, positions[i].y + 0.01, frontZ + 0.009); dummy.scale.set(1, 1, 1); dummy.updateMatrix(); ledRef.current.setMatrixAt(i, dummy.matrix); } ledRef.current.instanceMatrix.needsUpdate = true; } }, [count, positions, statuses, frontZ, dummy]); // 修复:正确更新SFP指示灯颜色 useEffect(() => { if (ledRef.current) { for (let i = 0; i < count; i++) { const isConnected = statuses[i] !== 'disconnected'; const color = new THREE.Color(isConnected ? '#22c55e' : '#475569'); ledRef.current.setColorAt(i, color); } if (ledRef.current.instanceColor) { ledRef.current.instanceColor.needsUpdate = true; } } }, [count, statuses]); // 资源清理 useEffect(() => { return () => { if (meshRef.current) { meshRef.current.geometry?.dispose(); meshRef.current.material?.dispose(); } if (innerRef.current) { innerRef.current.geometry?.dispose(); innerRef.current.material?.dispose(); } if (connectorRef.current) { connectorRef.current.geometry?.dispose(); connectorRef.current.material?.dispose(); } if (ledRef.current) { ledRef.current.geometry?.dispose(); ledRef.current.material?.dispose(); } }; }, []); return ( ); }; const FirewallFace = ({ device, height, frontZ, isSelected }) => { const halfWidth = 0.4826 / 2; return ( {/* 防火墙左侧红色标识条 */} {/* 防火墙主面板边框 */} {!PERFORMANCE_MODE && Array.from({ length: 4 }).map((_, col) => ( ))} {!PERFORMANCE_MODE && ( )} {['#22c55e', '#22c55e', device.status === 'error' ? '#ef4444' : '#4b5563'].map((color, i) => ( {i === 2 && device.status === 'error' && ( )} ))} ); }; const DeviceModel = ({ device, rackHeight, isSelected, onClick, onHover, uHeight: propUHeight, position, rackDepth, slideEnabled = true }) => { const mesh = useRef(); const groupRef = useRef(); const [hovered, setHover] = useState(false); // 使用 ref 存储动画状态,避免每帧触发 React 状态更新 const isExtendedRef = useRef(false); const currentZRef = useRef(0); // 使用 ref 避免重复设置悬停状态 const isHoveredRef = useRef(false); // 背板渲染控制状态 const [backPanelLevel, setBackPanelLevel] = useState(0); // 0: 不渲染, 1: 简化, 2: 完整 const { camera } = useThree(); const frameCount = useRef(0); // 使用传入的 uHeight (默认为 0.04445m) const uHeight = propUHeight || 0.04445; const depth = rackDepth || 1.0; // 机柜深度,默认1米 // 滑轨动画 - 仅在展开时运行且slideEnabled为true // 使用 ref 直接操作 mesh,避免 React 状态更新 useFrame(() => { if (!slideEnabled || (!isExtendedRef.current && Math.abs(currentZRef.current) < 0.001)) { return; } const targetZ = isExtendedRef.current ? 0.6 : 0; const lerpFactor = 0.1; currentZRef.current += (targetZ - currentZRef.current) * lerpFactor; // 直接操作 group 的 position,不通过 React state if (groupRef.current) { groupRef.current.position.z = currentZRef.current; } }); // 背板渲染控制 - 根据相机位置动态调整 // 只在相机绕到背面时才渲染背板 useFrame(() => { // 每10帧检查一次,减少计算频率 frameCount.current++; if (frameCount.current % 10 !== 0) return; if (!groupRef.current) return; // 获取相机相对设备的位置 const deviceWorldPos = new THREE.Vector3(); groupRef.current.getWorldPosition(deviceWorldPos); const cameraZ = camera.position.z; // 简单逻辑:相机在正面(z > threshold)时不渲染背板 // 相机在背面(z <= threshold)时渲染完整背板 const shouldShowBackPanel = cameraZ <= BACK_PANEL_CONFIG.visibilityThreshold; const newLevel = shouldShowBackPanel ? 2 : 0; // 只有当级别变化时才更新状态 if (newLevel !== backPanelLevel) { setBackPanelLevel(newLevel); } }); // 尺寸定义 (适配标准 0.6m 机柜) // 标准19英寸机柜内部净宽约 0.45m,设备面板宽 0.4826m (19inch) const chassisWidth = 0.44; // 机身宽度 440mm const chassisDepth = 0.8; // 机身深度 800mm const panelWidth = 0.4826; // 前面板宽度 482.6mm (19英寸) const panelDepth = 0.02; // 前面板厚度 20mm // 计算实际位置(防止超出机柜) // 优先使用父组件传入的 position,如果未传入则内部计算(作为 fallback) // 注意:父组件 RackModel 已经计算好了 position=[0, yPos, 0] // 我们只需要处理设备的高度 const dHeight = device.height || device.u_height || 1; const height = dHeight * uHeight; // 间隙调整:减少设备上下间隙,使其更饱满,减少视觉偏差 const gap = 0.002; // 总间隙 2mm // Z轴定位 // 假设机柜中心为0,前沿为 depth/2 // 设备前面板应该贴近前沿 const frontZ = depth / 2 - 0.02; const panelZ = frontZ - panelDepth / 2; const chassisZ = frontZ - panelDepth - chassisDepth / 2; // 颜色定义 (参考 CSS 变量) const colors = { server: '#3b82f6', // --color-device-server switch: '#22c55e', // --color-device-switch router: '#f59e0b', // --color-device-router firewall: '#ef4444', // --color-device-firewall storage: '#8b5cf6', // --color-device-storage default: '#3b82f6', status: { running: '#10b981', // --color-status-running warning: '#f59e0b', // --color-status-warning error: '#ef4444', // --color-status-error offline: '#6b7280' // --color-status-offline }, panelBg: '#1e293b', // 深色面板背景 panelLight: '#334155', text: '#f1f5f9' }; const getDeviceColor = (type) => { const t = type?.toLowerCase() || ''; if (t.includes('server') || t.includes('服务器')) return colors.server; if (t.includes('switch') || t.includes('交换机')) return colors.switch; if (t.includes('router') || t.includes('路由器')) return colors.router; if (t.includes('firewall') || t.includes('防火墙')) return colors.firewall; if (t.includes('storage') || t.includes('存储')) return colors.storage; return colors.default; }; const deviceColor = getDeviceColor(device.type); const statusColor = colors.status[device.status] || colors.status.running; // 渲染服务器前面板细节 const renderServerFace = () => { const is2U = height > 0.08; const driveRows = is2U ? 2 : 1; const driveCols = PERFORMANCE_MODE ? 3 : 4; const driveWidth = 0.07; const driveHeight = 0.035; const drivePositions = []; for (let row = 0; row < driveRows; row++) { for (let col = 0; col < driveCols; col++) { if (!is2U && row > 0) continue; const xPos = (col - 1) * (driveWidth + 0.005); const yPos = is2U ? (row === 0 ? 0.02 : -0.02) : 0; drivePositions.push({ x: xPos, y: yPos, z: 0 }); } } return ( {/* 服务器主面板 - 深蓝色 */} {/* 服务器左侧标识条 - 亮蓝色 */} {!PERFORMANCE_MODE && ( )} ); }; // 渲染存储设备前面板细节 const renderStorageFace = () => { const rows = PERFORMANCE_MODE ? 2 : 3; const cols = PERFORMANCE_MODE ? 2 : 4; const bayWidth = 0.10; const bayHeight = 0.028; const bayPositions = []; for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { const xPos = (col - 1) * (bayWidth + 0.002); const yStep = (height - 0.02) / rows; const yPos = (rows - 1 - row) * yStep - (rows - 1) * yStep / 2; bayPositions.push({ x: xPos, y: yPos, z: 0 }); } } return ( {/* 存储设备主面板 - 深紫色 */} {/* 存储设备左侧标识条 - 紫色 */} ); }; // 渲染交换机前面板细节 const renderSwitchFace = () => { const getPortStatus = (portName) => { if (!device.cables || !Array.isArray(device.cables)) return 'disconnected'; const cable = device.cables.find(c => (c.sourceDeviceId === device.deviceId && c.sourcePort === portName) || (c.targetDeviceId === device.deviceId && c.targetPort === portName) ); return cable ? (cable.status || 'normal') : 'disconnected'; }; const sfpCount = PERFORMANCE_MODE ? 2 : 4; const rj45GroupCount = PERFORMANCE_MODE ? 2 : 4; const rj45ColCount = PERFORMANCE_MODE ? 4 : 6; const sfpPositions = []; const sfpStatuses = []; for (let i = 0; i < sfpCount; i++) { sfpPositions.push({ x: -0.13 + i * 0.025, y: -0.005, z: 0 }); sfpStatuses.push(getPortStatus(`SFP+ ${i + 1}`)); } const rj45PortCount = rj45GroupCount * rj45ColCount * 2; const rj45Positions = []; const rj45Statuses = []; for (let groupIndex = 0; groupIndex < rj45GroupCount; groupIndex++) { const groupX = -0.02 + groupIndex * 0.09; for (let colIndex = 0; colIndex < rj45ColCount; colIndex++) { const portX = -0.03 + colIndex * 0.012; const globalCol = groupIndex * rj45ColCount + colIndex; const topPortNum = globalCol * 2 + 1; const bottomPortNum = globalCol * 2 + 2; const topStatus = getPortStatus(`Port ${topPortNum}`); const bottomStatus = getPortStatus(`Port ${bottomPortNum}`); rj45Positions.push({ x: groupX + portX, y: 0, z: 0 }); rj45Statuses.push(topStatus); rj45Positions.push({ x: groupX + portX, y: -0.024, z: 0 }); rj45Statuses.push(bottomStatus); } } return ( {/* 交换机主面板 - 深绿色背景突出显示 */} {/* 交换机左侧标识条 - 亮绿色 */} {/* 交换机类型标识 */} {['SYS', 'PWR'].map((label, idx) => ( ))} ); }; // 渲染简化版背板(只显示基础背板) const renderSimplifiedBackPanel = (backZ) => { return ( {/* 基础背板 - 使用共享几何体 */} ); }; // 渲染完整版背板(包含 PSU、风扇、网卡等细节) const renderFullBackPanel = (backZ) => { return ( {/* 基础背板 - 透明玻璃质感 */} {/* 电源模块 (PSU) - 左侧 */} {[-0.02, 0.02].map((yOffset, i) => ( 0.15 ? yOffset * 2 : 0, 0]}> {(height > 0.15 || i === 0) && ( {/* PSU 面板 */} 0.15 ? 0.04 : 0.08, 0.004]} /> {/* 把手 */} {/* 电源插口 */} {/* 状态灯 */} )} ))} {/* 风扇模块 (Fan Modules) - 中间 */} {Array.from({ length: 3 }).map((_, i) => ( {/* 风扇网罩 */} {/* 风扇叶片 */} {/* 红色拉手 */} ))} {/* 网卡/扩展模块 (PCIe/LOM) - 右侧 */} {Array.from({ length: 2 }).map((_, i) => ( {/* 端口 */} ))} ); }; // 根据级别渲染背板 const renderBackPanel = () => { // Back face Z position const backZ = chassisZ - chassisDepth / 2; // 根据背板级别决定渲染内容 switch (backPanelLevel) { case 0: // 不渲染背板 return null; case 1: // 简化背板 return renderSimplifiedBackPanel(backZ); case 2: // 完整背板 return renderFullBackPanel(backZ); default: return null; } }; const renderDeviceFace = () => { const type = device.type?.toLowerCase() || ''; if (type.includes('server') || type.includes('服务器')) return renderServerFace(); if (type.includes('switch') || type.includes('交换机')) return renderSwitchFace(); if (type.includes('storage') || type.includes('存储')) return renderStorageFace(); if (type.includes('firewall') || type.includes('防火墙') || type.includes('router') || type.includes('路由器')) { return ; } // 默认通用设备样式 return ( {/* 默认面板纹理 */} {/* 左侧灰色标识条 */} {/* 装饰线 */} ); }; return ( { e.stopPropagation(); isExtendedRef.current = !isExtendedRef.current; onClick && onClick(device); }} onPointerOver={(e) => { e.stopPropagation(); // 使用 ref 避免重复设置状态 if (!isHoveredRef.current) { isHoveredRef.current = true; setHover(true); onHover && onHover(device); } }} onPointerOut={(e) => { // 重置 ref 并更新状态 if (isHoveredRef.current) { isHoveredRef.current = false; setHover(false); onHover && onHover(null); } }} > {/* 机身 (Chassis) - 使用共享几何体和材质 */} {/* 前面板底座 (Front Panel Base) - 使用共享资源 */} {/* 告警时的红色辉光 (设备两侧) */} {(device.status === 'error' || device.status === 'fault') && ( <> )} {/* 设备特定前面板细节 */} {renderDeviceFace()} {/* 设备背板细节 */} {renderBackPanel()} {/* 设备名称 (悬浮显示,避免遮挡细节) - 暂时禁用 */} {/* {hovered && ( {device.name} )} */} {/* 状态指示灯 (统一位置) - 使用共享几何体 */} {/* 灯座 */} {/* 发光体 - 需要动态颜色,使用克隆材质 */} {/* 光晕效果 */} ); }; // 使用 React.memo 优化,避免不必要的重渲染 // 自定义比较函数:只在关键属性变化时才重新渲染 const DeviceModelMemo = React.memo(DeviceModel, (prevProps, nextProps) => { // 比较设备 ID if (prevProps.device?.id !== nextProps.device?.id) return false; if (prevProps.device?.deviceId !== nextProps.device?.deviceId) return false; // 比较选中状态 if (prevProps.isSelected !== nextProps.isSelected) return false; // 比较滑出动画开关 if (prevProps.slideEnabled !== nextProps.slideEnabled) return false; // 比较位置(使用 JSON.stringify 简单比较) if (JSON.stringify(prevProps.position) !== JSON.stringify(nextProps.position)) return false; // 比较机柜相关属性 if (prevProps.rackHeight !== nextProps.rackHeight) return false; if (prevProps.rackDepth !== nextProps.rackDepth) return false; if (prevProps.uHeight !== nextProps.uHeight) return false; // 修复:比较设备状态,确保状态变化时正确重渲染 if (prevProps.device?.status !== nextProps.device?.status) return false; // 修复:比较连接线,确保端口状态变化时正确重渲染 const prevCables = prevProps.device?.cables; const nextCables = nextProps.device?.cables; if (Array.isArray(prevCables) && Array.isArray(nextCables)) { if (prevCables.length !== nextCables.length) return false; // 简单比较:检查每个连接线的关键属性 for (let i = 0; i < prevCables.length; i++) { if (prevCables[i]?.status !== nextCables[i]?.status) return false; } } else if (prevCables !== nextCables) { return false; } // 以上都相同,不需要重新渲染 return true; }); export default DeviceModelMemo;