refactor(floorplan): 优化机房平面图交互与代码结构
- 重构设备悬停提示逻辑,使用画布坐标替代DOM坐标 - 提取缩放控制逻辑到独立回调函数 - 移除未使用的组件和引用 - 改进全屏切换实现方式 - 添加平面图导出功能
This commit is contained in:
@@ -16,7 +16,7 @@ const RoomSelector = ({ selectedRoomId, onRoomChange }) => {
|
|||||||
const response = await axios.get('/api/rooms', { params: { pageSize: 1000 } });
|
const response = await axios.get('/api/rooms', { params: { pageSize: 1000 } });
|
||||||
const roomList = response.data.rooms || [];
|
const roomList = response.data.rooms || [];
|
||||||
setRooms(roomList);
|
setRooms(roomList);
|
||||||
|
|
||||||
if (!hasAutoSelected.current && roomList.length > 0 && !selectedRoomId) {
|
if (!hasAutoSelected.current && roomList.length > 0 && !selectedRoomId) {
|
||||||
hasAutoSelected.current = true;
|
hasAutoSelected.current = true;
|
||||||
onRoomChange(roomList[0].roomId);
|
onRoomChange(roomList[0].roomId);
|
||||||
|
|||||||
@@ -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();
|
setTooltipPosition({
|
||||||
if (rect) {
|
x: (canvasX || 0) + 15,
|
||||||
setTooltipPosition({
|
y: (canvasY || 0) + 15,
|
||||||
x: event.clientX - rect.left + 15,
|
});
|
||||||
y: event.clientY - rect.top + 15,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setHoveredDevice(device);
|
setHoveredDevice(device);
|
||||||
setHoveredDeviceRack(rack);
|
setHoveredDeviceRack(rack);
|
||||||
} else {
|
} else {
|
||||||
@@ -155,63 +152,86 @@ const RoomFloorPlanContent = () => {
|
|||||||
}
|
}
|
||||||
}, [showDetail]);
|
}, [showDetail]);
|
||||||
|
|
||||||
if (loading) {
|
const roomName = layoutData?.room?.name || '';
|
||||||
return (
|
|
||||||
<PageContainer>
|
|
||||||
<LoadingOverlay>
|
|
||||||
<Spin size="large" />
|
|
||||||
<span>加载中...</span>
|
|
||||||
</LoadingOverlay>
|
|
||||||
</PageContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!layoutData || layoutData.racks.length === 0) {
|
const handleZoomIn = useCallback(() => {
|
||||||
return (
|
canvasRef.current?.zoomIn();
|
||||||
<PageContainer>
|
}, []);
|
||||||
<EmptyStateContainer>
|
|
||||||
<Empty
|
const handleZoomOut = useCallback(() => {
|
||||||
description={selectedRoomId ? '该机房暂无机柜数据' : '请先选择机房'}
|
canvasRef.current?.zoomOut();
|
||||||
/>
|
}, []);
|
||||||
</EmptyStateContainer>
|
|
||||||
</PageContainer>
|
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
|
<FloorPlanToolbar
|
||||||
roomName={layoutData.roomName}
|
roomName={roomName}
|
||||||
roomId={selectedRoomId}
|
roomId={selectedRoomId}
|
||||||
onRoomChange={setSelectedRoom}
|
onRoomChange={setSelectedRoom}
|
||||||
zoom={currentZoom}
|
zoom={currentZoom}
|
||||||
onZoomChange={setCurrentZoom}
|
onZoomIn={handleZoomIn}
|
||||||
|
onZoomOut={handleZoomOut}
|
||||||
|
onZoomReset={handleZoomReset}
|
||||||
onFullscreen={handleToggleFullscreen}
|
onFullscreen={handleToggleFullscreen}
|
||||||
isFullscreen={isFullscreen}
|
isFullscreen={isFullscreen}
|
||||||
onRefresh={refetch}
|
onRefresh={refetch}
|
||||||
|
onExport={handleExport}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ContentWrapper ref={containerRef}>
|
{loading ? (
|
||||||
<CanvasContainer>
|
<ContentWrapper>
|
||||||
<FloorPlanCanvas
|
<LoadingOverlay>
|
||||||
racks={layoutData.racks}
|
<Spin size="large" />
|
||||||
rooms={layoutData.rooms}
|
<span>加载中...</span>
|
||||||
selectedRoomId={selectedRoomId}
|
</LoadingOverlay>
|
||||||
zoom={currentZoom}
|
</ContentWrapper>
|
||||||
onRackClick={handleRackClick}
|
) : !layoutData || layoutData.racks.length === 0 ? (
|
||||||
onDeviceHover={handleDeviceHover}
|
<ContentWrapper>
|
||||||
/>
|
<EmptyStateContainer>
|
||||||
|
<Empty
|
||||||
{hoveredDevice && (
|
description={selectedRoomId ? '该机房暂无机柜数据' : '请先选择机房'}
|
||||||
<DeviceTooltip
|
|
||||||
device={hoveredDevice}
|
|
||||||
rack={hoveredDeviceRack}
|
|
||||||
x={tooltipPosition.x}
|
|
||||||
y={tooltipPosition.y}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</EmptyStateContainer>
|
||||||
</CanvasContainer>
|
</ContentWrapper>
|
||||||
</ContentWrapper>
|
) : (
|
||||||
|
<ContentWrapper data-floorplan-container>
|
||||||
|
<CanvasContainer>
|
||||||
|
<FloorPlanCanvas
|
||||||
|
ref={canvasRef}
|
||||||
|
room={layoutData.room}
|
||||||
|
racks={layoutData.racks}
|
||||||
|
onRackClick={handleRackClick}
|
||||||
|
onDeviceHover={handleDeviceHover}
|
||||||
|
onViewChange={handleViewChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{hoveredDevice && (
|
||||||
|
<DeviceTooltip
|
||||||
|
device={hoveredDevice}
|
||||||
|
rack={hoveredDeviceRack}
|
||||||
|
x={tooltipPosition.x}
|
||||||
|
y={tooltipPosition.y}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CanvasContainer>
|
||||||
|
</ContentWrapper>
|
||||||
|
)}
|
||||||
|
|
||||||
{detailVisible && detailRack && (
|
{detailVisible && detailRack && (
|
||||||
<RackDetailPanel
|
<RackDetailPanel
|
||||||
|
|||||||
Reference in New Issue
Block a user