使用react-grid-layout和react-full-screen实现一个可自定义和全屏展示的dashboard页面

Posted c.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用react-grid-layout和react-full-screen实现一个可自定义和全屏展示的dashboard页面相关的知识,希望对你有一定的参考价值。

文章目录

使用react-grid-layout和react-full-screen实现一个可自定义和全屏展示的dashboard页面

之前的博文中已经讲了如何使用react-grid-layoutecharts-for-react实现一个支持拖拽的自定义响应式dashboard页面, 还有使用react-sizeme解决了侧栏(抽屉)展开或隐藏时不会自适应容器大小的问题

参考:

《使用react-grid-layout和echarts-for-react实现一个支持拖拽的自定义响应式dashboard页面》

《使用react-sizeme解决react-grid-layout中侧栏(抽屉)展开或隐藏时不会自适应容器大小的问题》

接下来我们需要做的功能如下:

  1. 我们可以全局锁定和解锁dashboard中的图表组件,让其不允许拖动和缩放。
  2. 懒加载对应的图表组件显示到dashboard中
  3. 实时保存当前界面的布局到localstorage,以便下次进入页面可以展示上一次编辑的dashboard布局
  4. 实现dashboard全屏展示,这里会用到react-full-screen

具体实现代码展示

主展示页面

import React, useLayoutEffect, useState from "react";
import 'react-grid-layout/css/styles.css'
import 'react-resizable/css/styles.css'
import findIndex from "lodash";
import './dashboard.css'
import CloseOutlined, LockOutlined, QuestionCircleOutlined, UnlockOutlined from "@ant-design/icons";
import ReactGridLayout from "react-grid-layout";
import isEmpty from "lodash-es";
import withSize from 'react-sizeme';
import LazyWidget from "@/pages/Dashboard/Detail/Widget/LazyWidget";
import DashboardMenuButton from "@/pages/Dashboard/Detail/DashboardMenuButton";
import FullScreen, useFullScreenHandle from "react-full-screen";

interface DashboardWidgetInfo 
  widgetName: string,
  layout: ReactGridLayout.Layout



function DashboardGird(size: width: any) 
  const [widgets, setWidgets] = useState<DashboardWidgetInfo[]>([]);
  const handle = useFullScreenHandle();

  useLayoutEffect(() => 
    const layoutJson = localStorage.getItem("dashboard_layout");
    if (isEmpty(layoutJson)) 
      return;
    
    setWidgets(JSON.parse(layoutJson as string));
  , []);
  const getLayouts: any = () => 
    return widgets.map(item => 
      return 
        ...item.layout
      
    );
  
  const setLayoutStatic = (widget: DashboardWidgetInfo, staticFlag: boolean) => 
    const index = findIndex(widgets, (w: any) => w.widgetName === widget.widgetName);
    if (index !== -1) 
      const updateWidget = widgets[index];
      updateWidget.layout.static = staticFlag;
      widgets.splice(index, 1, ...updateWidget);
      const newWidgets = [...widgets];
      setWidgets(newWidgets);
    
  
  const lockWidget = (widget: DashboardWidgetInfo) => 
    setLayoutStatic(widget, true);
  
  const unlockWidget = (widget: DashboardWidgetInfo) => 
    setLayoutStatic(widget, false);
  
  const onRemoveWidget = (widget: DashboardWidgetInfo) => 
    const widgetIndex = findIndex(widgets, (w: any) => w.layout.i === widget.layout.i);
    if (widgetIndex !== -1) 
      widgets.splice(widgetIndex, 1);
      const newWidgets = [...widgets];
      setWidgets(newWidgets);
    
  
  const getWidgetComponent = (widgetName: string) => 
    return (<LazyWidget widgetName=widgetName/>)
  

  const createWidget = (widget: DashboardWidgetInfo) => 
    return (
        <div className='dashboard-widget-wrapper' key=widget.layout.i data-grid=widget.layout>
          <span className='dashboard-widget-header'>
            <QuestionCircleOutlined className='dashboard-widget-header-icon'/>
            widget.layout.static ? <LockOutlined className='dashboard-widget-header-icon' onClick=() => unlockWidget(widget)/> : (
                <UnlockOutlined className='dashboard-widget-header-icon' onClick=() => lockWidget(widget)/>)
            <CloseOutlined className='dashboard-widget-header-icon' onClick=() => onRemoveWidget(widget)/>
          </span>
          getWidgetComponent(widget.widgetName)
        </div>
    );
  
  const onAddWidget = () => 
    const x = (widgets.length * 3) % 12;
    const widgetName = x % 2 == 0 ? 'BarChartWidget' : 'PieChartWidget';
    const index = findIndex(widgets, (w) => w.widgetName === widgetName);
    if (index !== -1) 
      return;
    
    const newWidgets = [...widgets, 
      widgetName: widgetName,
      layout: i: widgetName, x: x, y: Infinity, w: 3, h: 2, static: false
    ] as DashboardWidgetInfo[];
    setWidgets(newWidgets);
  

  const onLayoutChange = (layouts: any[]) => 
    for (const layout of layouts) 
      const updateIndex = findIndex(widgets, (w) => w.layout.i === layout.i);
      if (updateIndex !== -1) 
        const updateWidget = widgets[updateIndex];
        updateWidget.layout = 
          ...layout,
        ;
        widgets.splice(updateIndex, 1, ...updateWidget);
      
    
    const newWidgets = [...widgets];
    setWidgets(newWidgets);
    localStorage.setItem("dashboard_layout", JSON.stringify(widgets));
  


  const lockAllWidgets = () => 
    setWidgets(widgets.map(w => 
      return 
        ...w,
        layout: 
          ...w.layout,
          static: true
        
      
    ) as DashboardWidgetInfo[]);
  

  const unlockAllWidgets = () => 
    setWidgets(widgets.map(w => 
      return 
        ...w,
        layout: 
          ...w.layout,
          static: false
        
      
    ) as DashboardWidgetInfo[]);
  

  const enterFullScreen = () => 
    handle.enter();
  


  const menuConfig = 
    addWidget: onAddWidget,
    lockAllWidgets: lockAllWidgets,
    unlockAllWidgets: unlockAllWidgets,
    enterFullScreen: enterFullScreen
  

  return (
      <FullScreen handle=handle>
        <div>
          <DashboardMenuButton ...menuConfig/>
          <ReactGridLayout
              cols=12
              rowHeight=100
              width=width
              autoSize=true
              isDraggable=true
              isResizable=true
              isBounded=true
              layout=getLayouts()
              className='layouts'
              onLayoutChange=onLayoutChange>
            widgets?.map(item => createWidget(item))
          </ReactGridLayout>
        </div>
      </FullScreen>
  );


export default withSize(refreshMode: 'debounce', refreshRate: 60)(DashboardGird);

懒加载组件

import React, useMemo from "react";
import WidgetLoadingSpin from "@/pages/Dashboard/Detail/WidgetLoadingSpin";

function LazyWidget(widgetName: any) 
  const LazyComponent = React.lazy(() => import('@/pages/Dashboard/Detail/Widget/' + widgetName));
  return useMemo(() => (<React.Suspense fallback=<WidgetLoadingSpin/>>
    <LazyComponent/>
  </React.Suspense>), [widgetName]);



export default LazyWidget

组件加载时展示的组件

import Spin from "antd";
import React from "react";
import './dashboard.css'
function WidgetLoadingSpin()

  return (
      <div className='dashboard-widget-loading'><Spin tip='Loading...'/></div>
  )



export default WidgetLoadingSpin;

dashboard菜单组件

import React, useState from "react";
import Button, Dropdown, MenuProps from "antd";
import 
  AppstoreAddOutlined,
  AppstoreOutlined,
  FullscreenOutlined,
  LockOutlined,
  RedoOutlined,
  UnlockOutlined
 from "@ant-design/icons";
import Draggable, DraggableBounds, DraggableData, DraggableEvent from 'react-draggable'
import './dashboard.css'
interface DashboardMenuProps 
  addWidget: () => void,
  lockAllWidgets: () => void,
  unlockAllWidgets: () => void,
  enterFullScreen: () => void


const DashboardMenuButton = (props: DashboardMenuProps) => 
  const [bound, setBound] = useState<DraggableBounds>(left: 0, top: 0, bottom: 0, right: 0)
  const onStart = (event: DraggableEvent, draggableData: DraggableData) => 
    const clientWidth, clientHeight = window?.document?.documentElement;
    const targetRect = document.getElementById("draggable-dashboard-menu-button")?.getBoundingClientRect();
    const rightSpaceWidth = 5;
    if (targetRect) 
      setBound(
        left: -targetRect?.left + draggableData?.x,
        right: clientWidth - (targetRect?.right - draggableData?.x) - rightSpaceWidth,
        top: -targetRect?.top + draggableData?.y,
        bottom: clientHeight - (targetRect?.bottom - draggableData?.y)
      )
    
  ;


  const items: MenuProps['items'] = [
    
      key: 'addWidget',
      label: (
          <AppstoreAddOutlined onClick=props.addWidget/>
      ),
    ,
    
      key: 'lockAll',
      label: (
          <LockOutlined onClick=props.lockAllWidgets/>
      ),
    ,
    
      key: 'unlockAll',
      label: (
          <UnlockOutlined onClick=props.unlockAllWidgets/>
      ),
    ,
    
      key: 'refreshAll',
      label: (
          <RedoOutlined/>
      ),
    ,
    
      key: 'fullScreen',
      label: (
          <FullscreenOutlined onClick=props.enterFullScreen/>
      ),
    
  ];

  return (<Draggable bounds=bound handle='.draggable-dashboard-button'
                     onStart=(event, uiData) => onStart(event, uiData)>
    <div className='dashboard-menu-button'
         id='draggable-dashboard-menu-button'>
      <Dropdown menu=items placement="topRight" arrow trigger=['click'] overlayClassName='dashboard-menu-button-dropdown'>
        <Button type="primary" icon=<AppstoreOutlined/> size='large' className='draggable-dashboard-button'/>
      </Dropdown>
    </div>
  </Draggable>)




export default DashboardMenuButton;

具体的图表组件

import React from "react";
import WidgetLoadingSpin from "@/pages/Dashboard/Detail/WidgetLoadingSpin";

const ReactEchartsLazy = React.lazy(() => import('echarts-for-react'));

function BarChartWidget() 

  const getBarChart = () => 
    return 
      tooltip使用react-sizeme解决react-grid-layout中侧栏(抽屉)展开或隐藏时不会自适应容器大小的问题

使用react-sizeme解决react-grid-layout中侧栏(抽屉)展开或隐藏时不会自适应容器大小的问题

使用react-grid-layout和echarts-for-react实现一个支持拖拽的自定义响应式dashboard页面

使用react-grid-layout和echarts-for-react实现一个支持拖拽的自定义响应式dashboard页面

react-grid-layout:可拖拽缩放网格布局插件

基于react-grid-layout实现可视化拖拽