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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于react-grid-layout实现可视化拖拽相关的知识,希望对你有一定的参考价值。

参考技术A

做前端的小伙伴们可能会经常遇到做一个自定义dashboard这样的需求。
那么什么是自定义dashboard呢?自定义dashboard其实就是一个自定义面板,用户能够在面板上自由的拖拽,新增,删除组件。组件可以是各种echarts图形,也可是各种数据表格。通过各个组件的拖拽组合,从而让用户自定义想要看到的面板。

demo地址
源码地址

首先,在js文件中引入WidthProvider和Responsive组件,并且实例化响应式拖拽组件。
其次,在css文件中引入插件的样式。

在React的render方法中渲染可拖拽布局。ResponsiveReactGridLayout组件有多个属性,这里举几个比较重要的说明一下:

通过generateDOM函数生成布局中的组件,首先先遍历组件数组,通过每个组件的类型判断生产柱状图组件,折线组件,还是饼图组件。每个组件必须定义一个全局唯一的key值。data-grid为每一个组件绑定了其属性。下面会介绍具体的data-grid属性。

每个组件属性如下:

感谢支持。若不足之处,欢迎大家指出,共勉。

如果觉得不错,记得 点赞,谢谢大家 😂

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

文章目录

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

前提概要

在上一篇博文中,我们讲到了使用react-grid-layoutecharts-for-react实现一个自定义的dashboar页面
《使用react-grid-layout和echarts-for-react实现一个支持拖拽的自定义响应式dashboard页面》

问题代码

import React, useState from "react";
import 'react-grid-layout/css/styles.css'
import 'react-resizable/css/styles.css'
import Button from "antd";
import findIndex, uniqueId from "lodash";
import './dashboard.css'
import WidgetLoadingSpin from "@/pages/Dashboard/Detail/WidgetLoadingSpin";
import CloseOutlined, LockOutlined, QuestionCircleOutlined, UnlockOutlined from "@ant-design/icons";
import RGL, Layout, WidthProvider from "react-grid-layout";
import cloneDeep from "lodash-es";
const BarChartWidgetLazy = React.lazy(() => import('@/pages/Dashboard/Detail/Widget/BarChartWidget'));
const PieChartWidgetLazy = React.lazy(() => import('@/pages/Dashboard/Detail/Widget/PieChartWidget'));
const ReactGridLayout = WidthProvider(RGL);
interface DashboardWidgetInfo 
  widgetName: string,
  layout: Layout

function DashboardGird() 
  const [widgets, setWidgets] = useState<DashboardWidgetInfo[]>([]);
  const getLayouts: any = () => 
    return cloneDeep(widgets.map(item => 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) => 
    if (widgetName === 'PieChartWidget')  //可以改成策略
      return (<React.Suspense fallback=<WidgetLoadingSpin/>>
        <PieChartWidgetLazy/>
      </React.Suspense>);
     else 
      return (<React.Suspense fallback=<WidgetLoadingSpin/>>
        <BarChartWidgetLazy/>
      </React.Suspense>);
    
  
  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 newWidgets = [...widgets, 
      widgetName: widgetName,
      layout: i: uniqueId(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);
  
  return (
    <>
      <Button onClick=onAddWidget>add widget</Button>
      <ReactGridLayout
        cols=12
        rowHeight=100
        autoSize=true
        isDraggable=true
        isResizable=true
        isBounded=true
        layout=getLayouts()
        className='layouts'
        onLayoutChange=onLayoutChange>
        widgets?.map(item => createWidget(item))
      </ReactGridLayout>
    </>
  );

export default DashboardGird

最终效果可以看到,当我们侧栏(抽屉)移动时并不会自动帮我们调整react-grid-layout中元素的占位,也就是在最右边我们可以看到,当侧栏(抽屉)隐藏的时候,最右边是有空白的,并不会自适应填充。

那其实在官网中给出了解决方案:https://github.com/react-grid-layout/react-grid-layout

Have a more complicated layout? WidthProvider is very simple and only listens to window ‘resize’ events. If you need more power and flexibility, try the SizeMe React HOC as an alternative to WidthProvider.

react-grid-layout中提供的WidthProvider只是很简单的监听了浏览器窗口’resize’的事件,根本没办法监听到当前容器大小更变的事件,所以我们需要react-sizeme来帮助我们知道当前容器的大小

所以我们需要首先npm install react-sizeme,然后我们就可以使用了。这里就不过多介绍如何使用,直接给出解决的代码

解决代码

import React, useState from "react";
import 'react-grid-layout/css/styles.css'
import 'react-resizable/css/styles.css'
import Button from "antd";
import findIndex, uniqueId from "lodash";
import './dashboard.css'
import WidgetLoadingSpin from "@/pages/Dashboard/Detail/WidgetLoadingSpin";
import CloseOutlined, LockOutlined, QuestionCircleOutlined, UnlockOutlined from "@ant-design/icons";
import ReactGridLayout from "react-grid-layout";
import cloneDeep from "lodash-es";
import withSize from 'react-sizeme';
const BarChartWidgetLazy = React.lazy(() => import('@/pages/Dashboard/Detail/Widget/BarChartWidget'));
const PieChartWidgetLazy = React.lazy(() => import('@/pages/Dashboard/Detail/Widget/PieChartWidget'));
interface DashboardWidgetInfo 
  widgetName: string,
  layout: ReactGridLayout.Layout

function DashboardGird( size:  width  : any) 
  const [widgets, setWidgets] = useState<DashboardWidgetInfo[]>([]);
  const getLayouts: any = () => 
    return cloneDeep(widgets.map(item => 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) => 
    if (widgetName === 'PieChartWidget')  //可以改成策略
      return (<React.Suspense fallback=<WidgetLoadingSpin/>>
        <PieChartWidgetLazy/>
      </React.Suspense>);
     else 
      return (<React.Suspense fallback=<WidgetLoadingSpin/>>
        <BarChartWidgetLazy/>
      </React.Suspense>);
    
  
  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 newWidgets = [...widgets, 
      widgetName: widgetName,
      layout: i: uniqueId(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);
  
  return (
    <div>
      <Button onClick=onAddWidget>add widget</Button>
      <ReactGridLayout
        cols=12
        rowHeight=100
        width=width
        autoSize=true
        isDraggable=true
        isResizable=true
        isBounded=true
        layout=getLayouts()
        className='layouts'
        onLayoutChange=onLayoutChange>
        widgets?.map以上是关于基于react-grid-layout实现可视化拖拽的主要内容,如果未能解决你的问题,请参考以下文章

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

基于 SpringBoot + Vue 实现的可视化拖拽编辑的大屏项目

angularjs,vue之类的框架如何实现可视化拖拽室组件开发,效率比手写高很多倍?

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

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

一个基于Bootstrap实现的HMTL可视化编辑工具