From a5505207bc3e990551d8689bf5f46b12156635ec Mon Sep 17 00:00:00 2001 From: zhang1106 <849185023@qq.com> Date: Fri, 8 May 2026 13:07:27 +0800 Subject: [PATCH] =?UTF-8?q?refactor(floorplan):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=9C=BA=E6=88=BF=E5=B9=B3=E9=9D=A2=E5=9B=BE=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E4=B8=8E=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构设备悬停提示逻辑,使用画布坐标替代DOM坐标 - 提取缩放控制逻辑到独立回调函数 - 移除未使用的组件和引用 - 改进全屏切换实现方式 - 添加平面图导出功能 --- .../floorplan/toolbar/RoomSelector.jsx | 2 +- frontend/src/pages/RoomFloorPlan.jsx | 138 ++++++++++-------- 2 files changed, 80 insertions(+), 60 deletions(-) diff --git a/frontend/src/components/floorplan/toolbar/RoomSelector.jsx b/frontend/src/components/floorplan/toolbar/RoomSelector.jsx index 079e6d6..cafd39c 100644 --- a/frontend/src/components/floorplan/toolbar/RoomSelector.jsx +++ b/frontend/src/components/floorplan/toolbar/RoomSelector.jsx @@ -16,7 +16,7 @@ const RoomSelector = ({ selectedRoomId, onRoomChange }) => { const response = await axios.get('/api/rooms', { params: { pageSize: 1000 } }); const roomList = response.data.rooms || []; setRooms(roomList); - + if (!hasAutoSelected.current && roomList.length > 0 && !selectedRoomId) { hasAutoSelected.current = true; onRoomChange(roomList[0].roomId); diff --git a/frontend/src/pages/RoomFloorPlan.jsx b/frontend/src/pages/RoomFloorPlan.jsx index df310a1..fe3d909 100644 --- a/frontend/src/pages/RoomFloorPlan.jsx +++ b/frontend/src/pages/RoomFloorPlan.jsx @@ -1,6 +1,5 @@ import React, { useRef, useCallback, useState, useEffect } from 'react'; -import { Spin, Empty, message, Button } from 'antd'; -import { HomeOutlined, ReloadOutlined } from '@ant-design/icons'; +import { Spin, Empty, message } from 'antd'; import useFloorPlanContext from '../hooks/floorplan/useFloorPlanContext'; import useFloorPlanData from '../hooks/floorplan/useFloorPlanData'; import { FloorPlanCanvas, FloorPlanToolbar, RackDetailPanel } from '../components/floorplan'; @@ -16,8 +15,6 @@ import { CanvasContainer, LoadingOverlay, EmptyStateContainer, - EmptyStateTitle, - EmptyStateSubtitle, DeviceTooltipContainer, DeviceTooltipHeader, DeviceTypeIndicator, @@ -80,13 +77,16 @@ const RoomFloorPlanContent = () => { const { layoutData, loading, error, refetch } = useFloorPlanData(selectedRoomId); const canvasRef = useRef(null); - const containerRef = useRef(null); const [currentZoom, setCurrentZoom] = useState(1); const [hoveredDevice, setHoveredDevice] = useState(null); const [hoveredDeviceRack, setHoveredDeviceRack] = useState(null); const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }); const [isFullscreen, setIsFullscreen] = useState(false); + const handleViewChange = useCallback(({ zoom }) => { + setCurrentZoom(zoom); + }, []); + useEffect(() => { if (error) { message.error(error); @@ -110,10 +110,10 @@ const RoomFloorPlanContent = () => { }, []); const handleToggleFullscreen = useCallback(() => { - if (!containerRef.current) return; + const elem = document.querySelector('[data-floorplan-container]'); + if (!elem) return; if (!document.fullscreenElement) { - const elem = containerRef.current; if (elem.requestFullscreen) { elem.requestFullscreen(); } 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) { - const rect = containerRef.current?.getBoundingClientRect(); - if (rect) { - setTooltipPosition({ - x: event.clientX - rect.left + 15, - y: event.clientY - rect.top + 15, - }); - } + setTooltipPosition({ + x: (canvasX || 0) + 15, + y: (canvasY || 0) + 15, + }); setHoveredDevice(device); setHoveredDeviceRack(rack); } else { @@ -155,63 +152,86 @@ const RoomFloorPlanContent = () => { } }, [showDetail]); - if (loading) { - return ( - - - - 加载中... - - - ); - } + const roomName = layoutData?.room?.name || ''; - if (!layoutData || layoutData.racks.length === 0) { - return ( - - - - - - ); - } + 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 ( - - - - - - {hoveredDevice && ( - + + + 加载中... + + + ) : !layoutData || layoutData.racks.length === 0 ? ( + + + - )} - - + + + ) : ( + + + + + {hoveredDevice && ( + + )} + + + )} {detailVisible && detailRack && (