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