使用react-grid-layout和react-full-screen实现一个可自定义和全屏展示的dashboard页面
创始人
2024-03-21 18:13:06

文章目录

    • 使用react-grid-layout和react-full-screen实现一个可自定义和全屏展示的dashboard页面
      • 具体实现代码展示
        • 主展示页面
        • 懒加载组件
        • 组件加载时展示的组件
        • dashboard菜单组件
        • 具体的图表组件
      • Demo演示

使用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([]);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 (widgetName}/>)}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) % 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 (handle}>
...menuConfig}/>12}rowHeight={100}width={width}autoSize={true}isDraggable={true}isResizable={true}isBounded={true}layout={getLayouts()}className={'layouts'}onLayoutChange={onLayoutChange}>{widgets?.map(item => createWidget(item))}
); }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(() => (}>), [widgetName]);
}export default LazyWidget

组件加载时展示的组件

在这里插入图片描述

import {Spin} from "antd";
import React from "react";
import './dashboard.css'
function WidgetLoadingSpin(){return (
'dashboard-widget-loading'}>'Loading...'}/>
) }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({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: (props.addWidget}/>),},{key: 'lockAll',label: (props.lockAllWidgets}/>),},{key: 'unlockAll',label: (props.unlockAllWidgets}/>),},{key: 'refreshAll',label: (),},{key: 'fullScreen',label: (props.enterFullScreen}/>),}];return (bound} handle={'.draggable-dashboard-button'}onStart={(event, uiData) => onStart(event, uiData)}>
'dashboard-menu-button'}id={'draggable-dashboard-menu-button'}>{items}} placement="topRight" arrow trigger={['click']} overlayClassName={'dashboard-menu-button-dropdown'}>
)}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: {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
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

到这里我们就完成了使用react-grid-layoutreact-full-screen实现一个可自定义和全屏展示的dashboard页面啦~

Demo演示

请添加图片描述

请添加图片描述

相关内容

热门资讯

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