refactor(floorplan): 优化机房平面图交互与代码结构

- 重构设备悬停提示逻辑,使用画布坐标替代DOM坐标
- 提取缩放控制逻辑到独立回调函数
- 移除未使用的组件和引用
- 改进全屏切换实现方式
- 添加平面图导出功能
This commit is contained in:
zhang1106
2026-05-08 13:07:27 +08:00
parent 2826a00192
commit a5505207bc
2 changed files with 80 additions and 60 deletions
+62 -42
View File
@@ -1,6 +1,5 @@
import React, { useRef, useCallback, useState, useEffect } from 'react'; import React, { useRef, useCallback, useState, useEffect } from 'react';
import { Spin, Empty, message, Button } from 'antd'; import { Spin, Empty, message } from 'antd';
import { HomeOutlined, ReloadOutlined } from '@ant-design/icons';
import useFloorPlanContext from '../hooks/floorplan/useFloorPlanContext'; import useFloorPlanContext from '../hooks/floorplan/useFloorPlanContext';
import useFloorPlanData from '../hooks/floorplan/useFloorPlanData'; import useFloorPlanData from '../hooks/floorplan/useFloorPlanData';
import { FloorPlanCanvas, FloorPlanToolbar, RackDetailPanel } from '../components/floorplan'; import { FloorPlanCanvas, FloorPlanToolbar, RackDetailPanel } from '../components/floorplan';
@@ -16,8 +15,6 @@ import {
CanvasContainer, CanvasContainer,
LoadingOverlay, LoadingOverlay,
EmptyStateContainer, EmptyStateContainer,
EmptyStateTitle,
EmptyStateSubtitle,
DeviceTooltipContainer, DeviceTooltipContainer,
DeviceTooltipHeader, DeviceTooltipHeader,
DeviceTypeIndicator, DeviceTypeIndicator,
@@ -80,13 +77,16 @@ const RoomFloorPlanContent = () => {
const { layoutData, loading, error, refetch } = useFloorPlanData(selectedRoomId); const { layoutData, loading, error, refetch } = useFloorPlanData(selectedRoomId);
const canvasRef = useRef(null); const canvasRef = useRef(null);
const containerRef = useRef(null);
const [currentZoom, setCurrentZoom] = useState(1); const [currentZoom, setCurrentZoom] = useState(1);
const [hoveredDevice, setHoveredDevice] = useState(null); const [hoveredDevice, setHoveredDevice] = useState(null);
const [hoveredDeviceRack, setHoveredDeviceRack] = useState(null); const [hoveredDeviceRack, setHoveredDeviceRack] = useState(null);
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }); const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
const [isFullscreen, setIsFullscreen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false);
const handleViewChange = useCallback(({ zoom }) => {
setCurrentZoom(zoom);
}, []);
useEffect(() => { useEffect(() => {
if (error) { if (error) {
message.error(error); message.error(error);
@@ -110,10 +110,10 @@ const RoomFloorPlanContent = () => {
}, []); }, []);
const handleToggleFullscreen = useCallback(() => { const handleToggleFullscreen = useCallback(() => {
if (!containerRef.current) return; const elem = document.querySelector('[data-floorplan-container]');
if (!elem) return;
if (!document.fullscreenElement) { if (!document.fullscreenElement) {
const elem = containerRef.current;
if (elem.requestFullscreen) { if (elem.requestFullscreen) {
elem.requestFullscreen(); elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) { } else if (elem.webkitRequestFullscreen) {
@@ -132,15 +132,12 @@ const RoomFloorPlanContent = () => {
} }
}, []); }, []);
const handleDeviceHover = useCallback((device, rack, event) => { const handleDeviceHover = useCallback((device, rack, canvasX, canvasY) => {
if (device) { if (device) {
const rect = containerRef.current?.getBoundingClientRect();
if (rect) {
setTooltipPosition({ setTooltipPosition({
x: event.clientX - rect.left + 15, x: (canvasX || 0) + 15,
y: event.clientY - rect.top + 15, y: (canvasY || 0) + 15,
}); });
}
setHoveredDevice(device); setHoveredDevice(device);
setHoveredDeviceRack(rack); setHoveredDeviceRack(rack);
} else { } else {
@@ -155,51 +152,73 @@ const RoomFloorPlanContent = () => {
} }
}, [showDetail]); }, [showDetail]);
if (loading) { const roomName = layoutData?.room?.name || '';
const handleZoomIn = useCallback(() => {
canvasRef.current?.zoomIn();
}, []);
const handleZoomOut = useCallback(() => {
canvasRef.current?.zoomOut();
}, []);
const handleZoomReset = useCallback(() => {
canvasRef.current?.zoomReset();
}, []);
const handleExport = useCallback(() => {
const dataUrl = canvasRef.current?.exportImage(roomName);
if (!dataUrl) {
message.warning('暂无数据可导出');
return;
}
const link = document.createElement('a');
link.download = `${roomName || '机房'}_平面图.png`;
link.href = dataUrl;
link.click();
}, [roomName]);
return ( return (
<PageContainer> <PageContainer>
<FloorPlanToolbar
roomName={roomName}
roomId={selectedRoomId}
onRoomChange={setSelectedRoom}
zoom={currentZoom}
onZoomIn={handleZoomIn}
onZoomOut={handleZoomOut}
onZoomReset={handleZoomReset}
onFullscreen={handleToggleFullscreen}
isFullscreen={isFullscreen}
onRefresh={refetch}
onExport={handleExport}
/>
{loading ? (
<ContentWrapper>
<LoadingOverlay> <LoadingOverlay>
<Spin size="large" /> <Spin size="large" />
<span>加载中...</span> <span>加载中...</span>
</LoadingOverlay> </LoadingOverlay>
</PageContainer> </ContentWrapper>
); ) : !layoutData || layoutData.racks.length === 0 ? (
} <ContentWrapper>
if (!layoutData || layoutData.racks.length === 0) {
return (
<PageContainer>
<EmptyStateContainer> <EmptyStateContainer>
<Empty <Empty
description={selectedRoomId ? '该机房暂无机柜数据' : '请先选择机房'} description={selectedRoomId ? '该机房暂无机柜数据' : '请先选择机房'}
/> />
</EmptyStateContainer> </EmptyStateContainer>
</PageContainer> </ContentWrapper>
); ) : (
} <ContentWrapper data-floorplan-container>
return (
<PageContainer>
<FloorPlanToolbar
roomName={layoutData.roomName}
roomId={selectedRoomId}
onRoomChange={setSelectedRoom}
zoom={currentZoom}
onZoomChange={setCurrentZoom}
onFullscreen={handleToggleFullscreen}
isFullscreen={isFullscreen}
onRefresh={refetch}
/>
<ContentWrapper ref={containerRef}>
<CanvasContainer> <CanvasContainer>
<FloorPlanCanvas <FloorPlanCanvas
ref={canvasRef}
room={layoutData.room}
racks={layoutData.racks} racks={layoutData.racks}
rooms={layoutData.rooms}
selectedRoomId={selectedRoomId}
zoom={currentZoom}
onRackClick={handleRackClick} onRackClick={handleRackClick}
onDeviceHover={handleDeviceHover} onDeviceHover={handleDeviceHover}
onViewChange={handleViewChange}
/> />
{hoveredDevice && ( {hoveredDevice && (
@@ -212,6 +231,7 @@ const RoomFloorPlanContent = () => {
)} )}
</CanvasContainer> </CanvasContainer>
</ContentWrapper> </ContentWrapper>
)}
{detailVisible && detailRack && ( {detailVisible && detailRack && (
<RackDetailPanel <RackDetailPanel