前端开发|数据可视化大屏-Vue项目

一、前端项目准备
1.vue-cli 搭建项目

npm install @vue/cli -g(一台电脑只执行一次即可)
vue create 项目名
选 (下键选择 空格确认)
:Manually select features手动选择某些特性
:RouterVuex CSS
:2.0 x
Use history mode for router?是否使用路由:no
CSS预处理语言:Less
ESLint 配置模式 - 标准模式:ESLint + Standard config
何时出现ESLint提示- 保存时:Lint on save
配置问件处理:In dedicated config files单独存放
Save this as a preset for future projects? (y/N):no
cd 项目名
npm run serve
2.删除无关代码
①App.vue文件中:

②components文件夹、 views文件夹清空
③router/index.js文件中 剩余内容:
import Vue from 'vue' import VueRouter from 'vue-router'Vue.use(VueRouter)const routes = [] const router = new VueRouter({ routes })export default router

3.静态资源的引入
图片img、第三方包lib、地图map、主题theme放在public文件夹下
4.项目的基本配置
根目录下创建vue.config.js文件:
module.exports = { devServer: { port: 8999, //前端 端口号访问网址(http://localhost:8999/#/具体页面路由) open: true //自动打开浏览器 } };

5.全局echarts对象的挂载
public/index.html文件中:

src/main.js文件中:
// 将全局的echarts对象挂载到Vue的原型对象上 // 别的组件中 使用this.$echarts Vue.prototype.$echarts = window.echarts;

6.axios的封装与挂载
下载axios模块 :npm install axios
src/main.js文件中:
import axios from "axios"; //引入axios // 请求基准路径的配置接口前缀 axios.defaults.baseURL = "http://127.0.0.1:8888/api/"; // 将axios挂载到Vue的原型对象上在别的组件中 使用this.$http发起ajax请求 Vue.prototype.$http = axios;

二、单独图表组件的开发
1.横向柱状图 Seller.vue
①组件结构和布局结构的设计
router/index.js文件中:(定义路由)
import SellerPage from '@/views/SellerPage' const routes = [{ path: '/sellerpage', component: SellerPage }]//路由规则

App.vue文件中:

父- views/SellerPage.vue文件中:(创建SellerPage.vue文件)

子- components/Seller.vue文件中:(创建Seller.vue文件,功能主要写在这个文件里面)

assets/css/global.less文件中:全局样式
html, body, #app { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; } .com-page { width: 100%; height: 100%; overflow: hidden; } .com-container { width: 100%; height: 100%; overflow: hidden; } .com-chart { width: 100%; height: 900px; //注意 overflow: hidden; } canvas { border-radius: 20px; // 全局样式圆角 } .com-container { position: relative; }

main.js文件中:
// 引入全局的样式文件 import "./assets/css/global.less";

public/index.html文件中: 声明主题

components/Seller.vue文件中:总的代码

2.折线图:Trend.vue
main.js文件中:
import './assets/font/iconfont.css'//引入字体icon的样式

router/index.js文件中:
import MapPage from '@/views/MapPage'const routes = [ { path: '/sellerpage', component: SellerPage }, { path: '/trendpage', component: TrendPage }, ]

views/TrendPage.vue文件中:

componens/Trend.vue文件中:总的代码
.title { position: absolute; left: 20px; top: 20px; z-index: 10; color: white; .title-icon { margin-left: 10px; cursor: pointer; } .select-con { background-color: #222733; } }

3.地图+散点图
router/index.js文件中:
import MapPage from '@/views/MapPage'const routes = [ { path: '/sellerpage', component: SellerPage }, { path: '/trendpage', component: TrendPage }, { path: '/mappage', component: MapPage } ]

views/MapPage.vue文件中:

componens/Map.vue文件中:总的代码

4.柱状图
router/index.js文件中:
import Vue from 'vue' import VueRouter from 'vue-router'import TrendPage from '@/views/TrendPage' import SellerPage from '@/views/SellerPage' import MapPage from '@/views/MapPage' import RankPage from '@/views/RankPage'Vue.use(VueRouter)const routes = [ { path: '/sellerpage', component: SellerPage }, { path: '/trendpage', component: TrendPage }, { path: '/mappage', component: MapPage }, { path: '/rankpage', component: RankPage }]const router = new VueRouter({ routes })export default router

views/RankPage.vue文件中:

componens/Rank.vue文件中:总的代码

5.饼图
router/index.js文件中:
import Vue from 'vue' import VueRouter from 'vue-router'import TrendPage from '@/views/TrendPage' import SellerPage from '@/views/SellerPage' import MapPage from '@/views/MapPage' import RankPage from '@/views/RankPage' import HotPage from '@/views/HotPage'Vue.use(VueRouter)const routes = [ { path: '/sellerpage', component: SellerPage }, { path: '/trendpage', component: TrendPage }, { path: '/mappage', component: MapPage }, { path: '/rankpage', component: RankPage }, { path: '/hotpage', component: HotPage }]const router = new VueRouter({ routes })export default router

views/HotPage.vue文件中:

componens/Hot.vue文件中:总的代码
.arr-left { position: absolute; left: 10%; top: 50%; transform: translateY(-50%); cursor: pointer; color: white; } .arr-right { position: absolute; right: 10%; top: 50%; transform: translateY(-50%); cursor: pointer; color: white; } .cat-name { position: absolute; left: 80%; bottom: 20px; color: white; }

6.循环饼图
router/index.js文件中:
import Vue from 'vue' import VueRouter from 'vue-router'import TrendPage from '@/views/TrendPage' import SellerPage from '@/views/SellerPage' import MapPage from '@/views/MapPage' import RankPage from '@/views/RankPage' import HotPage from '@/views/HotPage' import StockPage from '@/views/StockPage'Vue.use(VueRouter)const routes = [ { path: '/sellerpage', component: SellerPage }, { path: '/trendpage', component: TrendPage }, { path: '/mappage', component: MapPage }, { path: '/rankpage', component: RankPage }, { path: '/hotpage', component: HotPage }, { path: '/stockpage', component: StockPage }]const router = new VueRouter({ routes })export default router

views/StockPage.vue文件中:

componens/Stock.vue文件中:总的代码

三、WebSocket的引入
1.后端 app.js文件中:
const webSocketService = require('./service/web_socket_service') // 开启服务端的监听, 监听客户端的连接 // 当某一个客户端连接成功之后, 就会对这个客户端进行message事件的监听 webSocketService.listen()

src/utils/file_utils.js文件中:
// 读取文件的工具方法 const fs = require("fs"); module.exports.getFileJsonData = https://www.it610.com/article/filePath => { //return "你好"; return new Promise((resolve, reject) => { fs.readFile(filePath, "utf-8", (error, data) => { if (error) { reject(error); } else { resolve(data); } }); }); };

src/service/web_socket_service.js文件中:
const path = require('path') const fileUtils = require('../utils/file_utils') //WebSocket的引入 //下载插件npm i ws -S const WebSocket = require('ws') // 创建WebSocket服务端的对象, 绑定的端口号是9998 const wss = new WebSocket.Server({ port: 9998 })// 服务端开启了监听 module.exports.listen = () => {// 对客户端的连接事件进行connection事件的监听 // client:代表的是客户端的连接socket对象 wss.on('connection', client => { console.log('有客户端连接成功了...')// 对客户端的连接对象进行message事件的监听 // msg: 由客户端发给服务端的数据 client.on('message', async msg => { console.log('客户端发送数据给服务端了: ' + msg) let payload = JSON.parse(msg) const action = payload.action if (action === 'getData') { let filePath = '../data/' + payload.chartName + '.json' // payload.chartName // trend seller map rank hot stock filePath = path.join(__dirname, filePath) const ret = await fileUtils.getFileJsonData(filePath) // 需要在服务端获取到数据的基础之上, 增加一个data的字段 // data所对应的值,就是某个json文件的内容 payload.data = https://www.it610.com/article/ret client.send(JSON.stringify(payload)) } else { // 原封不动的将所接收到的数据转发给每一个处于连接状态的客户端 // wss.clients // 所有客户端的连接 wss.clients.forEach(client => { client.send(msg) }) }// 由服务端往客户端发送数据 // client.send('hello socket from backend') }) }) }

2.前端
src/utils/socket_service.js文件中:
export default class SocketService { /** * 单例 */ static instance = null static get Instance () { if (!this.instance) { this.instance = new SocketService() } return this.instance } // 和服务端连接的socket对象 ws = null // 存储回调函数 callBackMapping = {} // 标识是否连接成功 connected = false // 记录重试的次数 sendRetryCount = 0 // 重新连接尝试的次数 connectRetryCount = 0 //定义连接服务器的方法 connect () { // 连接服务器 if (!window.WebSocket) { return console.log('您的浏览器不支持WebSocket') } this.ws = new WebSocket('ws://localhost:9998') // 连接成功的事件 this.ws.onopen = () => { console.log('连接服务端成功了') this.connected = true // 重置重新连接的次数 this.connectRetryCount = 0 } // 1.连接服务端失败 // 2.当连接成功之后, 服务器关闭的情况 this.ws.onclose = () => { console.log('连接服务端失败') this.connected = false this.connectRetryCount++ setTimeout(() => { this.connect() }, 500 * this.connectRetryCount) } // 得到服务端发送过来的数据 this.ws.onmessage = msg => { console.log('从服务端获取到了数据') // 真正服务端发送过来的原始数据时在msg中的data字段 // console.log(msg.data) const recvData = https://www.it610.com/article/JSON.parse(msg.data) const socketType = recvData.socketType // 判断回调函数是否存在 if (this.callBackMapping[socketType]) { const action = recvData.action if (action ==='getData') { const realData = https://www.it610.com/article/JSON.parse(recvData.data) this.callBackMapping[socketType].call(this, realData) } else if (action ==='fullScreen') { this.callBackMapping[socketType].call(this, recvData) } else if (action === 'themeChange') { this.callBackMapping[socketType].call(this, recvData) } } } } // 回调函数的注册 registerCallBack (socketType, callBack) { this.callBackMapping[socketType] = callBack } // 取消某一个回调函数 unRegisterCallBack (socketType) { this.callBackMapping[socketType] = null } // 发送数据的方法 send (data) { // 判断此时此刻有没有连接成功 if (this.connected) { this.sendRetryCount = 0 this.ws.send(JSON.stringify(data)) } else { this.sendRetryCount++ setTimeout(() => { this.send(data) }, this.sendRetryCount * 500) } } }

src/main.js文件中:
import SocketService from '@/utils/socket_service' // 对服务端进行websocket的连接 SocketService.Instance.connect() // 其他的组件this.$socket Vue.prototype.$socket = SocketService.Instance

componens/Seller.vue文件中:其他组件一样
created () { // 在组件创建完成之后 进行回调函数的注册 this.$socket.registerCallBack('sellerData', this.getData) }, mounted () { // this.getData() this.$socket.send({ action: 'getData', socketType: 'sellerData', chartName: 'seller', value: '' }) }, destroyed () { this.$socket.unRegisterCallBack('sellerData') },methods:{ // 获取服务器的数据 getData (ret) { // const { data: ret } = await this.$http.get('seller') this.allData = https://www.it610.com/article/ret }, }

四、细节处置
1.组件合并
src/router/index.js文件中:
import Vue from 'vue' import VueRouter from 'vue-router' import ScreenPage from '@/views/ScreenPage'Vue.use(VueRouter)const routes = [ { path: '/', redirect: '/screen' }, { path: '/screen', component: ScreenPage } ] const router = new VueRouter({ routes }) export default router

src/views/ScreenPage.vue文件中:组件 -导入注册引用
// 全屏样式的定义 .fullscreen { position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; margin: 0 !important; z-index: 100; }.screen-container { width: 100%; height: 100%; padding: 0 20px; background-color: #161522; color: #fff; box-sizing: border-box; } .screen-header { width: 100%; height: 64px; font-size: 20px; position: relative; > div { img { width: 100%; } } .title { position: absolute; left: 50%; top: 50%; font-size: 20px; transform: translate(-50%, -50%); } .title-right { display: flex; align-items: center; position: absolute; right: 0px; top: 50%; transform: translateY(-80%); } .qiehuan { width: 28px; height: 21px; cursor: pointer; } .datetime { font-size: 15px; margin-left: 10px; } .logo { position: absolute; left: 0px; top: 50%; transform: translateY(-80%); img { height: 35px; width: 128px; } } } .screen-body { width: 100%; height: 100%; display: flex; margin-top: 10px; .screen-left { height: 100%; width: 27.6%; #left-top { height: 53%; position: relative; } #left-bottom { height: 31%; margin-top: 25px; position: relative; } } .screen-middle { height: 100%; width: 41.5%; margin-left: 1.6%; margin-right: 1.6%; #middle-top { width: 100%; height: 56%; position: relative; } #middle-bottom { margin-top: 25px; width: 100%; height: 28%; position: relative; } } .screen-right { height: 100%; width: 27.6%; #right-top { height: 46%; position: relative; } #right-bottom { height: 38%; margin-top: 25px; position: relative; } } } .resize { position: absolute; right: 20px; top: 20px; cursor: pointer; }

2.主题切换
①图表主题
src/store/index.js文件中:数据存储
import Vue from 'vue' import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({ state: { theme: 'chalk' }, mutations: { changeTheme (state) { if (state.theme === 'chalk') { state.theme = 'vintage' } else { state.theme = 'chalk' } } }, actions: { }, modules: { } })

public/static/index.html文件中:

src/views/ScreenPage.vue文件中:
methods:{ handleChangeTheme () { // 修改VueX中数据 this.$store.commit('changeTheme') } }

components/Seller.vue文件中:其他组件同样
import { mapState } from 'vuex' computed: { ...mapState(['theme']) }, watch: { theme () { console.log('主题切换了') this.chartInstance.dispose() // 销毁当前的图表 this.initChart() // 重新以最新的主题名称初始化图表对象 this.screenAdapter() // 完成屏幕的适配 this.updateChart() // 更新图表的展示 } }, methods:{ initChart () { this.chartInstance = this.$echarts.init(this.$refs.hot_ref, this.theme) } }

②特殊html的主题:
src/utils/theme_utils.js文件中:
const theme = { chalk: { backgroundColor: '#161522', // 背景颜色 titleColor: '#ffffff', // 标题的文字颜色 logoSrc: 'logo_dark.png', // 左上角logo的图标路径 themeSrc: 'qiehuan_dark.png', // 切换主题按钮的图片路径 headerBorderSrc: 'header_border_dark.png'// 页面顶部的边框图片 }, vintage: { backgroundColor: '#eeeeee', // 背景颜色 titleColor: '#000000',// 标题的文字颜色 logoSrc: 'logo_light2.png',// 左上角logo的图标路径 themeSrc: 'qiehuan_light.png',// 切换主题按钮的图片路径 headerBorderSrc: 'header_border_light.png' // 页面顶部的边框图片 } }export function getThemeValue (themeName) { return theme[themeName] }

components/Hot.vue文件中:其他组件同样
import { getThemeValue } from '@/utils/theme_utils' computed: { comStyle () { return { color: getThemeValue(this.theme).titleColor } }, },

src/views/ScreenPage.vue文件中:
前端开发|数据可视化大屏-Vue项目
文章图片
前端开发|数据可视化大屏-Vue项目
文章图片
import { mapState } from 'vuex' import { getThemeValue } from '@/utils/theme_utils' computed: { headerSrc () { return '/static/img/' + getThemeValue(this.theme).headerBorderSrc }, themeSrc () { return '/static/img/' + getThemeValue(this.theme).themeSrc }, containerStyle () { return { backgroundColor: getThemeValue(this.theme).backgroundColor, color: getThemeValue(this.theme).titleColor } }, ...mapState(['theme']) }

【前端开发|数据可视化大屏-Vue项目】

    推荐阅读