先看下效果图
先看下后台返回的数据结构是什么样子的
[
{
"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"
></span
>
<span class="iconfont arr-right" @click="toRight" :style="comStyle"
></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
扫描二维码关注公众号,回复:
12443344 查看本文章

//上一页
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
项目详情如下