背景
这两天在 Vue 项目中,用 echarts 的热力图实现了类似 kibana 机器学习的数据预测图。区别是,笔者用了 tab 签,切换总览和具体攻击 IP 的异常情况。
实现过程中,踩了两个 echarts 的坑,这里总结下这个过程。
功能描述
访问 kibana 官方任务视图 会看到这样一个异常数据热力图:
笔者用 tab 签将总览和攻击信息拆开了:
序号 ② 区域封装了一个 Vue 组件 DataGraph.vue
,在两个 tab 签中都引用它:
<el-tabs v-model="activeName" type="card" class="y-radioClassTab">
<el-tab-pane label="总览" name="first" lazy>
<exception-data-graph :isAll="true" :taskInfo="taskInfo"/>
</el-tab-pane>
<!--配置分组的任务有此切页-->
<el-tab-pane label="攻击IP" name="second" lazy>
<data-graph :isAll="false" :taskInfo="taskInfo"/>
</el-tab-pane>
</el-tabs>
DataGraph.vue
组件实现时,如果绘图区域共用一个 div ,则存在一个严重问题,就是 切换时另一个 tab 签的图是空白的
,一起来分析下这个问题。
热力图区域用同一个 div 的问题
从组件概念上来看,理论上父组件引用同一个子组件两次,本质上还是不同组件,绘图 div 共用一个,应该绘制在各自的子组件区域才对呢。
总览和攻击IP 的区别是:前者没有搜索框,但是都有热力图,所以抽取公共组件。以这个思路,笔者最初是这样写的:
<el-col class="borderLine padding4_8 marginB8">
<div style="text-align: right;height: 17px; line-height: 17px;">
<h6 style="float: left;margin:0;">异常数据时间线</h6>
</div>
<div id="anomalous" style="width: 100%;min-height: 100px;"></div>
</el-col>
第一步,启动应用,进入“总览”,能看到热力图:
第二步,定位 Tab 签,看到它的元素有一个 div 的 id 为 anomalous
,第二个 tab 签的 Panel 因用了懒加载还未渲染 :
第三步,切换到第二个页面,结果却看到空白:
第四步,定位 TabPanel2 的元素信息,看到它跟 TabPanel1 有一个相同 id 的 div ,但内容为空:
第五步,再切回去,发现热力图发生了变化:
调试代码,发现切到第二个页面时,创建的 echart 实例的 id 仍然是 ec_1592993260650
,就是第一个页面的那个图,所以断定渲染还是发生在第一个页面上。
一个组件内多次引用 echart 渲染错乱问题
这就令人费解了:明明引用了两次,理论上是两个子组件才对呢?
仔细看了一下绘图方法,终于明白了问题的根源:
const allAnomalousOptionCharts = echarts.init(document.getElementById(chartId));
allAnomalousOptionCharts.setOption(allAnomalousOption);
echarts 绘图时,使用的是 document.getElementById
,它在整个文档范围内找到某个 id 的元素,然后完成渲染。重复引用,整个文档中有两个 id 相同的元素,所以就渲染到了第一个 div 上去了。
解决办法
DataGraph.vue
组件用多个 div ,不同的 tab 页用不同的 div,修改实现,多加一个 div:
// 定义元素
<div v-if="isAll && !noData" id="anomalous1" style="width: 100%;min-height: 100px;"></div>
<div v-if="!isAll && !noData" id="anomalous2" style="width: 100%;min-height: 325px;"></div>
<div v-if="noData"><h3 style="text-align:center">无异常数据</h3></div>
// 绘图
let chartId = 'anomalous1';
if (!this.isAll) {
chartId = 'anomalous2';
}
const allAnomalousOptionCharts = echarts.init(document.getElementById(chartId));
allAnomalousOptionCharts.setOption(allAnomalousOption);
v-show 和 v-if
另外,关于无数据和有数据时页面的效果,笔者用了一个 “无数据” 文本,并试图用 v-show,来动态控制绘图组件的显示和隐藏:无数据时隐藏绘图区域,显示“无数据”文本。
测试发现,此路不通,具体表现在:
v-show 控制 echart 的 div 的显示隐藏时,当 js 完成 echarts 的实例化后,v-show 的属性即使已经设置为 true 了 ,此时页面并没有该 div,最终导致绘图失败。
解决办法:使用 v-if 强制渲染 div ,同时调用 this.$nextTick
将绘图逻辑作为回调,以确保图形能够正确绘制。