irpas技术客

Vue+echarts 前后端结合实现大屏数据可视化_鱼九·_数据可视化大屏项目前后端

irpas 7538

一、首先介绍一下数据可视化Echarts

数据可视化, 说白了, 就是把数据以更加直观的方式进行呈现. 那什么方式是更加直观的方式呢? 就是图表. ECharts是百度公司开源的一个使用 JavaScript 实现的开源可视化库,兼容性强,底层依赖矢量图形库 ZRender ,提供直观,交互丰富,可高度个性化定制的数据可视化图表。它的优点是:开源免费,功能丰富,移动端的优化,跨平台......

下面展示一下基本的echarts的图表,这里是 html+js 实现的效果,这里是本人初学echarts的练手项目:

可见echarts的强大,它可以使苍白无力的文字变得可视化。

二、关于后端 Koa2的使用

关于本项目,是前后端分离项目,前端使用vue+echarts,后端使用koa2,获取数据使用 axios(阿贾克斯), 项目起初是前端利用axios向后端发送数据请求,后来加以完善,转变成后端向前端推送数据。

关于koa2,是基于 Node.js 平台的Web服务器框架,利用洋葱模型的中间件,进行处理请求。

本项目利用koa2实现以下功能:

1.计算服务器处理请求的总耗时 计算出服务器对于这个请求它的所有中间件总耗时时长究竟是,我们需要计算一下 2.在响应头上加上响应内容的 mime 类型 加入mime类型, 可以让浏览器更好的来处理由服务器返回的数据. 如果响应给前端浏览器是 json 格式的数据,这时候就需要在咱们的响应头当中增加 Content- Type 它的值就是 application/json , application/json 就是 json 数据类型的 mime 类型 3.根据URL读取指定目录下的文件内容 为了简化后台服务器的代码,前端图表所要的数据, 并没有存在数据库当中,而是将存在文件当中 的,这种操作只是为了简化咱们后台的代码. 所以咱们是需要去读取某一个目录下面的文件内容 的。 每一个目标就是一个中间件需要实现的功能, 所以后台项目中需要有三个中间件

下面是三个中间件的代码逻辑:

// 处理业务逻辑的中间件,读取某一个json文件数据 const path = require('path') const fileUtils = require('../utils/file_utils') module.exports = async (ctx,next) => { // 获取请求路径,拼接路径 , 读取文件内容 //console.log(ctx.request.url); // 属于端口路径 ../data/seller.json const url = ctx.request.url let filePath = url.replace('/api','') // /seller filePath = '../data' + filePath + '.json' // 可以读取吗? filePath = path.join(__dirname,filePath) try{ const ret = await fileUtils.getFileJsonData(filePath) // 语法糖操作 ctx.response.body = ret } catch(error) { const errorMsg = { message:'读取文件失败,文件资源不存在', status:404 } ctx.response.body = JSON.stringify(errorMsg) // Json数据的转化 } //console.log(filePath); await next() // 设置响应体 通过 ctx } // 计算服务器消耗时长的中间件 module.exports = async (ctx, next) => { // 记录开始时间 const start = Date.now() // 让内层中间件得到执行 await next() // 记录结束的时间 const end = Date.now() // 打错字 // 设置响应头 X-Response-Time const duration = end - start // ctx.set 设置响应头 ctx.set('X-Response-Time', `${duration} ms`) // es6的模板字符串 } // 设置响应头的中间件 module.exports = async (ctx,next) =>{ const contentType = 'application/json; charset=utf-8' ctx.set('Content-Type', contentType) // 解决跨域的问题 设置请求头 ctx.set("Access-Control-Allow-Origin", "*") ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE") // 配置请求头的操作 await next() }

其中包括设置响应头处理跨域的方法。

三、关于 前端 vue

整个项目的架构是基于 Vue 的, 所以我们需要创建 Vue 项目, 然后在 Vue 项目中开发各个图表组件.涉及技术栈 :vue-cli、vue-router、vuex、node.js 、echarts、axios

目录结构:

前端项目准备:

1.vue-cli脚手架创建项目

2.全局echarts对象

3.axios的处理(原型链挂载)

4.单独图表组件的开发

将从这四个方面进行前端项目的编写:

图表的设计流程:

1.基本图表

2.分页动画

3.UI调整

4.分辨率适配

因为实现起来比较容易,代码就不发出来了。

下面分享关键步骤的代码:

1.main.js

import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' // 引入全局的样式文件 import './assets/css/global.less' import './assets/font/iconfont.css' // 引入字体文件 import axios from 'axios' import SocketService from './utils/socket_service' // 请求基准路径的配置 axios.defaults.baseURL = 'http://127.0.0.1:8888/api/' // 这里是进行了基础配置 并不是我们所需要的跨域解决 // 将axios挂载到Vue的原型对象上 // 在别的组件中 this.$http Vue.prototype.$http = axios // 将全局的echarts对象挂载到Vue的原型对象上 // 别的组件中 this.$echarts Vue.prototype.$echarts = window.echarts Vue.config.productionTip = false // 连接到后台WebSocket服务器 SocketService.Instacne.connect() // 把SocketService实例挂载到Vue原型上 Vue.prototype.$socket = SocketService.Instacne new Vue({ router, store, render: h => h(App) }).$mount('#app')

2.map.vue(图表)

<template> <div class="com-container" @dblclick="resetMap"> <div class="com-chart" ref="mapRef"></div> </div> </template> <script> // import { getChinaMapData, getMapData, getProvinceMapData } from '@/api/map' import { getChinaMapData, getProvinceMapData } from '@/api/map' import { getProvinceMapInfo } from '@/utils/map_utils' import { mapGetters } from 'vuex' export default { data () { return { echartInstance: null, allData: null, mapData: {} // 所获取的省份的地图矢量数据 } }, computed: { ...mapGetters(['getTheme']) // 扩展运算符 }, watch: { getTheme () { this.echartInstance.dispose() // 销毁之前的echarts实例 this.initChart() // 重新创建echarts实例 this.screenAdapter() // 重新进行屏幕适配 this.updateChart() // 重新绘制图表 } }, created () { this.$socket.registerCallBack('mapData', this.getData) // 后端向前端推送数据 }, mounted () { this.initChart() // this.getData() this.$socket.send({ // 实现多端联动的效果 action: 'getData', chartName: 'map', socketType: 'mapData', value: '' }) window.addEventListener('resize', this.screenAdapter) // 屏幕适配 this.screenAdapter() }, destroyed () { window.removeEventListener('resize', this.screenAdapter) this.$socket.unRegisterCallBack('mapData') }, methods: { async initChart () { this.echartInstance = this.$echarts.init(this.$refs.mapRef, this.getTheme) this.echartInstance.on('click', async arg => { console.log(arg.name) const provinceMapInfo = getProvinceMapInfo(arg.name) // 导出拼音 if (provinceMapInfo.key === undefined) { return 0 // bug问题 如果不存在这个省份 就直接退出 不进行数据的请求 } try { if (!this.mapData[provinceMapInfo.key]) { const res = await getProvinceMapData(provinceMapInfo.path) this.$echarts.registerMap(provinceMapInfo.key, res.data) this.mapData[provinceMapInfo.key] = res.data } const changeOption = { geo: { map: provinceMapInfo.key } } this.echartInstance.setOption(changeOption) } catch (err) { console.log(err) } }) const res = await getChinaMapData() this.$echarts.registerMap('china', res.data) const initOption = { title: { text: '▎商家分布', left: 20, top: 20 }, legend: { left: '5%', bottom: '5%', orient: 'vertical' }, geo: { type: 'map', map: 'china', top: '5%', bottom: '5%', itemStyle: { areaColor: '#2E72BF', borderColor: '#333' } } } this.echartInstance.setOption(initOption) }, // async getData () { getData (res) { // const res = await getMapData() // const legendData = res.data.map(item => item.name) // const seriesArr = res.data.map(item => { this.allData = res this.updateChart() }, updateChart () { const legendData = this.allData.map(item => item.name) const seriesArr = this.allData.map(item => { return { type: 'effectScatter', name: item.name, data: item.children, coordinateSystem: 'geo', rippleEffect: { scale: 5, brushType: 'stroke' } } }) const dataOption = { legend: { data: legendData }, series: seriesArr } this.echartInstance.setOption(dataOption) }, screenAdapter () { const titleFontSize = (this.$refs.mapRef.offsetWidth / 100) * 3.6 const adapterOption = { title: { textStyle: { fontSize: titleFontSize } }, legend: { itemWidth: titleFontSize / 2, itemHeight: titleFontSize / 2, itemGap: titleFontSize / 2, textStyle: { fontSize: titleFontSize / 2 } } } this.echartInstance.setOption(adapterOption) this.echartInstance.resize() }, resetMap () { // 双击返回 const changeOption = { geo: { map: 'china' } } this.echartInstance.setOption(changeOption) } } } </script>

3.socket约束

// 处理与websocket进行的数据 export default class SocketService { // 单利设计模式 static instance = null static get Instacne () { if (!this.instance) { this.instance = new SocketService() } return this.instance } ws = null connected = false // 发送失败之后尝试的次数 sendTryCount = 0 // 连接失败之后尝试的次数 connectTryCount = 0 // 存储回调函数 callBackMapping = {} registerCallBack = (type, callBackFunc) => { this.callBackMapping[type] = callBackFunc } unRegisterCallBack = type => { delete this.callBackMapping[type] } connect () { // 连接服务器 if (!window.WebSocket) { alert('您的浏览器不支持WebSocket') return } this.ws = new WebSocket('ws://127.0.0.1:9998') // this.ws = new WebSocket(process.env.VUE_APP_SOCKETURL) // 连接成功的事件 this.ws.onopen = () => { this.connected = true this.connectTryCount = 0 } // 连接失败的事件 this.ws.onclose = () => { console.log('连接失败,请重试...') this.connectTryCount++ this.connected = false // 失败之后尝试连接 setTimeout(() => { this.connect() }, this.connectTryCount * 500) } this.ws.onmessage = msg => { const msgObj = JSON.parse(msg.data) if (msgObj.action === 'getData') { if (msgObj.socketType) { this.callBackMapping[msgObj.socketType].call( this, JSON.parse(msgObj.data) ) } } else if (msgObj.action === 'fullScreen') { this.callBackMapping[msgObj.socketType].call(this, msgObj) } else if (msgObj.action === 'changeTheme') { this.callBackMapping[msgObj.socketType].call(this, msgObj) } } } // 发送数据给服务器 send = data => { if (this.connected) { this.ws.send(JSON.stringify(data)) } else { this.sendTryCount++ // 尝试再次发送数据 setTimeout(() => { this.send(data) }, this.sendTryCount * 500) } } }

四、项目运行结果

本项目可进行多端数据联动、支持主题切换效果

详情请关注作者gitee:https://gitee.com/hfuuwy


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #数据可视化大屏项目前后端 #说白了 #那什么方式是更加直观的方式呢 #就是图表常言道