vue3 echart組件封裝

来源:https://www.cnblogs.com/shapeY/p/18043757
-Advertisement-
Play Games

項目中用到了很多echart圖表,進行了簡單的組件封裝,主要包含以下功能: 創建圖表實例,渲染圖表 支持傳入自定義函數,可拿到圖表實例,實現個性化功能 支持配置更新後圖表自動刷新,可配置是清空後再刷新 loading狀態控制 resize時圖表更新 支持餅圖預設高亮功能 實現 資源引入 echart ...


項目中用到了很多echart圖表,進行了簡單的組件封裝,主要包含以下功能:

  • 創建圖表實例,渲染圖表
  • 支持傳入自定義函數,可拿到圖表實例,實現個性化功能
  • 支持配置更新後圖表自動刷新,可配置是清空後再刷新
  • loading狀態控制
  • resize時圖表更新
  • 支持餅圖預設高亮功能

實現

資源引入

  • echart資源按需引入
  • 第三方組件引入(echarts-liquidfill,水波紋圖表)
/* 即下文中的 @/modules/echartPlugin */

// https://echarts.apache.org/handbook/zh/basics/import#%E6%8C%89%E9%9C%80%E5%BC%95%E5%85%A5-echarts-%E5%9B%BE%E8%A1%A8%E5%92%8C%E7%BB%84%E4%BB%B6
import * as echarts from "echarts/core";
import {
  BarChart,
  // 系列類型的定義尾碼都為 SeriesOption
  BarSeriesOption,
  PieChart,
  PieSeriesOption,
  LineChart,
  LineSeriesOption,
  LinesChart,
  LinesSeriesOption,
  EffectScatterChart,
  EffectScatterSeriesOption,
} from "echarts/charts";
import {
  TitleComponent,
  // 組件類型的定義尾碼都為 ComponentOption
  TitleComponentOption,
  TooltipComponent,
  TooltipComponentOption,
  DatasetComponent,
  DatasetComponentOption,
  GridComponent,
  GridComponentOption,
  DataZoomComponent,
  DataZoomComponentOption,
  LegendComponent,
  LegendComponentOption,
  GeoComponent,
  GeoComponentOption,
} from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
import "echarts-liquidfill";

// 通過 ComposeOption 來組合出一個只有必須組件和圖表的 Option 類型
export type ECOption = echarts.ComposeOption<
  | BarSeriesOption
  | TitleComponentOption
  | TooltipComponentOption
  | GridComponentOption
  | DatasetComponentOption
  | DataZoomComponentOption
  | PieSeriesOption
  | LegendComponentOption
  | GeoComponentOption
  | LinesSeriesOption
  | LineSeriesOption
  | EffectScatterSeriesOption
>;

// https://www.npmjs.com/package/echarts-liquidfill
export interface LiquidFillOption {
  series: {
    type: "liquidFill";
    data: number[];
    color?: string[];
    radius?: string;
    center?: [string, string];
    label?: {
      color?: string;
      insideColor?: string;
      fontSize?: number;
      formatter?: (param: {
        borderColor: string;
        color: string;
        data: number;
        dataIndex: number;
        dataType: undefined;
        name: string;
        value: number;
      }) => string | number;
    };
    shape?:
      | "circle"
      | "rect"
      | "roundRect"
      | "triangle"
      | "diamond"
      | "pin"
      | "arrow";
    [name: string]: unknown;
  }[];
  [name: string]: unknown;
}

// 註冊必須的組件
echarts.use([
  TitleComponent,
  TooltipComponent,
  GridComponent,
  BarChart,
  LinesChart,
  CanvasRenderer,
  DatasetComponent,
  DataZoomComponent,
  PieChart,
  LegendComponent,
  GeoComponent,
  LineChart,
  EffectScatterChart,
]);

export default echarts;

組件封裝

<template>
  <div class="h-echart-wrapper" ref="chartWrapperDom">
    <div class="h-echart" ref="chartDom">loading</div>
  </div>
</template>
<script lang="ts" src="./index.ts"></script>
<style lang="less" scoped>
.h-echart-wrapper {
  height: 100%;
}
.h-echart {
  height: 100%;
  width: 100%;
  text-align: center;
}
</style>
import {
  defineComponent,
  onMounted,
  onUnmounted,
  PropType,
  ref,
  watch,
  toRaw,
} from "vue";
import echarts, { ECOption, LiquidFillOption } from "@/modules/echartPlugin";
import ResizeObserver from "resize-observer-polyfill";

export default defineComponent({
  name: "h-echart",
  props: {
    // echart配置
    options: {
      type: Object as PropType<ECOption | LiquidFillOption>,
      required: true,
    },
    // 餅圖是否需要預設高亮
    needDefaultHighLight: {
      type: Boolean,
      default: false,
    },
    loading: Boolean,
    // 自定義函數,會暴露echart實例出去,可以實現個性化操作
    customFn: Function as PropType<
      (echartInstance: null | echarts.ECharts) => void
    >,
    // 更新圖表之前是否先清空
    clearBeforeUpdate: Boolean,
  },
  setup(props) {
    const chartWrapperDom = ref<null | HTMLElement>(null);
    const chartDom = ref<null | HTMLElement>(null);
    // WARN: echarts5 實例用響應式對象存放時會導致功能tooltip功能異常
    let echartInstance: null | echarts.ECharts = null;
    let chartWrapperResize: null | ResizeObserver = null;
    let highlightName: string | null = null;
    let firstRender = true;

    const setOptions = (options?: ECOption | LiquidFillOption) => {
      echartInstance &&
        options &&
        echartInstance.setOption(toRaw(options), {
          notMerge: true,
        });

      if (props.needDefaultHighLight && firstRender) {
        firstRender = false;
        const _options = props.options as ECOption;

        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        if (_options.series && _options.series[0] && _options.series[0].data) {
          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
          // @ts-ignore
          const name = _options.series[0].data[0].name as string;

          setTimeout(() => {
            // 預設高亮
            echartInstance &&
              echartInstance.dispatchAction({
                type: "highlight",
                seriesIndex: 0,
                name,
              });

            highlightName = name;
          }, 600);
        }
      }
    };

    watch(
      () => props.loading,
      (newLoading) => {
        if (newLoading !== undefined && echartInstance) {
          newLoading
            ? echartInstance.showLoading({
                textColor: "rgb(255 255 255 / 0%)",
                showSpinner: false,
                zlevel: 0,
              })
            : echartInstance.hideLoading();
        }
      }
    );

    const init = () => {
      chartDom.value && (echartInstance = echarts.init(chartDom.value));

      props.customFn && props.customFn(echartInstance);

      if (props.needDefaultHighLight && echartInstance) {
        echartInstance.on("mouseover", function (e) {
          if (e.name !== highlightName) {
            echartInstance!.dispatchAction({
              type: "downplay",
              seriesIndex: 0,
              name: highlightName,
            });
          }
        });

        echartInstance.on("mouseout", function (e) {
          highlightName = e.name;
          echartInstance!.dispatchAction({
            type: "highlight",
            seriesIndex: 0,
            name: e.name,
          });
        });
      }
      setOptions(props.options);
    };

    onMounted(() => {
      // 初始化圖表實例
      setTimeout(init, 300);

      // 觀察包裹層變化,進行圖表resize
      if (chartWrapperDom.value) {
        chartWrapperResize = new ResizeObserver(() => {
          echartInstance && echartInstance.resize();
        });

        chartWrapperResize.observe(chartWrapperDom.value);
      }
    });

    // 觀察者清理
    onUnmounted(() => {
      chartWrapperResize?.disconnect();
    });
    watch(
      () => props,
      // 配置變化,重新設置
      (newVal) => {
        if (newVal.clearBeforeUpdate) {
          echartInstance && echartInstance.clear();
        }
        setOptions(toRaw(newVal.options));
      },
      { immediate: true, deep: true }
    );

    return {
      chartDom,
      chartWrapperDom,
    };
  },
});

組件註冊及全局類型聲明

/* ./components/index.ts */
import { App } from "vue";
import HEchart from "./h-echart";
import HIframeKeepAlive from "./h-iframe-keep-alive/index.vue";

export default function useCompoments(app: App<Element>) {
  app &&
    app.component &&
    [
      HEchart,
      HIframeKeepAlive,
    ].forEach((_component) => {
      app.component(_component.name, _component);
    });
}

// 聲明全局組件類型
// https://github.com/johnsoncodehk/volar/blob/master/extensions/vscode-vue-language-features/README.md
declare module "@vue/runtime-core" {
  export interface GlobalComponents {
    HEchart: typeof HEchart;
    HIframeKeepAlive: typeof HIframeKeepAlive;
  }
}
import useCompoments from "./components";

const app = createApp(App).use(router);
tempApp = app;

// 註冊所自定義組件
useCompoments(app);

使用

    <div class="chart-wrapper">
      <h-echart :options="boardPieOptions" needDefaultHighLight />
    </div>
  const boardPieOptions = computed(() => {
    return getBoardPieOptions(props.arrivalNodeStats.types);
  });

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 寫在前面 按照國際慣例,要先聊下生活,吐槽一番,今天是2月14日,也是下午聽老媽說,我才知道! 現在真的是對日期節日已經毫無概念可言,只知道星期幾。 現在已經覺得寫博客也好,學習文章也罷,和寫日記一樣,已經融入到我的生活中,或者更確切的說,變成生活的一部分了。 飯後和老媽閑聊了幾句後,我發現現在真的 ...
  • 概念 RCE(Remote code execution)遠程代碼執行漏洞,RCE又分命令執行和代碼執行。 RCE-遠程代碼執行:遠程執行PHP代碼 RCE-遠程命令執行:遠程執行Linux或者Windows等系統命令。 常見函數有: PHP:eval(),assert(),preg_replace ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、mixin是什麼 Mixin是面向對象程式設計語言中的類,提供了方法的實現。其他類可以訪問mixin類的方法而不必成為其子類 Mixin類通常作為功能模塊使用,在需要該功能時“混入”,有利於代碼復用又避免了多繼承的複雜 Vue中的mi ...
  • 一、react-transition-group 使用 相關技術的使用: React 18 React router v6 React Transition Group 是一個 React 庫,專門用於在 React 應用中管理和處理過渡動畫效果。這個庫提供了一組組件,包括 Transition、C ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、什麼是雙向綁定 我們先從單向綁定切入單向綁定非常簡單,就是把Model綁定到View,當我們用JavaScript代碼更新Model時,View就會自動更新雙向綁定就很容易聯想到了,在單向綁定的基礎上,用戶更新了View,Model的 ...
  • 前言 上周五晚上8點,開開心心的等著產品驗收完畢後就可以順利上線。結果產品突然找到我說要加需求,並且維護這一塊業務的同事已經下班走了,所以只有我來做。雖然內心一萬頭草泥馬在狂奔,但是嘴裡還是一口答應沒問題。由於這一塊業務很複雜並且我也不熟悉,加上還餓著肚子,在梳理代碼邏輯的時候我差點崩潰了。需要修改 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、組件是什麼 回顧以前對組件的定義: 組件就是把圖形、非圖形的各種邏輯均抽象為一個統一的概念(組件)來實現開發的模式,在Vue中每一個.vue文件都可以視為一個組件 組件的優勢 降低整個系統的耦合度,在保持介面不變的情況下,我們可以替換 ...
  • DataGear專業版 1.0.0 已發佈,歡迎試用! http://datagear.tech/pro/ DataGear 支持採用原生的HTML、JavaScript、CSS製作數據可視化看板,也支持導入由npm、vite等前端工具構建的前端程式包。得益於這一特性,可以很容易製作基於three. ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...