echarts+vue+koa+websocket(精)

项目一览: http://jinruixiang.top:4000/

github:https://github.com/chenqi13814529300/echarts

 比一篇前后端数据综合项目教程!主要有源码

你将在这里学会:

1、koa如何使用

2、echarts如何在vue中使用,重点是思路与结构

3、websocket在前后端项目中如何通信

4、vue前端+后端的项目如何部署在linux服务器上

搭建Nginx服务器与简单使用_江河地笑的博客-CSDN博客

目录

1.koa2是什么?

2.serve后端的koa项目搭建

2.1.app.js(后端启动入口文件)

2.2.file_utils.js(读取json文件)

2.3.web_socket_service.js(后端通信配置)

3.client前端项目搭建

普及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后面的语句回去的时候按顺序执行

网站输入http://localhost:3000/

再查看控制台

 

我想你大概知道这个是什么了,这里我介绍中间件是为了后面,我们将需要中间件来更细致的处理数据,例如前后端调用接口所花的时间、设置请求头和跨域、获取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

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江河地笑

实践是检验真理的唯一标准

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值