一、前端项目准备
1.vue-cli 搭建项目
npm install @vue/cli -g(一台电脑只执行一次即可)2.删除无关代码
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
①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文件中:总的代码
//ref更好的获取dom
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文件中:总的代码
{{ '▎ ' + showTitle }}
{{ item.text }}.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文件中:总的代码
{{ catName }}.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文件中:组件 -导入注册引用
文章图片
电商平台实时监控系统
文章图片
{{timeComput}} // 全屏样式的定义
.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文件中:
文章图片
文章图片
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项目】
推荐阅读
- 大屏云|数据可视化大屏 - 大屏云极简使用手册
- chrome|Chrome速度无人能敌(Safari也甘拜下风)
- Leetcode542(矩阵(广度遍历解法))
- 17个实用的JavaScript数组和对象的方法
- js循环
- vue input 验证输入内容为小数点后两位
- ECMAScript|js中对象和数组的浅拷贝与深拷贝(ES6、前端面试常用)
- 面试|前端面试——深拷贝与浅拷贝的区别
- 一些零碎代码|几十行代码,打造一款JavaScript便签系统