前言
最近,在工作中开发了一个数据大屏的展示系统,主要是将一些业务的关键指标以数据可视化的方式展示,供运营和数据分析的同学使用。本文梳理了整个系统前端实现的一些步骤点,并基于此实现了一个简单的 Demo,在文末处附上,感兴趣的同学可以先看下效果。
设计选型
- Vue 全家桶
- UI 组件库:Ant Design of Vue
- 图表库:Highcharts
- 界面参考:Antd pro vue
- 数据可视化设计:数据可视化页
项目前期配置
在使用 Vue CLI
创建空项目后,在 src
目录下创建以下文件夹。
javascript
src
├─ App.vue
├─ assets
├─ components
├─ config
├─ core
├─ layouts
├─ main.js
├─ mixins
├─ mock
├─ router
├─ store
└─ views
各文件夹的主要作用:
components
:业务组件config
:侧栏菜单配置项,路由组件配置core
:按需引入组件配置layouts
:通用布局和容器组件mixins
:组件抽离出来的公用组件mock
:页面调试假数据router
:路由引入配置store
:vuex 相关文件views
:视图组件
关键点
组件按需加载
参考 按需加载,通过 babel-plugin-import 插件实现。在 src/core/component_use.js
文件夹下,配置按需引入的组件,并在 main.js
中组件配置文件。
javascript
import Vue from "vue";
import {
Button
...
} from "ant-design-vue";
Vue.use(Button);
组件国际化配置
通过国际化配置,组件的显示文案都会配置成中文。
javascript
<template>
<a-config-provider :locale="zhCN">
<div id="app">
<router-view/>
</div>
</a-config-provider>
</template>
<script>
import zhCN from "ant-design-vue/lib/locale-provider/zh_CN";
export default {
data() {
return {
zhCN
};
}
};
</script>
图表组件
图表组件是基于原生 highcharts
进行封装的,没有使用 highcharts-vue
针对 vue
的扩展包。原因如下: 在 highcharts-vue
中使用 refs
等引用方式调用 highcharts
内置的函数存在限制,一些图表操作需要使用 trick
方法才能实现。官方 README
中也有说明:

封装思路
以堆叠柱状图为例
组件模板
xml
<template>
<div class="stack-column-chart"></div>
</template>
script
kotlin
import { chartMixin } from "@/mixins/chart_mixin";
var Highcharts = require("highcharts");
export default {
name: "StackColumnChart",
mixins: [chartMixin],
props: {
series: {
type: [Array, Object],
required: true
}
},
data() {
return {
stackColumnChartRef: undefined,
seriesData: []
};
},
watch: {
series: {
immediate: true,
handler(newVal) {
setTimeout(() => {
if (newVal && newVal.length > 0) {
const seriesData = [...newVal];
this.seriesData = seriesData;
this.stackColumnChartRef && this.updateChartData(this.seriesData);
this.stackColumnChartRef && this.stackColumnChartRef.reflow();
}
}, 0);
}
}
},
methods: {
generateChartOptions() {
return Highcharts.chart(this.$el, {
chart: {},
title: {},
xAxis: {},
yAxis: {},
legend: {},
plotOptions: {},
series: []
});
},
removeChartSeries() {
// 移除历史 series
if (this.stackColumnChartRef) {
while (this.stackColumnChartRef.series.length) {
this.stackColumnChartRef.series[0].remove();
}
}
},
initChart() {
this.stackColumnChartRef = this.generateChartOptions(this.chartType);
this.initChartRef(this.stackColumnChartRef);
},
updateChartData(data) {
// 移除历史 series
this.removeChartSeries();
// 方式一
// const len = data.length || 0
// for (let i = 0; i < len; i++) {
// this.stackColumnChartRef &&
// this.stackColumnChartRef.addSeries({
// name: data[i].name,
// data: data[i].data
// })
// }
// // 方式二(默认 addSeries 操作会触发 redraw 重绘,置为 false 则将不会触发重绘)
const len = data.length || 0;
for (let i = 0; i < len; i++) {
this.stackColumnChartRef &&
this.stackColumnChartRef.addSeries(
{
name: data[i].name,
data: []
},
false
);
this.stackColumnChartRef &&
this.stackColumnChartRef.series[i].update(
{
data: data[i].data
},
false
);
}
// 单独触发一次重绘
this.stackColumnChartRef.redraw();
}
},
mounted() {
this.initChart();
setTimeout(() => {
this.stackColumnChartRef.reflow();
}, 0);
}
};
</script>
- 图表初始化:
Highchart.chart(this.$el, { 配置对象 })
会返回一个引用。可以通过该引用来调用图表相关的方法。
- 图表重绘,自适应:
reflow
、redraw
- series 数据新增:
addSeries
- series 数据更新:
series[i].update
- series 历史数据清除:
series[i].remove
业务方案
全局图表组件自适应
在上一步中,可以通过 redraw
/reflow
方法来实现图表自适应。在项目中存在多种类型的图表时,可将 reflow
相应逻辑抽离出来,封装成一个 mixin
,chart_mixin
如下:
javascript
import { mapState } from "vuex";
export const chartMixin = {
data() {
return {
chartRef: undefined
};
},
computed: {
...mapState({
collapseTriggerCount: state => state.app.collapseTriggerCount
})
},
watch: {
collapseTriggerCount: {
immediate: true,
deep: true,
handler() {
this.resizeChart(this.chartRef);
}
}
},
methods: {
initChartRef(ref) {
this.chartRef = ref;
},
resizeChart(ref) {
setTimeout(() => {
ref && ref.reflow();
}, 100);
}
}
};
reflow
方法需要在setTimeout
调用。
图表渲染卡顿
在堆叠柱状图组件的实现中,发现当数据量相对大时,图表的渲染耗时比较长,并且图表的渲染会阻塞页面的事件相应,导致整个页面非常卡顿,如下:

上述图中,通过
addSeries
默认方式更新图表数据时,平均每个图的绘制需要耗时 3s。当使用在多 Tab 组件中进行图表绘制时。假设有 3 个 Tab。页面会存在着约 10 s 的非正常响应时间,体验非常之差。
优化方法
- 减少重绘次数,通过设置
addSeries
的第二个参数为 false,更新数据后不触发重绘。在数据更新完毕后,再通过redraw
方法触发重绘。

其他触发重绘的方法:
addPoint
。
- 关闭
dataLabels
设置
为了计算 dataLabels
的位置,highcharts
会对每列的 dataLabel
进行定位运算,以确定它的位置。在数据量较大时,耗时也是非常多的。
javascript
plotOptions: {
column: {
stacking: 'normal',
dataLabels: {
enabled: false
}
}
}
- 按需加载与 loading 状态
主要是给用户呈现一种数据在加载中的效果。
- 在多 Tab 组件中,只在切换 Tab 时,才去更新图表数据。
- 添加 loading 状态
通过上述的 3 个方式,在图表加载数据时,已经不会有卡顿的现象。单图的绘制时长也控制在了 1 ~ 2s 的范围。
源码
- CodeSandbox: highcharts-dashboard
- github: highcharts-dashboard