Vue中Echarts实现饼图,并且通过WebSocket即时通讯更新

先看下效果图
在这里插入图片描述
先看下后台返回的数据结构是什么样子的

[
  {
    "name": "女装",
    "children": [
      {
        "name": "裙装",
        "value": 56202,
        "children": [
          {
            "name": "套装裙",
            "value": 10281
          },
          {
            "name": "A字裙",
            "value": 22331
          },
          {
            "name": "复古连衣裙",
            "value": 23590
          }
        ]
      },
      {
        "name": "女士上衣",
        "value": 42013,
        "children": [
          {
            "name": "格子衬衫",
            "value": 7896
          },
          {
            "name": "雪纺衫",
            "value": 10422
          },
          {
            "name": "polo衫",
            "value": 23695
          }
        ]
      },
      {
        "name": "外套",
        "value": 210282,
        "children": [
          {
            "name": "牛仔外套",
            "value": 87330
          },
          {
            "name": "针织外套",
            "value": 65770
          },
          {
            "name": "风衣外套",
            "value": 57182
          }
        ]
      },
      {
        "name": "裤装",
        "value": 168203,
        "children": [
          {
            "name": "工装裤",
            "value": 68203
          },
          {
            "name": "阔腿裤",
            "value": 50000
          },
          {
            "name": "牛仔裤",
            "value": 50000
          }
        ]
      },
      {
        "name": "特色类目",
        "value": 40292,
        "children": [
          {
            "name": "大码女装",
            "value": 292
          },
          {
            "name": "旗袍",
            "value": 10000
          },
          {
            "name": "礼服",
            "value": 10000
          }
        ]
      },
      {
        "name": "童装",
        "value": 20313,
        "children": [
          {
            "name": "童衣",
            "value": 5000
          },
		  {
            "name": "童裤子",
            "value": 15313
          }
        ]
      }
    ]
  },
  {
    "name": "手机数码",
    "children": [
      {
        "name": "手机",
        "value": 201023,
        "children": [
          {
            "name": "拍照手机",
            "value": 1023
          },
          {
            "name": "游戏手机",
            "value": 40000
          },
          {
            "name": "全面屏手机",
            "value": 160000
          }
        ]
      },
      {
        "name": "手机配件",
        "value": 103735,
        "children": [
          {
            "name": "手机壳",
            "value": 3735
          },
          {
            "name": "手机贴膜",
            "value": 70000
          },
          {
            "name": "创意配件",
            "value": 30000
          }
        ]
      },
      {
        "name": "摄影摄像",
        "value": 83834,
        "children": [
          {
            "name": "单反相机",
            "value": 3834
          },
          {
            "name": "微单",
            "value": 50000
          },
          {
            "name": "镜头",
            "value": 30000
          }
        ]
      },
      {
        "name": "影音娱乐",
        "value": 68384,
        "children": [
          {
            "name": "耳机/耳麦",
            "value": 30000
          },
          {
            "name": "音箱/音响",
            "value": 8384
          },
          {
            "name": "麦克风",
            "value": 30000
          }
        ]
      },
      {
        "name": "数码配件",
        "value": 45038,
        "children": [
          {
            "name": "存储卡",
            "value": 10000
          },
          {
            "name": "三脚架/云台",
            "value": 5038
          },
          {
            "name": "机身附件",
            "value": 30000
          }
        ]
      },
      {
        "name": "智能设备",
        "value": 90382,
        "children": [
          {
            "name": "智能手环",
            "value": 382
          },
          {
            "name": "智能家居",
            "value": 60000
          },
          {
            "name": "无人机",
            "value": 30000
          }
        ]
      },
      {
        "name": "其他",
        "value": 10201,
        "children": [
          {
            "name": "运营商",
            "value": 10000
          },
          {
            "name": "电子教育",
            "value": 201
          }
        ]
      }
    ]
  },
  {
    "name": "美妆护肤",
    "children": [
      {
        "name": "面部护肤",
        "value": 430291,
        "children": [
          {
            "name": "乳液/面霜",
            "value": 30291
          },
          {
            "name": "洁面",
            "value": 100000
          },
          {
            "name": "面膜",
            "value": 300000
          }
        ]
      },
      {
        "name": "彩妆",
        "value": 80284,
        "children": [
          {
            "name": "口红",
            "value": 60284
          },
          {
            "name": "粉底液",
            "value": 10000
          },
          {
            "name": "眉笔/眉粉",
            "value": 10000
          }
        ]
      },
      {
        "name": "男士护肤",
        "value": 40294,
        "children": [
          {
            "name": "洁面",
            "value": 10294
          },
          {
            "name": "剃须",
            "value": 5000
          },
          {
            "name": "护肤套装",
            "value": 25000
          }
        ]
      },
      {
        "name": "美妆工具",
        "value": 50939,
        "children": [
          {
            "name": "化妆棉",
            "value": 10939
          },
          {
            "name": "化妆刷",
            "value": 10000
          },
          {
            "name": "双眼皮贴",
            "value": 30000
          }
        ]
      },
      {
        "name": "其他",
        "value": 10921,
        "children": [
          {
            "name": "香水",
            "value": 921
          },
          {
            "name": "当季主推",
            "value": 10000
          }
        ]
      }
    ]
  }
]

好了,开始实现前端的代码

html

<div class="com-page">
<div class="com-container">
<div class="com-chart" ref="hot_ref"></div>
<span class="iconfont arr-left" @click="toLeft" :style="comStyle"
  >&#xe6ef;</span
>
<span class="iconfont arr-right" @click="toRight" :style="comStyle"
  >&#xe6ed;</span
>
<span class="cat-name" :style="comStyle">{
   
   { catName }}</span>
css
html,body,#app{
	  width: 100%;
	  height: 100%;
	  padding: 0;
	  margin: 0;
	  overflow: hidden;
	}
	.com-page {
	  width: 100%;
	  height: 100%;
	  overflow: hidden;
	}
	
.com-container {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.com-chart {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.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;
}

data

 data() {
    return {
      chartInstance: null,  //初始化echartInstance对象
      allData: null,   //接收的后台数据
      currentIndex: 0, // 当前所展示出的一级分类数据
      titleFontSize: 0,
    };
  },

methods

initChart方法

initChart() {
     //初始化echartInstance对象
     //chalk是我们定义的主题,echarts官方有案例,怎么使用可以百度一下,不喜欢可以直接删掉
      this.chartInstance = this.$echarts.init(this.$refs.hot_ref, this.theme);
      const initOption = {
        title: {
          text: "▎ 热销商品的占比",
          left: 20,
          top: 20,
        },
        legend: {
          top: "15%",
          icon: "circle",
        },
        tooltip: {
          show: true,
          formatter: (arg) => {
            // console.log(arg)
            const thirdCategory = arg.data.children;
            // 计算出所有三级分类的数值总和
            let total = 0;
            thirdCategory.forEach((item) => {
              total += item.value;
            });
            let retStr = "";
            thirdCategory.forEach((item) => {
              retStr += `
              ${item.name}:${parseInt((item.value / total) * 100) + "%"}
              <br/>
              `;
            });
            return retStr;
          },
        },
        series: [
          {
            type: "pie",
            label: {
              show: false,
            },
            emphasis: {
              label: {
                show: true,
              },
              labelLine: {
                show: false,
              },
            },
          },
        ],
      };
      this.chartInstance.setOption(initOption);
    },

getData方法

这里还是用http请求获取的数据,后面我再讲怎么用WebSocket获取我们的数据

async getData() {
      // 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
      const { data: ret } = await this.$http.get("hot");
      this.allData = ret;
      console.log(this.allData);
      this.updateChart();
    },

updateChart方法

 updateChart() {
      //处理图表需要的数据
      const legendData = this.allData[this.currentIndex].children.map(
        (item) => {
          return item.name;
        }
      );
      const seriesData = this.allData[this.currentIndex].children.map(
        (item) => {
          return {
            name: item.name,
            value: item.value,
            children: item.children,
            // 新增加children的原因是为了在tooltip中的formatter的回调函数中,来拿到这个二级分类下的三级分类数据
          };
        }
      );
      const dataOption = {
        legend: {
          data: legendData,
        },
        series: [
          {
            data: seriesData,
          },
        ],
      };
      this.chartInstance.setOption(dataOption);
    },

screenAdapter方法

//适配屏幕
screenAdapter() {
      this.titleFontSize = (this.$refs.hot_ref.offsetWidth / 100) * 3.6;
      const adapterOption = {
        title: {
          textStyle: {
            fontSize: this.titleFontSize,
          },
        },
        legend: {
          itemWidth: this.titleFontSize,
          itemHeight: this.titleFontSize,
          itemGap: this.titleFontSize / 2,
          textStyle: {
            fontSize: this.titleFontSize / 2,
          },
        },
        series: [
          {
            radius: this.titleFontSize * 4.5,
            center: ["50%", "60%"],
          },
        ],
      };
      this.chartInstance.setOption(adapterOption);
      this.chartInstance.resize();
    },

toLeft

//上一页
toLeft() {
      this.currentIndex--;
      if (this.currentIndex < 0) {
        this.currentIndex = this.allData.length - 1;
      }
      this.updateChart();
    },

toRight

//下一页
 toRight() {
      this.currentIndex++;
      if (this.currentIndex > this.allData.length - 1) {
        this.currentIndex = 0;
      }
      this.updateChart();
    },

computed

 catName() {
      if (!this.allData) {
        return "";
      } else {
        return this.allData[this.currentIndex].name;
      }
    },
    comStyle() {
      return {
        fontSize: this.titleFontSize + "px",
        color: getThemeValue(this.theme).titleColor
      };
    },

mounted

  mounted() {
    this.initChart()
    this.getData()
    window.addEventListener('resize', this.screenAdapter)
    this.screenAdapter()
  },

destroyed

  destroyed() {
    window.removeEventListener('resize', this.screenAdapter)
  },

好了,完事,下面我把如何用WebSocket获取数据说一下

封装了一个WebSocket

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 = 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
  }

  // 取消某一个回调函数
  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)
    }
  }
}

在main.js中进行连接,挂载原型

//对服务端进行连接
import SocketService from '../utils/socket_service'
SocketService.Instance.connect()
// 其他的组件  this.$socket
Vue.prototype.$socket = SocketService.Instance

然后在组件中

  created() {
    //在组件创建完成之后进行回调函数注册
    this.$socket.registerCallBack('trendData',this.getData)
  },
  
   mounted() {
    this.initChart();
    //发送数据给服务器,告诉服务器,我现在需要数据
    this.$socket.send({
      action:'getData',
      socketType:'trendData',
      chartName:'trend',
      value:''
    })
    window.addEventListener("resize", this.screenAdapter);
    this.screenAdapter();
  },
  
  destroyed() {
    window.removeEventListener("resize", this.screenAdapter);
    //取消
    this.$socket.unRegisterCallBack('trendData')
  },
  methods:{
  //res就是服务端发送给客户端的图表数据
    getData(res) {
      this.allData = res;
      this.updateChart();
    },
    }

这样就实现了后端发生变化,前端即时更新视图

至于为什么WebSocket这样封装,因为后台定了规则

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 {
        // 原封不动的将所接收到的数据转发给每一个处于连接状态的客户端
        // wss.clients // 所有客户端的连接
        wss.clients.forEach(client => {
          client.send(msg)
        })
      }
      // 由服务端往客户端发送数据
      // client.send('hello socket from backend')
    })
  })
}

有不懂的可以去我的github查看源代码,前后端都有,后端必须启动,前端才有显示,WebSocket我只配了Trend组件,其他全部一样的操作

github项目地址https://github.com/lsh555/Echarts

项目详情如下
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45389051/article/details/108890185
今日推荐