使用react-grid-layout和echarts-for-react实现一个支持拖拽的自定义响应式dashboard页面
创始人
2024-02-24 02:53:28

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

需求概要

在前端工作中,我们会经常遇到自定义dashboard页这样的需求。然后我想做一个能够让用户可以在面板上自由的拖拽,固定(不允许拖拽),拖拉改变大小、新增,删除组件。组件可以是各种echarts图形,也可是各种数据表格。通过各个组件的拖拽组合,从而让用户自定义需要的dashboard页。

我们直接先来看最终的效果

请添加图片描述

技术栈

那我们这里就会是用到react-grid-layoutecharts-for-react

首先echarts-for-react,顾名思义就是用来绘制echarts图表的,这里不过多解释。然后react-grid-layout 是一个网格布局系统,可以实现响应式的网格布局,并且支持分割点(breakpoints)的设置,灵活运用可以方便的实现拖拽式组件的实现。

具体使用就不多介绍了,可以直接去官网看看,例子很多也很详细:https://github.com/react-grid-layout/react-grid-layout

简单实现

下面是dashboard的主渲染入口

import React, {useState} from "react";
import 'react-grid-layout/css/styles.css'
import 'react-resizable/css/styles.css'
import {Layout, Responsive, WidthProvider} from "react-grid-layout";
import {Button} from "antd";
import {findIndex} from "lodash";
import './dashboard.css'
import WidgetLoadingSpin from "@/pages/Dashboard/Detail/WidgetLoadingSpin";
import {CloseOutlined, LockOutlined, QuestionCircleOutlined, UnlockOutlined} from "@ant-design/icons";
const BarChartWidgetLazy = React.lazy(() => import('@/pages/Dashboard/Detail/Widget/BarChartWidget'));
const PieChartWidgetLazy = React.lazy(() => import('@/pages/Dashboard/Detail/Widget/PieChartWidget'));
const ResponsiveReactGridLayout = WidthProvider(Responsive);interface DashboardWidgetInfo {widgetName: string,layout: Layout
}function DashboardGird() {const [widgets, setWidgets] = useState([]);const [currentCols, setCurrentCols] = useState(12);const getLayouts: any = () => {return 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 (}>);} else {return (}>);}}const createWidget = (widget: DashboardWidgetInfo) => {return (
'dashboard-widget-wrapper'} key={widget.layout.i} data-grid={widget.layout}>'dashboard-widget-header-icon'}/>{widget.layout.static ? 'dashboard-widget-header-icon'} onClick={() => unlockWidget(widget)}/> : ('dashboard-widget-header-icon'} onClick={() => lockWidget(widget)}/>)}'dashboard-widget-header-icon'} onClick={() => onRemoveWidget(widget)}/>{getWidgetComponent(widget.widgetName)}
);}const onAddWidget = () => {const x = (widgets.length * 3) % (currentCols);const widgetName = x % 2 == 0 ? 'BarChartWidget' : 'PieChartWidget'const newWidgets = [...widgets, {widgetName: widgetName,layout: {i: widgetName, x: x, y: Infinity, w: 3, h: 2, static: false}}] as DashboardWidgetInfo[];setWidgets(newWidgets);}const onBreakpointChange = (newBreakpoint: string, newCols: number) => {setCurrentCols(newCols);}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 (<>getLayouts()}className={'layouts'}onLayoutChange={onLayoutChange}onBreakpointChange={onBreakpointChange}>{widgets?.map(item => createWidget(item))}); }export default DashboardGird

然后接下来是自己的一些自定制化的一些图表或者表格的组件

import React from "react";
import WidgetLoadingSpin from "@/pages/Dashboard/Detail/WidgetLoadingSpin";const ReactEchartsLazy = React.lazy(() => import('echarts-for-react'));function PieChartWidget() {const getPieChart = () => {return {color: ['#3AA1FF', '#36CBCB', '#4ECB73', '#FBD338'],tooltip: {trigger: 'item',formatter: '{a} 
{b}: {c} ({d}%)'},grid: {left: '3%',right: '4%',bottom: '3%',containLabel: true},series: [{name: '消费能力',type: 'pie',radius: ['40%', '55%'],center: ['50%', '55%'],avoidLabelOverlap: true,itemStyle: {normal: {borderColor: '#FFFFFF',borderWidth: 2}},label: {normal: {show: false,},},labelLine: {normal: {show: false}},data: [{name: 'a',value: '20'}, {name: 'b',value: '40'}, {name: 'c',value: '10'}, {name: 'd',value: '10'}]}]};}return (}>getPieChart()}notMerge={true}lazyUpdate={true}style={{width: '100%', height: '100%'}}/>) }export default PieChartWidget
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: {trigger: 'axis',axisPointer: {type: 'shadow'}},grid: {left: '3%',right: '4%',bottom: '3%',containLabel: true},xAxis: [{type: 'category',data: ['2014', '2015', '2016', '2017', '2018', '2019'],axisLine: {lineStyle: {color: '#8FA3B7',//y轴颜色}},axisLabel: {show: true,textStyle: {color: '#6D6D6D',}},axisTick: {show: false}}],yAxis: [{type: 'value',splitLine: {show: false},//max: 700,splitNumber: 3,axisTick: {show: false},axisLine: {lineStyle: {color: '#8FA3B7',//y轴颜色}},axisLabel: {show: true,textStyle: {color: '#6D6D6D',}},}],series: [{name: 'a',type: 'bar',barWidth: '40%',itemStyle: {normal: {color: '#FAD610'}},stack: '信息',data: [320, 132, 101, 134, 90, 30]},{name: 'b',type: 'bar',itemStyle: {normal: {color: '#27ECCE'}},stack: '信息',data: [220, 182, 191, 234, 290, 230]},{name: 'c',type: 'bar',itemStyle: {normal: {color: '#4DB3F5'}},stack: '信息',data: [150, 132, 201, 154, 90, 130]}]};}return (}>getBarChart()}notMerge={true}lazyUpdate={true}style={{width: '100%', height: '100%'}}/>)
}export default BarChartWidget

这里用到了React.lazy,所以还需要定制一下未加载时候渲染出来的组件

import {Spin} from "antd";
import React from "react";
import './dashboard.css'
function WidgetLoadingSpin(){return (
'dashboard-widget-loading'}>'Loading...'}/>
) }export default WidgetLoadingSpin;

最后是一些简单的CSS样式

.dashboard-widget-loading {display: flex;justify-content: center;align-items: center;width: 100%;height: 100%
}.dashboard-widget-wrapper {background: white;
}.dashboard-widget-wrapper:hover {box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.9)
}.dashboard-widget-header {display: none;
}.dashboard-widget-header-icon {margin: 4px;opacity: 0.7;
}.dashboard-widget-header-icon:hover {color: #00508E;
}.dashboard-widget-wrapper:hover .dashboard-widget-header {position: absolute;right: 7px;top: 2px;cursor: pointer;z-index: 999;display: block;
}

参考

https://github.com/react-grid-layout/react-grid-layout

https://github.com/Bilif/react-drag-grid

react-grid-layout实现拖拽,网格布局

React-grid-layout 一个支持拖拽的栅格布局库

echarts-for-react

在 React 中使用 echarts-for-react / react ,如,柱状图,折线图,饼图

react-grid-layout核心功能实现

React的动态加载(lazy import)

深入理解React:懒加载(lazy)实现原理

react-grid-layout 使用说明

React-Grid-Layout

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

React 实现炫酷的可拖拽网格布局

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...