项目一览: http://jinruixiang.top:4000/
github:https://github.com/chenqi13814529300/echarts
比一篇前后端数据综合项目教程!主要有源码
你将在这里学会:
1、koa如何使用
2、echarts如何在vue中使用,重点是思路与结构
3、websocket在前后端项目中如何通信
4、vue前端+后端的项目如何部署在linux服务器上
目录
2.3.web_socket_service.js(后端通信配置)
普及websocket是啥?与普通接口有啥区别?HTTP与WebSocket的区别 - 小菜鸡1枚 - 博客园
1.koa2是什么?
koa2是一个洋葱模型,从外到里渗透,出来则从内至外,有点类似于栈操作,先进后出
可以看看下面这张图片
安装koa2的环境
//在你想要目录下安装koa的运行环境
npm init -y
npm install koa
在当前目录下创建app.js,内容如下
// 1.创建koa对象
const Koa = require('koa')
const app = new Koa()
// 2.编写相应函数(中间件)
// ctx:上下文,web容器,ctx.request ctx.response
// next:下一个中间件,下一层中间件是否能得到执行,取决于next这个函数是否被调用
app.use((ctx, next) => {
console.log('1');
ctx.response.body = "hello"
next()
console.log('*1*');
})
app.use(async (ctx, next) => {
console.log('2');
next()
console.log('*2*');
})
app.use((ctx, next) => {
})
// 3.绑定端口号 3000
app.listen(3000)
node app.js
结果如下,next前面的语句进入的时候执行,next后面的语句回去的时候按顺序执行
再查看控制台
我想你大概知道这个是什么了,这里我介绍中间件是为了后面,我们将需要中间件来更细致的处理数据,例如前后端调用接口所花的时间、设置请求头和跨域、获取json这类的文件
2.serve后端的koa项目搭建
项目创建跟上面一致
我将创建如下目录与文件,我画线的需要我们手写,json文件我这里有,咱们看一下json文件啥东西,咱们要通过koa这个框架把这些json文件传输给前端(这里有人估计想问这些json文件怎么来的,我想做过大数据的人应该知道,这些json文件是通过数据集群(mysql或者hive)之类的利用java或者hadoop,spark等语言生成的json文件)
2.1.app.js(后端启动入口文件)
这里引入了websocket通信
// 服务器点入口文件
// 1.koa的对象
const koa = require('koa')
const app = new koa()
// 3.绑定端口号 8888
app.listen(8888);
// // 开启服务端的监听,监听客户端的连接
// // 当某个客户端连接成功后,对这个客户端进行message事件的监听
const webSocketService = require('./service/web_socket_service')
// 开启服务端的监听, 监听客户端的连接
// 当某一个客户端连接成功之后, 就会对这个客户端进行message事件的监听
webSocketService.listen()
2.2.file_utils.js(读取json文件)
这个很简单,也就是读取json文件而已
// 读取文件的相关方法
const fs = require('fs');
module.exports.getFileJsonData = (filePath) => {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf-8', (err, data) => {
if (err) {
// 读取文件失败
reject(err);
} else {
// 读取文件成功
resolve(data);
}
});
});
};
2.3.web_socket_service.js(后端通信配置)
这里普及一下websocket三次握手
主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;
主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;
主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。
三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。
这里是配置websocket后端通信的,前端给后端发送一个类似这样的请求体,后端根据前方发送的信息进行行为判断,如果是action==getData就获取json文件,否则就把发送来的信息返回给前端
{
action: "getData",
socketType: "rankData",
chartName: "rank",
value: "",
}
const path = require('path')
const fileUtils = require('../utils/file_utils')
const WebSocket = require('ws')
// 创建WebSocket服务端的对象, 绑定的端口号是9998
const wss = new WebSocket.Server({
port: 9998
})
// 服务端开启了监听
module.exports.listen = () => {
// 对客户端的连接事件进行监听
// 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 = ret
client.send(JSON.stringify(payload))
} else {
console.log("budong"+msg);
// 原封不动的将所接收到的数据转发给每一个处于连接状态的客户端
// wss.clients // 所有客户端的连接
wss.clients.forEach((item) => {
item.send(JSON.stringify(payload));
});
}
})
})
}
middleware文件下面三个中间件就不需要写了,因为我这次写的文章是用websocket来请求,优点:实时性更强
3.client前端项目搭建
创建项目和具体安装的依赖我就不说了,你们直接可以查看我的代表,看package.json
直接上干货吧,咱们在vue中使用echarts基本上分为三个难点
如何调用echarts这个依赖呢?
echarts的api样式怎么修改?
如何使图表自适应屏幕大小呢?
先安装
npm install echarts -S
main.js
import * as echarts from 'echarts'
Vue.prototype.$echarts = echarts
先实现如下的图表
首先:我们完成这个图表需要分三步:初始化图表、获取图表数据并更新、自适应调整
他们的方法分别是initChart()、getData()与updateChart()、screenAdapter()
这个是颜色渐变api
new this.$echarts.graphic.LinearGradient(0, 0, 1, 0, [
// 0%状态下的颜色值
{
offset: 0,
color: "#5052EE",
},
{
offset: 1,
color: "#AB6EE5",
},
]),这是对鼠标移到图表上的事件监听
this.chartInstance.on("mouseover", () => {
clearInterval(this.timerId);
});这里的自适应调整使用了
const titleFontSize = (this.$refs.seller_ref.offsetWidth / 100) * 3.6;
随着屏幕的变化titleFontSize发生变化,把这个值的赋给图表字体、图例宽高等
这里的websocket通信流程
首先在created()中注册this.$socket.registerCallBack("sellerData", this.getData);
其次在mounted()中前端对后端发送请求体
this.$socket.send({
action: "getData",
socketType: "sellerData",
chartName: "seller",
value: "",
});
然后请求体发送到后端的时候,后端进行处理返回一个回调函数也就是getData
getData(ret)携带的ret就是后端返回的数据
<!-- 商家销售横向柱状图 -->
<template>
<div class="com-container">
<div class="com-chart" ref="seller_ref"></div>
</div>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import { mapState } from "vuex";
export default {
//import引入的组件需要注入到对象中才能使用
components: {},
data() {
//这里存放数据
return {
chartInstance: null,
allData: null,
currentPage: 1,
totalPage: 0,
showData: null,
timerId: null, //定时器的标识
};
},
//监听属性 类似于data概念
computed: {
...mapState(["theme"]),
},
//监控data中的数据变化
watch: {
theme() {
// 当前图表销毁
this.chartInstance.dispose();
// 以最新的主题初始化图表对象
this.initChart();
// 屏幕适配
this.screenAdapter();
// 更新图表的展示
this.updateChart();
},
},
//方法集合
methods: {
// 初始化echartInstance对象
initChart() {
this.chartInstance = this.$echarts.init(this.$refs.seller_ref, this.theme);
// 拆分图表的option->>>>>>>1.初始化配置 2.获取数据后的配置 3.分辨率适配的配置
// ****对图表进行初始化配置的控制*****
const initOption = {
title: {
text: "|商家销售量统计",
textStyle: {
fontSize: 66,
},
left: 20,
top: 20,
},
grid: {
top: "20%",
left: "3%",
right: "6%",
bottom: "3%",
// 图表与周围的距离,如果要包含文字设置如下
containLabel: true,
},
xAxis: {
type: "value",
},
yAxis: {
type: "category",
},
// 鼠标选定后的样式
tooltip: {
trigger: "axis",
axisPointer: {
z: 0,
type: "line",
lineStyle: {
width: 66,
color: "#2d3443",
},
},
},
series: [
{
type: "bar",
barWidth: 66,
// 每条的数据
label: {
show: true,
position: "right",
textStyle: {
color: "white",
},
},
// 设置每个小柱条
itemStyle: {
barBorderRadius: [0, 33, 33, 0],
// 渐变颜色
// x1 y1 x2 y2
color: new this.$echarts.graphic.LinearGradient(0, 0, 1, 0, [
// 0%状态下的颜色值
{
offset: 0,
color: "#5052EE",
},
{
offset: 1,
color: "#AB6EE5",
},
]),
},
},
],
};
this.chartInstance.setOption(initOption);
// 对图表进行鼠标事件的监听
this.chartInstance.on("mouseover", () => {
clearInterval(this.timerId);
});
this.chartInstance.on("mouseout", () => {
this.startInterval();
});
},
// 获取服务器的数据
async getData(ret) {
// const { data: ret } = await this.$http.get("seller");
this.allData = ret;
// 从小到大 a-b
this.allData.sort((a, b) => a.value - b.value);
// 每五个元素显示一页
this.totalPage =
this.allData.length % 5 === 0
? (this.totalPage = this.allData.length / 5)
: (this.totalPage = this.allData.length / 5 + 1);
this.updateChart();
this.startInterval();
},
// 更新图表
updateChart() {
const start = (this.currentPage - 1) * 5;
const end = this.currentPage * 5;
const showData = this.allData.slice(start, end);
const sellerNames = showData.map((item) => item.name);
const sellerValues = showData.map((item) => item.value);
// ***** 获取数据后的配置****
const dataOption = {
yAxis: {
data: sellerNames,
},
series: [
{
data: sellerValues,
},
],
};
this.chartInstance.setOption(dataOption);
this.chartInstance.resize();
},
startInterval() {
if (this.timerId) {
clearInterval(this.timerId);
}
this.timerId = setInterval(() => {
this.currentPage++;
if (this.currentPage > this.totalPage) {
this.currentPage = 1;
}
this.updateChart();
}, 3000);
},
// 屏幕适配
screenAdapter() {
const titleFontSize = (this.$refs.seller_ref.offsetWidth / 100) * 3.6;
const adapterOption = {
title: {
textStyle: {
fontSize: titleFontSize,
},
},
// 鼠标选定后的样式
tooltip: {
axisPointer: {
lineStyle: {
width: titleFontSize,
},
},
},
series: [
{
barWidth: titleFontSize,
// 设置每个小柱条
itemStyle: {
barBorderRadius: [0, titleFontSize / 2, titleFontSize / 2, 0],
},
},
],
};
this.chartInstance.setOption(adapterOption);
this.chartInstance.resize();
},
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
this.$socket.registerCallBack("sellerData", this.getData);
},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {
this.initChart();
// this.getData();
this.$socket.send({
action: "getData",
socketType: "sellerData",
chartName: "seller",
value: "",
});
window.addEventListener("resize", this.screenAdapter);
// 刚开始就要调整自适应,因为一开始固定了字体大小,不调整,一开始字体太大了
this.screenAdapter();
},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {
clearInterval(this.timerId);
this.$socket.unrRegisterCallBack("sellerData");
// 在组件销毁的时候,把监听器取消掉
window.removeEventListener("resize", this.screenAdapter);
}, //生命周期 - 销毁完成
activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
/*@import url(); 引入公共css类*/
</style>
socket_service.js
这是前端封装的一个webscoket,在main里面要进行引用
import SocketService from './utils/socket_service'
SocketService.Instance.connect()
服务端发过来的数据需要进行判断,看看前端这里有没有注册回调
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) {
// 浏览器不支持websocket
return console.log('当前浏览器不支持WebSocket!');
}
this.ws = new WebSocket('ws://localhost:9998');
// 连接成功
this.ws.onopen = () => {
console.log('connect success!');
this.connected = true;
this.connectRetryCount = 0;
};
// 1.连接失败
// 2.当连接成功后,服务器关闭
this.ws.onclose = () => {
console.log('connect error!');
this.connected = false;
this.connectRetryCount++;
setTimeout(() => {
this.connect();
}, this.connectRetryCount * 500);
};
// 服务端发送过来的数据
this.ws.onmessage = (msg) => {
// 服务端发送过来的数据
const recvData = JSON.parse(msg.data);
const socketType = recvData.socketType;
// 判断回调函数是否存在
if (this.callbackMapping[socketType]) {
const action = recvData.action;
if (action === 'getData') {
const realData = 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;
}
// 取消某一个回调函数
unrRegisterCallBack(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);
}
}
}
其他的我就不说了,自己看代码应该能看得懂,不懂就看视频,传送门:电商平台数据可视化实时监控系统-Echarts-vue项目综合练习-pink老师推荐(持续更新)素材已经更新_哔哩哔哩_bilibili