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 && (