diff --git a/frontend/src/pages/RackVisualization.jsx b/frontend/src/pages/RackVisualization.jsx index 1a95d5f..369d77b 100644 --- a/frontend/src/pages/RackVisualization.jsx +++ b/frontend/src/pages/RackVisualization.jsx @@ -23,13 +23,57 @@ style.textContent = ` @keyframes slideIn { from { opacity: 0; - transform: translateY(-50%) translateX(20px); + transform: translateY(-50%) translateX(20px) scale(0.95); } to { opacity: 1; - transform: translateY(-50%) translateX(0); + transform: translateY(-50%) translateX(0) scale(1); } } + + /* 强制设备详情面板在最顶层,超越所有3D空间 */ + .device-detail-panel { + z-index: 2147483647 !important; /* 使用最大z-index值 */ + position: fixed !important; + transform: translateY(-50%) !important; /* 强制2D变换 */ + backface-visibility: hidden !important; + -webkit-backface-visibility: hidden !important; + perspective: none !important; + transform-style: flat !important; + isolation: isolate !important; + will-change: transform !important; + contain: layout style paint !important; + overflow: visible !important; + pointer-events: auto !important; + /* 强制独立层 */ + -webkit-transform: translateY(-50%) !important; + -moz-transform: translateY(-50%) !important; + -ms-transform: translateY(-50%) !important; + -o-transform: translateY(-50%) !important; + /* 强制最高优先级 */ + filter: drop-shadow(0 0 0 transparent) !important; + box-sizing: border-box !important; + max-width: none !important; + min-width: auto !important; + } + + /* 防止页面3D空间影响设备详情面板 */ + .rack-visualization-container { + perspective: none !important; + transform-style: flat !important; + contain: layout !important; + overflow: hidden !important; + } + + /* 确保页面所有元素都不会覆盖设备详情面板 */ + html, body { + overflow-x: hidden !important; + } + + /* 强制提升页面所有容器的层级 */ + .ant-layout, .ant-card, .ant-card-head, .ant-card-body { + contain: none !important; + } `; document.head.appendChild(style); @@ -98,6 +142,9 @@ function RackVisualization() { const [error, setError] = useState(null); const [hoveredDevice, setHoveredDevice] = useState(null); const [hoverTimeout, setHoverTimeout] = useState(null); + const [backgroundImage, setBackgroundImage] = useState(null); + const [backgroundType, setBackgroundType] = useState('gradient'); // gradient or image + const [backgroundSize, setBackgroundSize] = useState('contain'); // cover, contain, auto // 获取所有机柜 const fetchRacks = async () => { @@ -416,107 +463,93 @@ function RackVisualization() { return marks; }; - // 生成左侧U数标记(从实际范围开始显示) + // 生成左侧U数标记(从U1开始显示) const generateLeftUMarks = (height) => { const marks = []; const uHeight = 18; + const rackContentHeight = height * uHeight; // 机柜内容总高度 + const containerPadding = 6; // 前面板上下padding总和 - // 从实际U位范围开始(通常是4-42U或类似范围) - const startU = Math.max(1, height - 40); // 假设机柜有40U左右,从较高位置开始显示 - - for (let i = 1; i <= height; i++) { - const actualU = startU + i - 1; - if (actualU <= 42) { // 只显示到42U - marks.push( -
- {/* U位网格背景 */} -
- {/* U位序号 */} -
- {actualU} -
+ for (let u = 1; u <= height; u++) { + // 计算从上到下的位置,考虑前面板的padding + // U1在最底部,所以从底部开始计算 + const bottomToTop = (u - 1) * uHeight; + const topPosition = rackContentHeight - bottomToTop - uHeight + containerPadding / 2; + + marks.push( +
+ {/* U位序号 */} +
+ {u}
- ); - } +
+ ); } return marks; }; - // 生成右侧U数标记(从实际范围开始显示) + // 生成右侧U数标记(从U1开始显示) const generateRightUMarks = (height) => { const marks = []; const uHeight = 18; + const rackContentHeight = height * uHeight; // 机柜内容总高度 + const containerPadding = 6; // 前面板上下padding总和 - // 从实际U位范围开始(通常是4-42U或类似范围) - const startU = Math.max(1, height - 40); // 假设机柜有40U左右,从较高位置开始显示 - - for (let i = 1; i <= height; i++) { - const actualU = startU + i - 1; - if (actualU <= 42) { // 只显示到42U - marks.push( -
- {/* U位网格背景 */} -
- {/* U位序号 */} -
- {actualU} -
+ for (let u = 1; u <= height; u++) { + // 计算从上到下的位置,考虑前面板的padding + // U1在最底部,所以从底部开始计算 + const bottomToTop = (u - 1) * uHeight; + const topPosition = rackContentHeight - bottomToTop - uHeight + containerPadding / 2; + + marks.push( +
+ {/* U位序号 */} +
+ {u}
- ); - } +
+ ); } return marks; @@ -655,6 +688,59 @@ function RackVisualization() { + + {backgroundType === 'image' && ( + + setBackgroundImage(e.target.value)} + value={backgroundImage || ''} + /> + { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + setBackgroundImage(event.target.result); + }; + reader.readAsDataURL(file); + } + }} + style={{ display: 'none' }} + id="backgroundFileInput" + /> + + + {backgroundImage && ( + + )} + + )}
@@ -673,17 +759,32 @@ function RackVisualization() { 加载机柜数据中...
) : selectedRack ? ( -
+
{loadingDevices && (
{/* 机柜主体 */} @@ -720,8 +824,34 @@ function RackVisualization() { borderRadius: '8px', boxShadow: '0 8px 32px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.2)', transformStyle: 'preserve-3d', - overflow: 'hidden' + overflow: 'visible' }}> + {/* 左侧U数标记 */} +
+ {generateLeftUMarks(selectedRack.height)} +
+ + {/* 右侧U数标记 */} +
+ {generateRightUMarks(selectedRack.height)} +
+ {/* 前面板 */}
- {/* 左侧U数标记 */} -
- {generateLeftUMarks(selectedRack.height)} -
- - {/* 右侧U数标记 */} -
- {generateRightUMarks(selectedRack.height)} -
- - {/* 原有的U数标记(内部) */} - {generateUMarks(selectedRack.height)} + {/* 移除了内侧U数标记,因为外侧已有U数显示 */} {/* 设备 */} {/* 调试信息 */} @@ -1058,20 +1163,38 @@ function RackVisualization() { {/* 设备详情面板 */} {hoveredDevice && (
{