chore: 统一代码风格并配置ESLint和Prettier
配置ESLint和Prettier规则 添加前端和后端的忽略文件 统一代码格式和缩进 修复代码风格问题
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -5,12 +5,12 @@ import * as THREE from 'three';
|
||||
export const LOD_LEVELS = {
|
||||
HIGH: 0,
|
||||
MEDIUM: 1,
|
||||
LOW: 2
|
||||
LOW: 2,
|
||||
};
|
||||
|
||||
export const LOD_DISTANCES = {
|
||||
HIGH: 5,
|
||||
MEDIUM: 10
|
||||
MEDIUM: 10,
|
||||
};
|
||||
|
||||
const createSimplifiedDeviceMesh = (device, uHeight, rackDepth, deviceColor, statusColor) => {
|
||||
@@ -34,7 +34,7 @@ const createSimplifiedDeviceMesh = (device, uHeight, rackDepth, deviceColor, sta
|
||||
<boxGeometry args={[chassisWidth, height - 0.002, chassisDepth]} />
|
||||
<meshStandardMaterial color="#333333" roughness={0.9} metalness={0.3} />
|
||||
</mesh>
|
||||
<mesh position={[panelWidth/2 - 0.03, halfHeight - 0.02, frontZ + 0.01]}>
|
||||
<mesh position={[panelWidth / 2 - 0.03, halfHeight - 0.02, frontZ + 0.01]}>
|
||||
<circleGeometry args={[0.006, 16]} />
|
||||
<meshBasicMaterial color={statusColor} toneMapped={false} />
|
||||
</mesh>
|
||||
@@ -65,9 +65,9 @@ const createMediumDeviceMesh = (device, uHeight, rackDepth, deviceColor, statusC
|
||||
<meshStandardMaterial color={deviceColor} roughness={0.8} metalness={0.1} />
|
||||
</mesh>
|
||||
<mesh position={[0, 0, frontZ - panelDepth - chassisDepth / 2]}>
|
||||
<boxGeometry args={[chassisWidth, height - 0.002, chassisDepth]} />
|
||||
<meshStandardMaterial color="#333333" roughness={0.9} metalness={0.3} />
|
||||
</mesh>
|
||||
<boxGeometry args={[chassisWidth, height - 0.002, chassisDepth]} />
|
||||
<meshStandardMaterial color="#333333" roughness={0.9} metalness={0.3} />
|
||||
</mesh>
|
||||
<group position={[-0.18, 0, frontZ + 0.006]}>
|
||||
<mesh position={[-0.02, 0, 0]}>
|
||||
<boxGeometry args={[0.04, height - 0.01, 0.002]} />
|
||||
@@ -86,7 +86,7 @@ const createMediumDeviceMesh = (device, uHeight, rackDepth, deviceColor, statusC
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
<mesh position={[panelWidth/2 - 0.03, halfHeight - 0.02, frontZ + 0.01]}>
|
||||
<mesh position={[panelWidth / 2 - 0.03, halfHeight - 0.02, frontZ + 0.01]}>
|
||||
<circleGeometry args={[0.006, 16]} />
|
||||
<meshBasicMaterial color={statusColor} toneMapped={false} />
|
||||
</mesh>
|
||||
@@ -94,15 +94,15 @@ const createMediumDeviceMesh = (device, uHeight, rackDepth, deviceColor, statusC
|
||||
);
|
||||
};
|
||||
|
||||
const LODManager = ({
|
||||
device,
|
||||
uHeight,
|
||||
rackDepth,
|
||||
position,
|
||||
deviceColor,
|
||||
statusColor,
|
||||
const LODManager = ({
|
||||
device,
|
||||
uHeight,
|
||||
rackDepth,
|
||||
position,
|
||||
deviceColor,
|
||||
statusColor,
|
||||
children,
|
||||
level = LOD_LEVELS.HIGH
|
||||
level = LOD_LEVELS.HIGH,
|
||||
}) => {
|
||||
const groupRef = useRef();
|
||||
const highDetailRef = useRef();
|
||||
@@ -119,26 +119,26 @@ const LODManager = ({
|
||||
|
||||
useFrame(() => {
|
||||
if (!groupRef.current) return;
|
||||
|
||||
|
||||
// 节流:每5帧检查一次
|
||||
frameCount.current++;
|
||||
if (frameCount.current % 5 !== 0) return;
|
||||
|
||||
|
||||
const distance = camera.position.distanceTo(groupRef.current.position);
|
||||
distanceRef.current = distance;
|
||||
|
||||
|
||||
// 添加缓冲避免频繁切换(10% 缓冲)
|
||||
const buffer = 0.1;
|
||||
const highThreshold = LOD_DISTANCES.HIGH * (1 + buffer);
|
||||
const mediumThreshold = LOD_DISTANCES.MEDIUM * (1 + buffer);
|
||||
|
||||
|
||||
let newLevel = LOD_LEVELS.HIGH;
|
||||
if (distance > mediumThreshold) {
|
||||
newLevel = LOD_LEVELS.LOW;
|
||||
} else if (distance > highThreshold) {
|
||||
newLevel = LOD_LEVELS.MEDIUM;
|
||||
}
|
||||
|
||||
|
||||
// 只有当级别变化时才更新
|
||||
if (newLevel !== lodLevelRef.current) {
|
||||
lodLevelRef.current = newLevel;
|
||||
@@ -169,12 +169,12 @@ const LODManager = ({
|
||||
<group ref={highDetailRef} visible={true}>
|
||||
{children}
|
||||
</group>
|
||||
|
||||
|
||||
{/* 中等细节模型 */}
|
||||
<group ref={mediumDetailRef} visible={false}>
|
||||
{createMediumDeviceMesh(device, uHeight, rackDepth, deviceColor, statusColor)}
|
||||
</group>
|
||||
|
||||
|
||||
{/* 低细节模型 */}
|
||||
<group ref={lowDetailRef} visible={false}>
|
||||
{createSimplifiedDeviceMesh(device, uHeight, rackDepth, deviceColor, statusColor)}
|
||||
|
||||
@@ -3,24 +3,24 @@ import * as THREE from 'three';
|
||||
import DeviceModel from './DeviceModel';
|
||||
import LODManager, { LOD_LEVELS } from './LODManager';
|
||||
|
||||
const RackModel = ({
|
||||
rack,
|
||||
devices = [],
|
||||
selectedDeviceId,
|
||||
onDeviceClick,
|
||||
onDeviceLeave,
|
||||
onDeviceHover,
|
||||
onEditDevice,
|
||||
onAddNic,
|
||||
onAddPort,
|
||||
const RackModel = ({
|
||||
rack,
|
||||
devices = [],
|
||||
selectedDeviceId,
|
||||
onDeviceClick,
|
||||
onDeviceLeave,
|
||||
onDeviceHover,
|
||||
onEditDevice,
|
||||
onAddNic,
|
||||
onAddPort,
|
||||
tooltipFields,
|
||||
deviceSlideEnabled = true
|
||||
deviceSlideEnabled = true,
|
||||
}) => {
|
||||
const width = 0.6;
|
||||
const depth = 1.0;
|
||||
const uHeight = 0.04445;
|
||||
const postWidth = 0.05;
|
||||
|
||||
|
||||
const rackHeight = rack?.height || 45;
|
||||
const height = rackHeight * uHeight + 0.2;
|
||||
|
||||
@@ -33,21 +33,21 @@ const RackModel = ({
|
||||
storage: '#8b5cf6',
|
||||
default: '#3b82f6',
|
||||
status: {
|
||||
running: '#10b981',
|
||||
warning: '#f59e0b',
|
||||
error: '#ef4444',
|
||||
offline: '#6b7280'
|
||||
}
|
||||
running: '#10b981',
|
||||
warning: '#f59e0b',
|
||||
error: '#ef4444',
|
||||
offline: '#6b7280',
|
||||
},
|
||||
};
|
||||
|
||||
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 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;
|
||||
};
|
||||
|
||||
// 设备组的Y偏移量(与下方设备渲染的偏移一致)
|
||||
@@ -62,7 +62,7 @@ const RackModel = ({
|
||||
const yPos = (u - 1) * uHeight + uHeight / 2 + deviceGroupOffset;
|
||||
|
||||
// 创建数字纹理 - 白色数字在深色背景上更清晰
|
||||
const createNumberTexture = (num) => {
|
||||
const createNumberTexture = num => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 128;
|
||||
canvas.height = 128;
|
||||
@@ -90,9 +90,9 @@ const RackModel = ({
|
||||
const planeGeometry = new THREE.PlaneGeometry(planeSize, planeSize);
|
||||
|
||||
// 前侧柱子的位置: [-width/2 + postWidth/2, y, depth/2 - postWidth/2]
|
||||
const leftPostX = -width/2 + postWidth/2;
|
||||
const rightPostX = width/2 - postWidth/2;
|
||||
const frontPostZ = depth/2 - postWidth/2;
|
||||
const leftPostX = -width / 2 + postWidth / 2;
|
||||
const rightPostX = width / 2 - postWidth / 2;
|
||||
const frontPostZ = depth / 2 - postWidth / 2;
|
||||
|
||||
// 刻度线颜色:每5U使用醒目的黄色,其他使用灰色
|
||||
const tickColor = isMajorU ? '#fbbf24' : '#6b7280';
|
||||
@@ -102,7 +102,7 @@ const RackModel = ({
|
||||
<group key={`u-label-${u}`}>
|
||||
{/* 左前侧柱子上的U位标识 - 贴在柱子正侧面(面向前方) */}
|
||||
<mesh
|
||||
position={[leftPostX, yPos, frontPostZ + postWidth/2 + 0.001]}
|
||||
position={[leftPostX, yPos, frontPostZ + postWidth / 2 + 0.001]}
|
||||
geometry={planeGeometry}
|
||||
>
|
||||
<meshBasicMaterial
|
||||
@@ -114,7 +114,7 @@ const RackModel = ({
|
||||
</mesh>
|
||||
{/* 右前侧柱子上的U位标识 - 贴在柱子正侧面(面向前方) */}
|
||||
<mesh
|
||||
position={[rightPostX, yPos, frontPostZ + postWidth/2 + 0.001]}
|
||||
position={[rightPostX, yPos, frontPostZ + postWidth / 2 + 0.001]}
|
||||
geometry={planeGeometry}
|
||||
>
|
||||
<meshBasicMaterial
|
||||
@@ -125,12 +125,12 @@ const RackModel = ({
|
||||
/>
|
||||
</mesh>
|
||||
{/* 左前侧柱子上的刻度线 */}
|
||||
<mesh position={[leftPostX, yPos, frontPostZ + postWidth/2 + 0.002]}>
|
||||
<mesh position={[leftPostX, yPos, frontPostZ + postWidth / 2 + 0.002]}>
|
||||
<boxGeometry args={[postWidth, tickHeight, 0.001]} />
|
||||
<meshBasicMaterial color={tickColor} />
|
||||
</mesh>
|
||||
{/* 右前侧柱子上的刻度线 */}
|
||||
<mesh position={[rightPostX, yPos, frontPostZ + postWidth/2 + 0.002]}>
|
||||
<mesh position={[rightPostX, yPos, frontPostZ + postWidth / 2 + 0.002]}>
|
||||
<boxGeometry args={[postWidth, tickHeight, 0.001]} />
|
||||
<meshBasicMaterial color={tickColor} />
|
||||
</mesh>
|
||||
@@ -142,53 +142,47 @@ const RackModel = ({
|
||||
|
||||
// 生成机柜框架
|
||||
const frame = useMemo(() => {
|
||||
const materialProps = { color: "#333", roughness: 0.5, metalness: 0.8 };
|
||||
const materialProps = { color: '#333', roughness: 0.5, metalness: 0.8 };
|
||||
const postArgs = [postWidth, height, postWidth];
|
||||
const topBottomArgs = [width + 0.02, 0.02, depth + 0.02];
|
||||
|
||||
return (
|
||||
<group>
|
||||
<mesh position={[-width/2 + postWidth/2, height/2, -depth/2 + postWidth/2]}>
|
||||
<mesh position={[-width / 2 + postWidth / 2, height / 2, -depth / 2 + postWidth / 2]}>
|
||||
<boxGeometry args={postArgs} />
|
||||
<meshStandardMaterial {...materialProps} />
|
||||
</mesh>
|
||||
<mesh position={[width/2 - postWidth/2, height/2, -depth/2 + postWidth/2]}>
|
||||
<mesh position={[width / 2 - postWidth / 2, height / 2, -depth / 2 + postWidth / 2]}>
|
||||
<boxGeometry args={postArgs} />
|
||||
<meshStandardMaterial {...materialProps} />
|
||||
</mesh>
|
||||
<mesh position={[-width/2 + postWidth/2, height/2, depth/2 - postWidth/2]}>
|
||||
<mesh position={[-width / 2 + postWidth / 2, height / 2, depth / 2 - postWidth / 2]}>
|
||||
<boxGeometry args={postArgs} />
|
||||
<meshStandardMaterial {...materialProps} />
|
||||
</mesh>
|
||||
<mesh position={[width/2 - postWidth/2, height/2, depth/2 - postWidth/2]}>
|
||||
<mesh position={[width / 2 - postWidth / 2, height / 2, depth / 2 - postWidth / 2]}>
|
||||
<boxGeometry args={postArgs} />
|
||||
<meshStandardMaterial {...materialProps} />
|
||||
</mesh>
|
||||
|
||||
<mesh position={[0, height, 0]}>
|
||||
<boxGeometry args={topBottomArgs} />
|
||||
<meshStandardMaterial {...materialProps} />
|
||||
<boxGeometry args={topBottomArgs} />
|
||||
<meshStandardMaterial {...materialProps} />
|
||||
</mesh>
|
||||
<mesh position={[0, 0, 0]}>
|
||||
<boxGeometry args={topBottomArgs} />
|
||||
<meshStandardMaterial {...materialProps} />
|
||||
<boxGeometry args={topBottomArgs} />
|
||||
<meshStandardMaterial {...materialProps} />
|
||||
</mesh>
|
||||
|
||||
{[-1, 1].map((side) => (
|
||||
<mesh key={`side-${side}`} position={[side * (width/2 - 0.005), height/2, 0]}>
|
||||
<boxGeometry args={[0.01, height - 0.04, depth - 0.04]} />
|
||||
<meshStandardMaterial
|
||||
color="#2d3748"
|
||||
roughness={0.4}
|
||||
metalness={0.7}
|
||||
side={2}
|
||||
/>
|
||||
</mesh>
|
||||
{[-1, 1].map(side => (
|
||||
<mesh key={`side-${side}`} position={[side * (width / 2 - 0.005), height / 2, 0]}>
|
||||
<boxGeometry args={[0.01, height - 0.04, depth - 0.04]} />
|
||||
<meshStandardMaterial color="#2d3748" roughness={0.4} metalness={0.7} side={2} />
|
||||
</mesh>
|
||||
))}
|
||||
|
||||
{/* U位刻度标识 */}
|
||||
{uLabels}
|
||||
|
||||
</group>
|
||||
);
|
||||
}, [width, height, depth, postWidth, uLabels]);
|
||||
@@ -197,47 +191,47 @@ const RackModel = ({
|
||||
<group position={[0, 0.5, 0]}>
|
||||
{frame}
|
||||
|
||||
<group position={[0, 0.1, 0]}>
|
||||
{devices.map((device) => {
|
||||
const uStart = device.position || device.u_position || 1;
|
||||
const uSize = device.height || device.u_height || 1;
|
||||
const yPos = (uStart - 1) * uHeight + (uSize * uHeight) / 2;
|
||||
|
||||
const deviceColor = getDeviceColor(device.type);
|
||||
const statusColor = colors.status[device.status] || colors.status.running;
|
||||
|
||||
return (
|
||||
<LODManager
|
||||
key={device.id}
|
||||
device={device}
|
||||
uHeight={uHeight}
|
||||
rackDepth={depth}
|
||||
position={[0, yPos, 0]}
|
||||
deviceColor={deviceColor}
|
||||
statusColor={statusColor}
|
||||
level={LOD_LEVELS.HIGH}
|
||||
>
|
||||
<DeviceModel
|
||||
device={device}
|
||||
uHeight={uHeight}
|
||||
rackDepth={depth}
|
||||
position={[0, 0, 0]}
|
||||
isSelected={selectedDeviceId === device.id}
|
||||
onClick={onDeviceClick}
|
||||
onPointerOver={onDeviceHover}
|
||||
onPointerOut={onDeviceLeave}
|
||||
onEdit={onEditDevice}
|
||||
onAddNic={onAddNic}
|
||||
onAddPort={onAddPort}
|
||||
tooltipFields={tooltipFields}
|
||||
slideEnabled={deviceSlideEnabled}
|
||||
/>
|
||||
</LODManager>
|
||||
);
|
||||
<group position={[0, 0.1, 0]}>
|
||||
{devices.map(device => {
|
||||
const uStart = device.position || device.u_position || 1;
|
||||
const uSize = device.height || device.u_height || 1;
|
||||
const yPos = (uStart - 1) * uHeight + (uSize * uHeight) / 2;
|
||||
|
||||
const deviceColor = getDeviceColor(device.type);
|
||||
const statusColor = colors.status[device.status] || colors.status.running;
|
||||
|
||||
return (
|
||||
<LODManager
|
||||
key={device.id}
|
||||
device={device}
|
||||
uHeight={uHeight}
|
||||
rackDepth={depth}
|
||||
position={[0, yPos, 0]}
|
||||
deviceColor={deviceColor}
|
||||
statusColor={statusColor}
|
||||
level={LOD_LEVELS.HIGH}
|
||||
>
|
||||
<DeviceModel
|
||||
device={device}
|
||||
uHeight={uHeight}
|
||||
rackDepth={depth}
|
||||
position={[0, 0, 0]}
|
||||
isSelected={selectedDeviceId === device.id}
|
||||
onClick={onDeviceClick}
|
||||
onPointerOver={onDeviceHover}
|
||||
onPointerOut={onDeviceLeave}
|
||||
onEdit={onEditDevice}
|
||||
onAddNic={onAddNic}
|
||||
onAddPort={onAddPort}
|
||||
tooltipFields={tooltipFields}
|
||||
slideEnabled={deviceSlideEnabled}
|
||||
/>
|
||||
</LODManager>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
</group>
|
||||
);
|
||||
};
|
||||
|
||||
export default RackModel;
|
||||
export default RackModel;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import React, { Suspense, useMemo, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import React, {
|
||||
Suspense,
|
||||
useMemo,
|
||||
useRef,
|
||||
useEffect,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
||||
import { OrbitControls, PerspectiveCamera, Environment } from '@react-three/drei';
|
||||
const envMapUrl = '/assets/3d/env.hdr';
|
||||
@@ -22,7 +29,7 @@ const Controls = ({ rack, onControlsReady }) => {
|
||||
const { camera } = useThree();
|
||||
// 机柜中心点(中轴线)
|
||||
const rackHeight = rack?.height || 45;
|
||||
const targetY = rackHeight * 0.04445 / 2 + 0.5;
|
||||
const targetY = (rackHeight * 0.04445) / 2 + 0.5;
|
||||
const fixedTarget = useMemo(() => new THREE.Vector3(0, targetY, 0), [targetY]);
|
||||
|
||||
// 根据机柜高度计算合适的相机距离限制
|
||||
@@ -48,7 +55,7 @@ const Controls = ({ rack, onControlsReady }) => {
|
||||
camera.position.copy(initialCameraPosition);
|
||||
controlsRef.current.target.copy(fixedTarget);
|
||||
controlsRef.current.update();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -76,107 +83,122 @@ const Controls = ({ rack, onControlsReady }) => {
|
||||
enableZoom={true}
|
||||
enableRotate={true}
|
||||
mouseButtons={{
|
||||
LEFT: 0, // 左键旋转
|
||||
MIDDLE: 1, // 中键平移
|
||||
RIGHT: 2 // 右键平移
|
||||
LEFT: 0, // 左键旋转
|
||||
MIDDLE: 1, // 中键平移
|
||||
RIGHT: 2, // 右键平移
|
||||
}}
|
||||
touches={{
|
||||
ONE: 1,
|
||||
TWO: 2
|
||||
TWO: 2,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Scene = forwardRef(({ rack, tooltipFields, onDeviceClick, onDeviceHover, onDeviceLeave }, ref) => {
|
||||
// 从 Context 获取3D场景状态
|
||||
const {
|
||||
devices,
|
||||
selectedDevice,
|
||||
deviceSlideEnabled
|
||||
} = useScene3D();
|
||||
const Scene = forwardRef(
|
||||
({ rack, tooltipFields, onDeviceClick, onDeviceHover, onDeviceLeave }, ref) => {
|
||||
// 从 Context 获取3D场景状态
|
||||
const { devices, selectedDevice, deviceSlideEnabled } = useScene3D();
|
||||
|
||||
// 用于存储 controls API
|
||||
const controlsApiRef = useRef(null);
|
||||
// 用于存储 controls API
|
||||
const controlsApiRef = useRef(null);
|
||||
|
||||
// 使用 useImperativeHandle 暴露重置方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
resetView: () => {
|
||||
if (controlsApiRef.current) {
|
||||
controlsApiRef.current.reset();
|
||||
}
|
||||
}
|
||||
}));
|
||||
// 使用 useImperativeHandle 暴露重置方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
resetView: () => {
|
||||
if (controlsApiRef.current) {
|
||||
controlsApiRef.current.reset();
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
// 使用 useMemo 稳定 props 引用
|
||||
const rackModelProps = useMemo(() => ({
|
||||
rack,
|
||||
devices,
|
||||
selectedDeviceId: selectedDevice?.id,
|
||||
onDeviceClick,
|
||||
onDeviceLeave,
|
||||
onDeviceHover,
|
||||
tooltipFields,
|
||||
deviceSlideEnabled
|
||||
}), [rack, devices, selectedDevice, onDeviceClick, onDeviceLeave, onDeviceHover, tooltipFields, deviceSlideEnabled]);
|
||||
// 使用 useMemo 稳定 props 引用
|
||||
const rackModelProps = useMemo(
|
||||
() => ({
|
||||
rack,
|
||||
devices,
|
||||
selectedDeviceId: selectedDevice?.id,
|
||||
onDeviceClick,
|
||||
onDeviceLeave,
|
||||
onDeviceHover,
|
||||
tooltipFields,
|
||||
deviceSlideEnabled,
|
||||
}),
|
||||
[
|
||||
rack,
|
||||
devices,
|
||||
selectedDevice,
|
||||
onDeviceClick,
|
||||
onDeviceLeave,
|
||||
onDeviceHover,
|
||||
tooltipFields,
|
||||
deviceSlideEnabled,
|
||||
]
|
||||
);
|
||||
|
||||
// 根据机柜高度动态计算相机初始位置
|
||||
const rackHeight = rack?.height || 45;
|
||||
const rackHeightMeters = rackHeight * 0.04445;
|
||||
// 相机位置:确保能完整看到机柜,高度随机柜高度调整
|
||||
const cameraPosition = useMemo(() => {
|
||||
const baseHeight = 2;
|
||||
const heightFactor = rackHeightMeters * 0.6;
|
||||
const distance = Math.max(3, rackHeightMeters * 1.2);
|
||||
return [distance * 0.7, baseHeight + heightFactor * 0.3, distance];
|
||||
}, [rackHeightMeters]);
|
||||
// 根据机柜高度动态计算相机初始位置
|
||||
const rackHeight = rack?.height || 45;
|
||||
const rackHeightMeters = rackHeight * 0.04445;
|
||||
// 相机位置:确保能完整看到机柜,高度随机柜高度调整
|
||||
const cameraPosition = useMemo(() => {
|
||||
const baseHeight = 2;
|
||||
const heightFactor = rackHeightMeters * 0.6;
|
||||
const distance = Math.max(3, rackHeightMeters * 1.2);
|
||||
return [distance * 0.7, baseHeight + heightFactor * 0.3, distance];
|
||||
}, [rackHeightMeters]);
|
||||
|
||||
// 相机目标点(机柜中心)
|
||||
const cameraTarget = useMemo(() => {
|
||||
return [0, rackHeightMeters / 2 + 0.5, 0];
|
||||
}, [rackHeightMeters]);
|
||||
// 相机目标点(机柜中心)
|
||||
const cameraTarget = useMemo(() => {
|
||||
return [0, rackHeightMeters / 2 + 0.5, 0];
|
||||
}, [rackHeightMeters]);
|
||||
|
||||
return (
|
||||
<Canvas
|
||||
shadows
|
||||
dpr={deviceDpr}
|
||||
performance={{ min: 0.5 }}
|
||||
gl={{
|
||||
antialias: true, // 对所有设备开启抗锯齿提升清晰度
|
||||
alpha: true, // 必须开启alpha以支持透明背景
|
||||
powerPreference: 'high-performance'
|
||||
}}
|
||||
style={{ background: 'transparent' }}
|
||||
>
|
||||
<PerspectiveCamera makeDefault position={cameraPosition} fov={45} />
|
||||
return (
|
||||
<Canvas
|
||||
shadows
|
||||
dpr={deviceDpr}
|
||||
performance={{ min: 0.5 }}
|
||||
gl={{
|
||||
antialias: true, // 对所有设备开启抗锯齿提升清晰度
|
||||
alpha: true, // 必须开启alpha以支持透明背景
|
||||
powerPreference: 'high-performance',
|
||||
}}
|
||||
style={{ background: 'transparent' }}
|
||||
>
|
||||
<PerspectiveCamera makeDefault position={cameraPosition} fov={45} />
|
||||
|
||||
<ambientLight intensity={0.5} color="#ffffff" />
|
||||
<pointLight position={[5, 8, 5]} intensity={2} color="#ffffff" castShadow />
|
||||
<directionalLight
|
||||
position={[10, 10, 5]}
|
||||
intensity={1}
|
||||
castShadow
|
||||
shadow-mapSize={[2048, 2048]}
|
||||
shadow-camera-far={20}
|
||||
shadow-camera-left={-10}
|
||||
shadow-camera-right={10}
|
||||
shadow-camera-top={10}
|
||||
shadow-camera-bottom={-10}
|
||||
/>
|
||||
<ambientLight intensity={0.5} color="#ffffff" />
|
||||
<pointLight position={[5, 8, 5]} intensity={2} color="#ffffff" castShadow />
|
||||
<directionalLight
|
||||
position={[10, 10, 5]}
|
||||
intensity={1}
|
||||
castShadow
|
||||
shadow-mapSize={[2048, 2048]}
|
||||
shadow-camera-far={20}
|
||||
shadow-camera-left={-10}
|
||||
shadow-camera-right={10}
|
||||
shadow-camera-top={10}
|
||||
shadow-camera-bottom={-10}
|
||||
/>
|
||||
|
||||
<Suspense fallback={null}>
|
||||
<Environment files={envMapUrl} blur={0.5} resolution={256} background={false} />
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<Environment files={envMapUrl} blur={0.5} resolution={256} background={false} />
|
||||
</Suspense>
|
||||
|
||||
{/* Models */}
|
||||
<group position={[0, 0, 0]}>
|
||||
<RackModel {...rackModelProps} />
|
||||
</group>
|
||||
{/* Models */}
|
||||
<group position={[0, 0, 0]}>
|
||||
<RackModel {...rackModelProps} />
|
||||
</group>
|
||||
|
||||
{/* Controls - 使用独立组件保持旋转中心固定 */}
|
||||
<Controls rack={rack} onControlsReady={(api) => { controlsApiRef.current = api; }} />
|
||||
</Canvas>
|
||||
);
|
||||
});
|
||||
{/* Controls - 使用独立组件保持旋转中心固定 */}
|
||||
<Controls
|
||||
rack={rack}
|
||||
onControlsReady={api => {
|
||||
controlsApiRef.current = api;
|
||||
}}
|
||||
/>
|
||||
</Canvas>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default Scene;
|
||||
export default Scene;
|
||||
|
||||
@@ -3,27 +3,62 @@ import { MATERIAL_CONFIGS, MATERIAL_TYPES } from './constants.js';
|
||||
|
||||
export const createDeviceChassisMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.DEVICE_CHASSIS];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createDevicePanelMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.DEVICE_PANEL];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createDevicePanelSelectedMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.DEVICE_PANEL_SELECTED];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createDriveTrayMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.DRIVE_TRAY];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createDriveTrayHandleMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.DRIVE_TRAY_HANDLE];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createLedIndicatorMaterial = (color = '#10b981', options = {}) => {
|
||||
@@ -37,32 +72,78 @@ export const createLedErrorMaterial = (options = {}) => {
|
||||
|
||||
export const createSfpPortMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.SFP_PORT];
|
||||
return <meshPhysicalMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} transparent opacity={config.opacity} {...options} />;
|
||||
return (
|
||||
<meshPhysicalMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
transparent
|
||||
opacity={config.opacity}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createRj45PortMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.RJ45_PORT];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createVentHoleMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.VENT_HOLE];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createBackPanelMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.BACK_PANEL];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} transparent opacity={config.opacity} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
transparent
|
||||
opacity={config.opacity}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createPsuModuleMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.PSU_MODULE];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createFanModuleMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.FAN_MODULE];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createConsolePortMaterial = (color = '#facc15') => {
|
||||
|
||||
@@ -3,32 +3,79 @@ import { MATERIAL_CONFIGS, MATERIAL_TYPES } from './constants.js';
|
||||
|
||||
export const createRackFrameMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.RACK_FRAME];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} envMapIntensity={config.envMapIntensity} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
envMapIntensity={config.envMapIntensity}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createRailMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.RAIL];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} envMapIntensity={config.envMapIntensity} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
envMapIntensity={config.envMapIntensity}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createRailHoleMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.RAIL_HOLE];
|
||||
return <meshBasicMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} {...options} />;
|
||||
return (
|
||||
<meshBasicMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createSidePanelMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.SIDE_PANEL];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} envMapIntensity={config.envMapIntensity} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
envMapIntensity={config.envMapIntensity}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createTopPlateMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.TOP_PLATE];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} envMapIntensity={config.envMapIntensity} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
envMapIntensity={config.envMapIntensity}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createBottomPlateMaterial = (options = {}) => {
|
||||
const config = MATERIAL_CONFIGS[MATERIAL_TYPES.BOTTOM_PLATE];
|
||||
return <meshStandardMaterial color={config.color} metalness={config.metalness} roughness={config.roughness} envMapIntensity={config.envMapIntensity} {...options} />;
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
color={config.color}
|
||||
metalness={config.metalness}
|
||||
roughness={config.roughness}
|
||||
envMapIntensity={config.envMapIntensity}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createTextLabelMaterial = (color = '#ffffff') => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { MATERIAL_CONFIGS, MATERIAL_TYPES } from './constants.js';
|
||||
|
||||
export const getMaterialConfig = (type) => {
|
||||
export const getMaterialConfig = type => {
|
||||
return MATERIAL_CONFIGS[type] || null;
|
||||
};
|
||||
|
||||
export const getMaterialType = (type) => {
|
||||
export const getMaterialType = type => {
|
||||
return MATERIAL_TYPES[type] || null;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user