700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Code For Better 谷歌开发者之声——Vue+echarts 前后端结合实现大屏数据可视化

Code For Better 谷歌开发者之声——Vue+echarts 前后端结合实现大屏数据可视化

时间:2021-02-02 23:46:54

相关推荐

Code For Better 谷歌开发者之声——Vue+echarts 前后端结合实现大屏数据可视化

一、首先介绍一下数据可视化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.jsonconst url = ctx.request.urllet filePath = url.replace('/api','') // /sellerfilePath = '../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-Timeconst 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.$httpVue.prototype.$http = axios// 将全局的echarts对象挂载到Vue的原型对象上// 别的组件中 this.$echartsVue.prototype.$echarts = window.echartsVue.config.productionTip = false// 连接到后台WebSocket服务器SocketService.Instacne.connect()// 把SocketService实例挂载到Vue原型上Vue.prototype.$socket = SocketService.Instacnenew 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 = resthis.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.6const 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 = nullstatic get Instacne () {if (!this.instance) {this.instance = new SocketService()}return this.instance}ws = nullconnected = 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 = truethis.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)}}}

四、项目运行结果

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

CODE FOR BETTER 唤醒代码的力量!!!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。