先看下效果图
先看下后台返回的数据结构是什么样子的
{
"map": {
"title": "地区销量趋势",
"base": 310,
"unit": "万",
"data": [{
"name": "上海",
"data": ["155.13","154.65","171.46","164.38","237.23","300.65","240.29","232.07","193.31","136.70","48.64","90.20"]
}, {
"name": "北京",
"data": ["86.25","33.80","145.58","21.79","176.09","132.41","291.05","191.89","151.54","94.25","141.75","157.14"]
}, {
"name": "深圳",
"data": ["143.94","186.29","183.64","251.48","195.48","152.16","52.47","184.12","203.79","39.16","56.37","161.64"]
}, {
"name": "广州",
"data": ["57.60","77.61","307.24","165.05","175.41","276.88","269.04","296.11","105.31","283.39","134.08","265.38"]
}, {
"name": "重庆",
"data": ["200.82","215.56","249.80","222.67","216.98","60.12","309.68","273.35","150.99","251.97","26.15","186.99"]
}]
},
"seller": {
"title": "商家销量趋势",
"base": 120,
"unit": "万",
"data": [{
"name": "商家1",
"data": ["33.00","86.07","28.77","34.29","102.45","0.30","50.50","21.70","25.41","25.71","66.90","63.29"]
}, {
"name": "商家2",
"data": ["12.83","102.42","37.37","95.55","45.45","112.72","113.53","106.41","75.67","113.91", "37.32", "28.04"]
}, {
"name": "商家3",
"data": ["73.54","40.92","89.81","113.41","76.34","107.15","55.61","0.33","106.29","78.30","98.05","38.67"]
}, {
"name": "商家4",
"data": ["47.19","73.57","44.60","84.03","62.82","15.65","64.72","88.98","29.25","5.41","79.11","118.46"]
}, {
"name": "商家5",
"data": ["74.84","116.45","107.69","11.03","17.31","42.22","97.60","108.64","43.87","110.65","5.96","38.41"]
}]
},
"commodity": {
"title": "商品销量趋势",
"base": 50,
"unit": "万",
"data": [{
"name": "女装",
"data": ["47.71","13.34","19.30","7.93","41.93","23.01","22.63","26.91","0.62","39.23","48.74","29.48"]
}, {
"name": "手机数码",
"data": ["46.66","46.52","23.65","1.73","44.26","47.07","17.86","40.20","3.78","31.46","28.01","8.63"]
}, {
"name": "男装",
"data": ["26.98","30.71","42.59","29.50","26.86","17.65","30.15","15.85","9.28","30.20","32.35","34.46"]
}, {
"name": "大家电",
"data": ["20.26","46.23","43.84","46.75","28.29","32.36","45.30","16.73","40.40","45.07","29.86","41.92"]
}, {
"name": "美妆护肤",
"data": ["7.58","23.66","39.78","30.20","25.72","36.20","47.55","35.39","27.85","37.56","16.91", "3.91"]
}]
},
"common": {
"month": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]
},
"type": [{
"key": "map",
"text": "地区销量趋势"
}, {
"key": "seller",
"text": "商家销量趋势"
}, {
"key": "commodity",
"text": "商品销量趋势"
}]
}
好了,开始实现前端的代码
html
<div class="com-page">
<div class="com-container">
<div class="title" :style="comStyle">
<span>{
{ '▎ ' + showTitle }}</span>
<span class="iconfont title-icon" :style="comStyle" @click="showChoice = !showChoice"></span>
<div class="select-con" v-show="showChoice" :style="marginStyle">
<div class="select-item" v-for="item in selectTypes" :key="item.key" @click="handleSelect(item.key)">
{
{ item.text }}
</div>
</div>
</div>
<div class="com-chart" ref="trend_ref"></div>
</div>
</div>
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;
}
.title {
position: absolute;
left: 20px;
top: 20px;
z-index: 10;
color: white;
.title-icon {
margin-left: 10px;
cursor: pointer;
}
.select-con {
background-color: #222733;
}
}
data
data() {
return {
chartInstance: null, //初始化echartInstance对象
allData: null, //接收的后台数据
showChoice: false, // 是否显示可选项
choiceType: 'map', // 显示的数据类型
titleFontSize: 0 // 指明标题的字体大小
};
},
methods
initChart方法
//初始化echartInstance对象
//chalk是我们定义的主题,echarts官方有案例,怎么使用可以百度一下,不喜欢可以直接删掉
this.chartInstance = this.$echarts.init(this.$refs.trend_ref, "chalk");
const initOption = {
grid: {
left: "3%",
top: "35%",
right: "4%",
bottom: "1%",
containLabel: true,
},
tooltip: {
trigger: "axis",
},
legend: {
left: 20,
top: "15%",
icon: "circle",
},
xAxis: {
type: "category",
boundaryGap: false,
},
yAxis: {
type: "value",
},
};
this.chartInstance.setOption(initOption);
},
getData方法
这里还是用http请求获取的数据,后面我再讲怎么用WebSocket获取我们的数据
async getData(){
const { data: res } = await this.$http.get("trend");
console.log(res);
this.allData = res;
this.updateChart();
}
updateChart
//更新数据
updateChart() {
// 半透明的颜色值
const colorArr1 = [
'rgba(11, 168, 44, 0.5)',
'rgba(44, 110, 255, 0.5)',
'rgba(22, 242, 217, 0.5)',
'rgba(254, 33, 30, 0.5)',
'rgba(250, 105, 0, 0.5)'
]
// 全透明的颜色值
const colorArr2 = [
'rgba(11, 168, 44, 0)',
'rgba(44, 110, 255, 0)',
'rgba(22, 242, 217, 0)',
'rgba(254, 33, 30, 0)',
'rgba(250, 105, 0, 0)'
]
//处理数据
//类目轴的数据
const timeArr = this.allData.common.month;
//y轴的数据,series下的数据
const valueArr = this.allData[this.choiceType].data;
const seriesArr = valueArr.map((item,index) => {
return {
name: item.name,
type: "line",
data: item.data,
stack: "map",
areaStyle:{
color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: colorArr1[index]
}, // %0的颜色值
{
offset: 1,
color: colorArr2[index]
} // 100%的颜色值
])
}
};
});
//图例的数据
const legendArr = valueArr.map((item) => {
return item.name;
});
const dataOption = {
xAxis: {
data: timeArr,
},
legend: {
data: legendArr,
},
series: seriesArr,
};
this.chartInstance.setOption(dataOption);
},
screenAdapter
//适配屏幕
screenAdapter() {
this.titleFontSize = this.$refs.trend_ref.offsetWidth / 100 * 3.6
const adapterOption = {
legend: {
itemWidth: this.titleFontSize,
itemHeight: this.titleFontSize,
itemGap: this.titleFontSize,
textStyle: {
fontSize: this.titleFontSize / 2
}
}
};
this.chartInstance.setOption(adapterOption);
this.chartInstance.resize();
}
},
handleSelect
//选择标题
handleSelect(currentType){
this.choiceType = currentType
this.updateChart()
this.showChoice = false
}
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
项目详情如下