Vue 折騰記 - (7) 寫一個挺不靠譜的Vue-Echarts元件

CRPER發表於2017-07-25

前言

上基友社群看了下,發現對echarts的封裝都是打包進去的...想想就還是算了..
圖表這貨.說實在的,若不是整個系統大量用到,打包進去沒必要...

CDN是個好東西,我們完全可以寫一個非同步載入JS然後封裝按需呼叫...

至於你能學到什麼,見仁見智了...不過有所收穫就是我這文章的意義所在了

廢話不多說,看效果圖;

  • 2017-10-19: 修正初次渲染及切換傳值渲染錯誤
  • 2017-08-09: 加入圖表資料更新監聽
  • 2017-08-31 : 防止多次append cdnjs

效果圖

實現思路及實現的功能

  • 非同步引入echarts,有三個版本,不是最大也不是最小那個,具體上官網看
  • 圖表的銷燬釋放記憶體
  • 圖表跟隨父包含塊自適應(事件監聽)
  • setOption的抽離,圖表例項化id隨機生成或者手動傳入
    • setOption可以一次性傳入整個圖表的引數,也可以拆分傳入(太多,只選了幾個複用率很高選項的)...都設定了預設值.
    • 隨機id是大寫字母的組合,外部有傳入則用外部的

程式碼

實現Vue.use(?)

  • index.js -- 匯出元件的,內部實在亮瞎眼

import echarts from "./echarts";

// Vue.use必須有install這個函式..可能我這裡寫的比較粗糙..有興趣的可以去完善
// 用是可以正常使用的
export default {
  install: function(Vue, Option) {
    Vue.component("vue-echarts", echarts);
  }
};複製程式碼
  • echarts.vue
<template>
  <div class="vue-echarts" :id="id" :style="style">
  </div>
</template>

<script>
  export default {
    name: 'vue-echarts',
    data: function () {
      return {
        loadJS: '', // 儲存非同步載入echart的promise狀態
        chart: '', // 儲存地圖初始化的例項
      }
    },
    props: {
      id: {
        type: String,
        default: function () { // 生成一個隨機字串,純英文的,當不傳入ID的時候生成例項,加i確保不會隨機到一樣的
          let temp = [];
          for (let i = 0; i < 6; i++) {
            let randomChar = String.fromCharCode(65 + Math.floor(Math.random() * 19) + i);
            temp.push(randomChar);

          }
          return temp.reduce((pre, next) => pre + next);
        }
      },
      height: { // 圖表高度
        type: String,
        default: '300px'
      },
      width: { // 圖表寬度
        type: String,
        default: '100%'
      },
      legend: { // 以下的配置都是echarts官方圖表的自定義部分,我拆分這麼幾大塊
        type: Object,
        default: function () {
          return {
            data: ['郵件營銷', '聯盟廣告', '視訊廣告', '直接訪問', '搜尋引擎']
          }
        }
      },
      title: {
        type: Object,
        default: function () {
          return {
            text: '堆疊區域圖'
          }
        }
      },
      tooltip: {
        type: [Array, Object],
        default: function () {
          return {
            trigger: 'axis',
            axisPointer: {
              type: 'cross',
              label: {
                backgroundColor: '#6a7985'
              },
              animation: true
            }
          }
        }
      },
      toolbox: {
        type: Object,
        default: function () {
          return {
            show: true,
            feature: {
              dataZoom: {
                yAxisIndex: 'none'
              },
              dataView: { readOnly: false },
              magicType: { type: ['line', 'bar'] },
              restore: {},
              saveAsImage: {}
            }
          }
        }
      },
      grid: {
        type: Object,
        default: function () {
          return {
            left: '3%',
            right: '4%',
            bottom: '3%',
            containLabel: true
          }
        }
      },
      xAxis: {
        type: [Array, Object],
        default: function () {
          return [
            {
              type: 'category',
              boundaryGap: true,
              data: ['00:00', '02:00', '04:00', '06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00', '24:00']

            }
          ]
        }
      },
      yAxis: {
        type: [Array, Object],
        default: function () {
          return [
            {
              type: 'value'
            }
          ]
        }
      },
      series: {
        type: [Array, Object],
        default: function () {
          return [
            {
              name: '曝光',
              type: 'line',
              smooth: true,
              lineStyle: {
                normal: {
                  color: '#f00',
                }
              },
              data: [120, 132, 101, 134, 90, 230, 210, 120, 132, 101, 134, 90, 120]
            },
            {
              name: '點選',
              type: 'line',
              smooth: true,
              lineStyle: {
                normal: {
                  color: '#20a0ff',
                }
              },
              data: [220, 182, 191, 234, 290, 330, 310, 120, 132, 101, 134, 90, 120]
            },
            {
              name: '點選率',
              type: 'line',
              lineStyle: {
                normal: {
                  color: '#42b983',
                }
              },
              smooth: true,
              data: [150, 232, 201, 154, 190, 330, 410, 120, 132, 101, 134, 90, 120]
            }
          ]
        }
      },
      setOption: {
        type: Object
      },
      dispose: Boolean

    },
    computed: {
      style () {
        return {
          height: this.height,
          width: this.width
        }
      }
    },
    created () {
      this.loadJS = this.loadEchartsJS();
    },
    mounted () {
      this.loadJS.then(() => {
        let st = setTimeout(() => {
           this.init(); // CDNJS載入後,元素渲染後,初始化圖表
        }, 300);
      }).catch(err => {
        console.log('echarts js載入失敗');
      });
    },
    beforeDestroy () {
      window.removeEventListener('resize', this.chart.resize)
      if (this.chart) {
        this.chart.dispose(); // 銷燬圖表例項
      }
    },
    methods: {
      init () { // 初始化圖表
        this.chart = new echarts.init(document.getElementById(this.id));
        this.chart.setOption(this.setOption);
        window.addEventListener('resize', this.chart.resize) // 圖表響應大小的關鍵,重繪
      },
      loadEchartsJS () { // 非同步請求cdnjs
        const CDNURL = 'https://cdn.bootcss.com/echarts/3.7.1/echarts.min.js';
        return new Promise((resolve, reject) => {
          const script = document.createElement('script');
          script.type = "text/javascript";
          script.id = "cdnEchart";
          if (script.readyState) {  //IE
            script.onreadystatechange = function () {
              if (script.readyState == "loaded" ||
                script.readyState == "complete") {
                script.onreadystatechange = null;
                resolve('success: ' + CDNURL);
              }
            };
          } else {  //Others
            script.onload = function () {
              resolve('success: ' + CDNURL);
            };
          }
          script.onerror = function () {
            reject(Error(CDNURL + 'load error!'));
          };

          script.src = CDNURL;
          if (!document.getElementById('cdnEchart')) {
            document.head.appendChild(script);
          }

        });
      }
    },
    watch: {
      setOption: {
        handler: function (newVal, oldVal) { // 監聽外部傳入的值,渲染新的的圖表資料
          if (this.chart) {
            if (newVal) {
              this.chart.setOption(newVal);
            } else {
              this.chart.setOption(oldVal);
            }
            this.chart.resize();
          } else {
            setTimeout(() => {
              this.init();
            }, 300);
          }
        },
        deep: true
      }
    }
  }
</script>複製程式碼

如何使用?

  • main.js -- 主入口檔案,
// promise的polyfill
require("es6-promise").polyfill(); 
// 百度圖表
import echarts from './components/common/echarts';
Vue.use(echarts);複製程式碼

總結

好吧,寫這個東西花了一下午的時間,從構思到去查echarts api到實現;

粗糙的版本已經誕生,可能還有可以優化的地方,只能待我以後發現了再做調整了.

有更好的實現方案和思路的可以往下面留言..

相關文章