Node.js + express + react + echarts 从零搭建数据可视化平台

最近负责搭建公司大屏可视化平台,前端用到 vue + echarts ,后端 java 以及 大数据 提供数据支持。过程中踩过许多坑,于是准备在项目上线后,自己搭建响应式数据可视化平台。

技术栈

  • react
  • node.js

第三方插件

  • express 中间件
  • echarts 图表
  • Socket.io 服务
  • WOW.js 动画

资源连接:https://github.com/zhangyongwnag/screen-visual
Demo:https://zhangyongwnag.github.io/screen-visual/build/index.html

客户端

一、初始化

create-react-app screen-visual

二、安装插件

npm i echarts react-countup wowjs react-transition-group -S
yarn add echarts react-countup wowjs react-transition-group

三、静态页面

因为我们最后要做成响应式布局,所以编写css要规范化,考虑自适应

我们决定网页的整体布局采用传统的圣杯布局,俩边固定,中间自适应

基础默认样式:这里由于用到很多transform动画,动画触发间接浏览器回流重绘,考虑到性能问题,这里开启硬件加速

* {
  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif !important;
  padding: 0;
  margin: 0;
  user-select: none; /*禁止选中*/
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  cursor: pointer; /*鼠标指针*/
  box-sizing: border-box; /*怪异盒模型*/
  font-size: 18px;
  transform: translateZ(0); /*硬件加速*/
  -webkit-transform: translateZ(0);
  -moz-transform: translateZ(0);
  -o-transform: translateZ(0);
  -ms-transform: translateZ(0);
}

考虑到自适应,我们把俩边也设为百分比

整体的布局:左边24%,中间40%,右边36%

/*最外层包裹器*/
.wrap {
  width: 100vw;
  min-width: 360px;
  height: 100vh;
  overflow: hidden;
  /*filter: blur(5px);*/
  background-color: #000;
  display: flex;
  justify-content: space-evenly;
  padding: 0.5%;
  transition: all 0.5s;
}

/*俩边*/
.section_side {
  width: 24%;
  height: 100%;
  padding-bottom: 0.5%;
  transition: all 0.5s;
  background-color: rgba(32, 43, 71, 0.6);
  -webkit-transition: border linear .2s, -webkit-box-shadow linear .5s;
  border-color: rgba(141, 39, 142, .75);
  -webkit-box-shadow: 0 0 7px rgba(87, 147, 243, 1);
}

/*中间*/
.section_middle {
  width: 40%;
  height: 100%;
  margin: 0 0.5%;
  padding-bottom: 0.5%;
  transition: all 0.5s;
  background-color: rgba(32, 43, 71, 0.6);
  -webkit-transition: border linear .2s, -webkit-box-shadow linear .5s;
  border-color: rgba(141, 39, 142, .75);
  -webkit-box-shadow: 0 0 7px rgba(87, 147, 243, 1);
}

布局

return (
	<div className='wrap'>
	       <div className='section_side'></div>
	       <div className='section_middle'></div>
	       <div className='section_side' style={{width: '36%'}}></div>
	</div>
)

我们看到基本的布局效果:
在这里插入图片描述
接下来,我们把不同的echarts图表插入到页面
在这里插入图片描述
插入不用的图表,经过这一步,我想你对echarts的图表会有一个更深的体会,更熟练的应用(数据都是模拟)


走到这里,会发现一个问题,切换视口大小时,echarts图表不会自适应切换,我们查阅文档,发现官方提供了对应的方法:resize()

于是我们在每个echarts绘制完后,主动调用echarts.resize()方法,发现他只会在绘制的时候生效,切换视口还是不会自适应
在这里插入图片描述

于是找度娘,度娘给出的意见:

在每个echarts图表绘制完后,监听窗口resize,随之调用resize()方法
在这里插入图片描述
方法的确有效果,但是我们每绘制一个echarts图表,就要监听窗口resize,代码重复

我们尝试在页面初始化监听窗口resize,并且把绘制的echarts维护到全局的变量,当窗口大小发生变化,我们就遍历调用resize()

import React, {Component} from 'react';
import echarts from 'echarts'

export default class App extends Component {
  constructor() {
    super()
    this.state = {
      echartsList: [], // echarts绘制的结果
    }
  }

componentDidMount() {
  // 监听窗口变化
  window.addEventListener('resize', this.resizeEcharts)
}

// 绘制某个echarts图表
renderUserInfoEcharts = () => {
	let dom = document.getElementById('user_info')
	let eCharts = echarts.init(dom)
	let option = [
		...
	]
	eCharts.setOption(option, true)
	let echartsList = [...this.state.echartsList, eCharts]
	this.setState({
		echartsList 
	})
}

// 重置echarts的大小
resizeEcharts = () => {
    this.state.echartsList.map(item => {
      setTimeout(() => {
        item.resize()
      }, 200)
    })
}

根据上面的方式,我们在绘制图表时配置一遍,配置完后。我们可以看到效果图,完美自适应 ( gif制作工具没充钱,看起来比较卡,实际不卡 )
在这里插入图片描述

响应式

PC布局我们已经写好了,接下来,我们加几个断点制作一个简单地响应式布局

基准断点:
< 1499px
< 1199px
< 967px
< 748px
< 479px
< 1499px
@media screen and (max-width: 1499px) {
	...
}

在这里插入图片描述

< 1199px
@media screen and (max-width: 1199px) {
	...
}

在这里插入图片描述

< 967px
@media screen and (max-width: 967px) {
	...
}

在这里插入图片描述

< 748px
@media screen and (max-width: 748px) {
	...
}

在这里插入图片描述

< 479px
@media screen and (max-width: 479px) {
	...
}

在这里插入图片描述
四、动画效果

静态页面我们已经完成了,接下来,我们给网页加些动画效果

①:首次Loading加载效果

首先,我们在初始化加载页面时,加载一个loading动画,这里我们写一个Loading加载的组件

import React, {Component} from 'react'
import '../asset/css/Loading.css'

export default class Loading extends Component {
  constructor(props){
    super(props)
  }
  render () {
    return (
      <div style={{width:'100vw',height:'100vh',backgroundColor:'#2a2a2a'}} className='common_flex'>
        <div className='loader'>Loading...</div>
      </div>
    )
  }
}

接着在App.js引入使用,

import React, {Component} from 'react';
import Loading from ' ./components/Loading.'

export default class App extends Component {
 constructor() {
 	super()
 	this.state = {
 		loading: true, // loading加载标识,默认加载中
 	}
 }
 componentDidMount() {
 	// 这里模拟加载一秒,后面我们写了服务端后,根据数据返回情况按需加载
 	setTimeout(() => {
 		this.setState({
 			loading: false
 		}, () => {
 			// 当loading状态发生变化立即执行
 		})
 	}, 1000)
 }
 render() {
 	let { loading } = this.state
 	if (loading) {
 		return (
 			<Loading/>
 		)
 	}else {
	 	return (
 			<div>主内容</div>
 		)
 	}
  }
}

Loading加载动画基本实现
在这里插入图片描述
②:元素滚动动画效果

接下来,我们给页面的每个元素加滚动动画,这里我们用到 wow.js 动画库

wowjs 动画库依赖于 animate.css 所以大部分的css都可以直接拿来使用

前面已经下载过了,直接在我们需要用到的组件引入 wowjs

import React, {Component} from 'react';
import {WOW} from ' wowjs'

export default class App extends Component {
  constructor() {
  	super()
  }
  componentDidMount() {
  	let wow = new WOW({
	    boxClass: 'wow',
	    animateClass: 'animated',
	    offset: 0,
	    mobile: true,
	    live: true
	});
	wow.init();
 }
 render() {
 	return (
 		<div className=‘wow slideInLeft’ data-wow-duration="2s" data-wow-delay="5s" data-wow-offset="10" data-wow-iteration="10">动画效果</div>
 	)
}

自定义配置

属性/方法 类型 默认值 说明
boxClass String wow 需要执行动画的元素的 class
animateClass String animated animation.css 动画的 class
offset Number 0 距离可视区域多少开始执行动画
mobile Boolean true 是否在移动端执行动画
live Boolean true 异步加载的内容是否有效

标签属性配置

属性 说明
data-wow-duration=“2s” 执行动画所需要的时间
data-wow-delay=“2s” 执行动画延迟执行的时间
data-wow-offset=“0” 距顶部多少开始执行动画
data-wow-iteration=“infinity” 执行动画的次数 infinity无限

在这里插入图片描述
③:数字滚动效果

我们利用 react-countup 插件实现数字滚动效果,他是依赖于 countup 轻量级插件

前面已经下载,直接在所需要的组件引入使用

import React, {Component} from 'react';
import countUp from 'react-countup'

export default class App extends Component {
  constructor() {
  	super()
  }
 render() {
 	return (
 		<CountUp start={0} end={parseInt(Math.random() * 10000)} suffix=' 个' duration={4} separator=','/>
 	)
}

属性配置

属性 类型 说明
start Number 开始的值
end Number 结束的值
suffix String 单位
duration Number 动画执行时间
separator String 分隔符

在这里插入图片描述

④:列表插入效果

之前的文章我们有介绍 react-transition-group 官方提供的动画库,这里就不过多介绍了

我们直接介绍用法

import React, {Component} from 'react'
import {TransitionGroup, CSSTransition} from 'react-transition-group'

export default class Table extends Component {
  constructor(props) {
    super(props)
    this.state = {
    	tableList:['1','2','3','4']
    }
  }
  render() {
  	let { tableList } = this.state
  	return (
       <TransitionGroup>
           {
                tableList.map((item, index) => (
                  <CSSTransition
                    key={index}
                    timeout={1000} //动画执行1秒
                    classNames='fade' //自定义的class名
                  >
                  	<div>{item}</div>
                  </CSSTransition>
                ))
            }
        </TransitionGroup>
  	)
  }
}

在这里插入图片描述
这里的滚动效果,利用 transform: translateY(value)

客户端OK,接下来我们写一个简单的服务端


服务端

我们利用express中间件搭配socket.io建立通信

一、安装

npm i express socket.io -S

二、搭建

socket服务配合http服务一起使用,所以必须要http监听

let express = require('express')
let app = express()
let http = require('http').Server(app)
let io = require('socket.io')(http)

//设置CORS
app.all('*',function (req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  res.header('Access-Control-Allow-Credentials','true');
  next();
});

app.get('/', (req, res) => res.send('Hello'))

// 建立socket连接
io.on('connection', socket => {
	// 发送消息标识open
   socket.emit('open', '初始化连接')
   // 当有用户关闭时,全体广播
   socket.on('disconnect', () => {
     socket.broadcast.emit('关闭')
   })
})

// 这里必须http监听 否则客户端会报404
let server = http.listen(8888, '127.0.0.1', () => {
  let host = server.address().address
  let port = server.address().port

  console.log(`Server running at http://${host}:${port}`)
})

我们启动服务端 node serve.js,可以看到,正常启动
在这里插入图片描述
访问 http://127.0.0.1:8888,可以看到,启动成功了
在这里插入图片描述


!!! 接下来,与客户端交互 !!!

客户端安装:npm i socket.io-client -S

import React, {Component} from 'react';

// 引入 socket.io-client 并建立连接 http://127.0.0.1:8888 即为node启动的服务器地址
let socket = require('socket.io-client')('http://127.0.0.1:8888')

export default class App extends Component {
  constructor() {
  	super()
  }
  componentDidMount() {
  	// 客户端用on接受消息,open是服务端设置的标识
    socket.on('open', data => {
      // 如果是首次加载
      if (this.state.loading) {
        this.setState({
          loading: false
        }, () => {
          // 渲染数据
          ...
        })
      }else {
        // 渲染数据
        ...
      }
    })
 }
 render() {
 	return (
 		<div>socket.io-client 测试</div>
 	)
}

在这里插入图片描述
前后端交互基本完毕,socket 通信


相关资源

相关文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章