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

news/2024/5/7 0:47:43/文章来源:https://blog.csdn.net/cckevincyh/article/details/128207264

文章目录

    • 使用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<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}/><ReactGridLayoutcols={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: {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 (<React.Suspense fallback={<WidgetLoadingSpin/>}><ReactEchartsLazyoption={getBarChart()}notMerge={true}lazyUpdate={true}style={{width: '100%', height: '100%'}}/></React.Suspense>)
}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} <br/>{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 (<React.Suspense fallback={<WidgetLoadingSpin/>}><ReactEchartsLazyoption={getPieChart()}notMerge={true}lazyUpdate={true}style={{width: '100%', height: '100%'}}/></React.Suspense>)
}export default PieChartWidget

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

Demo演示

请添加图片描述

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_231309.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【微服务】SpringCloud中OpenFeign请求处理及负载均衡流程

&#x1f496; Spring家族及微服务系列文章 ✨【微服务】SpringCloud中Ribbon的WeightedResponseTimeRule策略 ✨【微服务】SpringCloud中Ribbon的轮询(RoundRobinRule)与重试(RetryRule)策略 ✨【微服务】SpringCloud中Ribbon集成Eureka实现负载均衡 ✨【微服务】SpringCloud…

EMQ 宣布推出 LF Edge eKuiper 全新 Logo 标识

全球领先的开源物联网数据基础设施软件供应商 EMQ 映云科技宣布,即日起,将正式启用全新的 LF Edge eKuiper(以下简称 eKuiper)产品 Logo。焕然一新的 eKuiper 产品 Logo 标志着 EMQ 在不断提升品牌全球化战略之上,对于打造高性能、高可用、高安全的世界级开源软件产品的极致追求…

【1805. 字符串中不同整数的数目】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个字符串 word &#xff0c;该字符串由数字和小写英文字母组成。 请你用空格替换每个不是数字的字符。例如&#xff0c;"a123bc34d8ef34" 将会变成 " 123 34 8 34" 。注意…

面试题: LEAD 和 LAG 求每个用户的页面停留时长

我们先来看看这两个函数的语法&#xff1a; LEAD(col,n,default) OVER() 说明&#xff1a; 用于统计窗口内向下第n行的值参数1&#xff1a; 为要取值的列名参数2&#xff1a; 为向下第n行&#xff0c;默认值为1&#xff0c;这个值是固定的&#xff0c;不能动态的变化参数3&am…

深度学习-全卷积神经网络(FCN)

1. 简介 全卷积神经网络&#xff08;Fully Convolutional Networks&#xff0c;FCN&#xff09;是Jonathan Long等人于2015年在Fully Convolutional Networks for Semantic Segmentation一文中提出的用于图像语义分割的一种框架&#xff0c;是深度学习用于语义分割领域的开山之…

国际学校妈妈哭诉IB太难:中国孩子都不知道怎么答题?

听别人说考国际学校的IB体系相对简单直到我看到IB试题才知道其实IB一点都不容易特别对于中国学生有大量写论文的部分来看看IB的真题有些学生真的很难下笔不知道怎么答题啊&#xff01;商科生物 题目都是非常考验综合能力的&#xff0c;学生需要掌握很多知识点并融会贯通&#x…

cengbox2靶机(hydra爆破、公私钥免密登录)

环境准备 靶机链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;zdpr 虚拟机网络链接模式&#xff1a;桥接模式 攻击机系统&#xff1a;kali linux 2021.1 信息收集 1.arp-scan -l探测目标靶机ip 2.nmap -p- -A -T4 192.168.1.107 探测目标靶机开放端口和服务 …

Locust学习记录5-任务属性【Task】

Task 当负载测试开始时&#xff0c;将为每个模拟用户创建一个User类的实例&#xff0c;他们将开始在自己的绿色线程中运行。当这些用户运行时&#xff0c;他们选择他们执行的任务&#xff0c;休眠一段时间&#xff0c;然后选择一个新任务。 这些任务时普通的python可调用文件…

服务访问质量(QoS)介绍与技术 一

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。个人爱好: 编程&#xff0c;打篮球&#xff0c;计算机知识个人名言&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石…

K8S Pod控制器详细讲解

文章目录一、Pod控制器介绍二、ReplicaSet(RS)三、Deployment(Deploy)1.镜像更新&#xff1a;2.版本回退3.金丝雀发布/灰度发布四、Horizontal Pod Autoscaler(HPA)五、DaemonSet(DS)六、Job七、CronJob(CJ)结尾一、Pod控制器介绍 Pod是kubernetes的最小管理单元&#xff0c;在…

使用JAR签名进行代码签名

JavaArchive(JAR)包格式可用于打包Java应用程序和库。 签名的JAR文件可以选择包含来自TSA时间戳响应&#xff0c;使用RFC#3161格式。 添加JAR签名者添加JAR签名者的链接&#xff0c;SignServer中的JAR签名器称为JArchiveSigner。 要配置JArchiveSigner&#xff0c;请按照以下…

用代码画两棵圣诞树送给你【附详细代码】

大家好&#xff0c;我是宁一 代码的魔力之处在于&#xff0c;可以帮我们实现许多奇奇怪怪、有趣的想法。 比如&#xff0c;用Python的Turtle库&#xff0c;可以帮我们在电脑上画出好看的图像。 下面这张樱花图就是用Turtle库实现的。 这不圣诞节快到啦。 那么就用代码来画一…

jsp servlet mysql实现的二手车汽车管理系统项目源码附带视频指导运行教程

今天给大家演示一下由jsp servlet mysql实现的一款简单的二手车汽车管理系统&#xff0c;系统设计采用了mvc分层的模式&#xff0c;结构非常清晰&#xff0c;功能虽简单&#xff0c;但是把所有可能用到的功能都实现了&#xff0c;往上面添加功能很简单&#xff0c;直接复制代码…

计算机网络学习笔记(Ⅲ):数据链路层

目录 1 数据链路层概述 1.1 基本概念 1.2 主要功能 2 封装成帧和透明传输 2.1 封装成帧 2.2 透明传输 1.字符计数法 2.字符填充法 3.零比特填充法 4.违规编码法 3 差错控制 3.1 差错 3.1 检错编码 1.奇偶校验码 2.CRC循环冗余码 3.2 纠错编码 1.确定校验码位数…

docker安装nginx代理nacos2.1.2版本集群

目录 安装docker最新版本 创建一个docker network&#xff0c;使之固定docker局域ip docker安装mysql主从 配置挂载的my.cnf配置文件 进入mysql主数据库容器命令 登录主数据库创建用于从数据连接主数据的账号密码 输入show master status;查看master数据库状态 在从数据库…

关于JVM:内容以及流程释义

对于JVM、GC、类加载&#xff0c;很多人摸不清楚头绪&#xff0c;不知道他们之间的关系。误以为GC和类加载还有JVM区分统称垃圾回收&#xff0c;实则他们包含的东西很多&#xff0c;很细&#xff0c;完整的了解正个JVM的加载过程&#xff0c;就需要全面理解这些东西。 我认为的…

如何在 Windows 10 上启用和设置 BitLocker 加密

启用和设置 BitLocker 加密 通过控制面板启用 BitLocker通过命令提示符启用 BitLockerBitlocker 可以使用控制面板中的图形界面或在命令提示符中执行一些命令来启用。在 Windows 10 上启用 Bitlocker 非常简单,但用户通常更喜欢通过控制面板而不是命令提示符来管理 Bitlocker …

Java数据结构与Java算法学习Day05---二叉树(简略笔记记录)

目录 一、二叉树 79 1.1树的基本定义79 1.2数的相关术语 80 1.3二叉树的基本定义 81 1.4二叉查找树的创建 82 1.4.1二叉树查找树创建---插入方法&#xff08;put&#xff09; 83 1.4.2二叉树查找树创建---获取方法&#xff08;get&#xff09;84 1.4.3二叉树查找树创建…

【测试沉思录】23. 如何实现基于场景的接口自动化测试用例?

作者&#xff1a;陈爱娇 编辑&#xff1a;毕小烦 自动化本身是为了提高工作效率&#xff0c;不论选择何种框架&#xff0c;何种开发语言&#xff0c;我们最终想实现的效果&#xff0c;就是让大家用最少的代码&#xff0c;最小的投入&#xff0c;完成自动化测试的工作。 基于这…

K-Means++代码实现

K-Means代码实现 数据集 https://download.csdn.net/download/qq_43629083/87246495 import pandas as pd import numpy as np import random import math %matplotlib inline from matplotlib import pyplot as plt# 按文件名读取整个文件 data pd.read_csv(data.csv)class…