如何优(偷)雅(懒)使用ECharts

595 阅读4分钟

在无情的增删模块页面之后,客户终于意识到我们的产品还缺少什么东西了,那就是一套看起来就很扎眼球统计看板页,反复压榨我们产品设计之后(好像是六稿,我们开发过程中还改了两稿)

最后差不多是这个样子...

两层tab页,每个页面差不多三四个图表,就这平平无奇的页面,竟然藏着60个图表

设计: 看看这里,三层呢,客户就要这样

还能怎么办?爱他就要为他码代码


准备

分析一下需求

  • 技术栈:vue
  • 图表数量多
  • 花样少 就饼图 折线图 柱状图
  • 结构重复,适合组件化

使用echarts作为图表库,为了方便控制,我使用指令来控制图表配置

使用vue自定义指令绘制图表

Vue 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令

注册全局指令

<div v-echarts:[isload].clear="option"/>
<!--	isload参数 控制图表是否加载中 对应echarts实例showLoading() 和 hideLoading()
	默认为false,不显示加载  -->
<!-- clear修饰符 使用后 图表绘制前执行一次 echarts实例的clear()  -->
const directiveObj = {
  inserted(el, binding) {
      const charts = echarts.init(el, "themeName");
      obz.add(el); // 添加到监听 尺寸变化时 运行echarts实例resize() 后续讲解 
      updateCharts(charts, binding); // 绘制数据到图表
    },
  update(el, binding) {
    const charts = echarts.getInstanceByDom(el);
    updateCharts(charts, binding);// 绘制数据到图表
  },
  unbind(el) {
    const charts = echarts.getInstanceByDom(el);
    obz.remove(el);// 移除监听
    charts.dispose(); // 释放实例
  }
}
app.directive("echarts", directiveObj);// 添加全局自定义指令

inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有

unbind:只调用一次,指令与元素解绑时调用。

上述代码,我们可以看出来,在元素插入时,我们echarts.init了一个实例,后面就根据指令传入的数据执行更新图表; 数据更新时,我们通过echarts.getInstanceByDom拿到了当前echarts实例,对它进行更新操作 卸载时,我们echarts.getInstanceByDom拿到实例后,执行charts.dispose()销毁实例

更新图表数据

接下来就要讲解 我们的updateCharts方法是如何实现的

const updateCharts = (charts, binding) => {
  if (typeof binding.arg === "boolean") {
    if (binding.arg) //  v-echarts:[true]
      charts.showLoading("default", {
        text: "",
        color: theme.color[0],
        textColor: "#000",
        maskColor: "rgba(255, 255, 255, 0.4)",
        zlevel: 0,
        fontSize: 50,
        showSpinner: true,
        spinnerRadius: 30,
        lineWidth: 4
      });
    else charts.hideLoading(); //v-echarts:[false] 或者压根就没使用isload参数
  }
 if(binding.modifiers.clear) // 存在 v-echarts.clear
    charts.clear()
  if (binding.value)
    charts.setOption(binding.value);
  charts.resize();
}

binding.value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。

binding.arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"

binding.modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。

我们首先判断当前isload参数,控制加载面板显示隐藏,再判断clear修饰符控制是否加载前清除图表元素,最后再resize适配尺寸

适配大小尺寸

pc页面,免不了放大缩小尺寸,一些情况下咱们就添加document的resize事件,但是图表一多,我们就很难进行控制了,所以我们使用了ResizeObserverAPI,但是出于兼容性考虑,我们使用'resize'事件做下降级

class ObserveSize {
  constructor(cb, delay = 300) {
    if ("ResizeObserver" in window) {
      this.obz = new ResizeObserver(
        // 添加防抖
        debounce((targets) => cb(targets.map(({ target }) => target)), delay)
      );
    } else {
      this.domSet = new Set();
      this.onResize = debounce(() => {
        const { domSet } = this;
        if (domSet) cb([...domSet]);
      }, delay);
    }
  }
  add(target) {
    if (this.obz) {
      this.obz.observe(target);
    } else if (this.domSet) {
      if (this.domSet.size === 0 && this.onResize)
        window.addEventListener("resize", this.onResize);
      this.domSet.add(target);
    }
  }
  remove(target) {
    if (this.obz) {
      this.obz.unobserve(target);
    } else if (this.domSet) {
      this.domSet.delete(target);
      if (this.domSet.size === 0 && this.onResize)
        window.removeEventListener("resize", this.onResize);
    }
  }
}
const obz = new ObserveSize((targetList) => {
  for (const target of targetList) {
    const charts = echarts.getInstanceByDom(target);
    if (charts) charts.resize();
  }
})

前方的ObserveSize类 提供了两个方法remove add,就是添加控制,new ObserveSize传递回调函数,当尺寸变化时,会传递当前尺寸变化的dom元素 ,针对的处理下charts.resize()就可以了;本文不涉及这个ObserveSizeAPI


强行结尾

之前看大佬写的文章通俗易懂,现在自己写的却是这鬼样子,好多想表达的知识都没有表达,比如生命周期、用管理可复用的元素,这就是石立差距吧。算了,时间到了不睡就猝死的时间了,后期再修改了