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