【vue3 + ts + echarts】封裝一個通用echarts元件-2.0版

前端小高發表於2023-05-08

閒時基於上次封裝的圖表元件做了一層最佳化,話不多說,直接上程式碼吧

Step-1: 新建一個config.ts檔案

export const config = {
  labelKey: "name",
  valueKey: "value",
};

export type chartDataItemType = {
  [key: string]: string | number;
};

// 當前支援顯示的圖表型別(折線,柱形)
export type chartSupportType = "line" | "bar";

export type chartDataType = {
  name: string;
  data: chartDataItemType[];
  type: chartSupportType;
  labelKey?: string;
  valueKey?: string;
  options?: any;
};

export type chartOptionsType = {
  [key: string]: any;
};

// 基礎圖例顏色資料
export const baseColorArr = [
  "#3CD7D7",
  "#F9CB28",
  "#A089FF",
  "#5CC4FF",
  "#FF9292",
  "#5CC4FF",
  "#FF8AEC",
  "#FFB35B",
];

// 陰影區域顏色資料
export const areaColorArr = [
  "#C5F3F3",
  "#FDEFBF",
  "#dcd3ff",
  "#bde7ff",
  "#ffd3d3",
  "#bde7ff",
  "#ffd0f7",
  "#ffe0bd",
];

// 圖例頂部字型大小
export const defaultLabelFontsize = 8;

export const getColor = (index, type: "default" | "area" = "default") => {
  const colorArr = type === "default" ? baseColorArr : areaColorArr;
  const colorIndex = index % colorArr.length;
  return colorArr[colorIndex];
};

// 圖表底部捲軸預設樣式
export const defaultDataZoomOptions: any[] = [
  {
    type: "slider",
    show: true,
    xAxisIndex: [0],
    start: 0,
    end: 30,
    textStyle: {
      color: "#ccd7d7",
    },
  },
];

// 獲取捲軸樣式
export const getDataZoom = (seriesLength, showZoomLength) => {
  if (seriesLength > showZoomLength) {
    return defaultDataZoomOptions;
  } else {
    return [];
  }
};

// 獲取折線圖、柱形圖基礎配置
export const getLineBarBaseOptions = (seriesLength, showZoomLength = 7) => {
  return {
    tooltip: {
      trigger: "axis",
      axisPointer: {
        type: "shadow",
      },
    },
    grid: {
      left: "3%",
      right: "4%",
      bottom: "3%",
      containLabel: true,
    },
    yAxis: {
      type: "value",
    },
    dataZoom: getDataZoom(seriesLength, showZoomLength),
  };
};

// 獲取折線系列配置
export const getLineSeriesOptions = (index) => {
  return {
    tooltip: {
      trigger: "axis",
    },
    areaStyle: {
      color: getColor(index, "area"),
    },
    smooth: true,
    label: {
      show: true, //開啟顯示
      position: "top", //在上方顯示
      //數值樣式
      color: getColor(index, "default"),
      fontSize: defaultLabelFontsize,
    },
  };
};

// 獲取柱形系列配置
export const getBarSeriesOptions = (index) => {
  return {
    barWidth: 10,
    label: {
      show: true, //開啟顯示
      position: "top", //在上方顯示
      //數值樣式
      color: getColor(index, "default"),
      fontSize: defaultLabelFontsize,
    },
    itemStyle: {
      borderRadius: [6, 6, 0, 0],
    },
  };
};

Step-2: 新建一個index.vue檔案

<!--
/**
* Author: 前端小高
* Date: 2023-05-08 14:58
* Desc: MyChart 檔案描述
*/
-->

<template>
  <div ref="chartDom" class="chart-dom"></div>
</template>

<script name="MyChart" lang="ts" setup>
import { ref, computed, watch, onMounted, onBeforeUnmount, PropType, nextTick } from "vue";
import * as ECharts from "echarts";
import { debounce } from "lodash";
import {
  baseColorArr,
  chartDataType,
  chartOptionsType,
  chartSupportType,
  chartDataItemType,
  getLineBarBaseOptions,
  getLineSeriesOptions,
  getBarSeriesOptions,
  config,
} from "./config";

const props = defineProps({
  color: {
    type: Array as PropType<string[]>,
    default: () => {
      return baseColorArr;
    },
  },
  // 超過該數字時顯示捲軸
  showZoomLimit: {
    type: Number,
    default: 7,
  },
  // 座標軸是否兩邊留白
  isBoundaryGap: {
    type: Boolean,
    default: false,
  },
  baseOptions: {
    type: Object as PropType<chartOptionsType>,
    default: () => {
      return {};
    },
  },
  data: {
    type: Array as PropType<chartDataType[]>,
    require: true,
  },
});

const emits = defineEmits(["chart-click"]);

const initSeriesData = () => {
  if (props?.data?.length) {
    getXAxisDataArr();
    const tempSeriesArr = props?.data.map((item: chartDataType, index: number) => {
      const chartOptions = getSeriesItemOptions(item, index);
      return {
        name: item.name,
        data: getValueArr(item.data, item?.valueKey),
        type: item.type,
        ...chartOptions,
      };
    });
    const tempLegendDataArr = props?.data.map((item: chartDataType) => item.name);
    legendDataArr.value = tempLegendDataArr;
    seriesArr.value = tempSeriesArr;
  }
};

const getXAxisDataArr = () => {
  const labelName = props?.data?.[0]?.labelKey || config.labelKey;
  const xDataArr = props?.data?.[0]?.data.map((item) => item[labelName]);
  dataArr.value = xDataArr as string[];
};

const callbackMap = {
  line: getLineSeriesOptions,
  bar: getBarSeriesOptions,
};

const getSeriesItemDefaultOptions = (type: chartSupportType = "line") => {
  return callbackMap[type];
};

const getChartBaseOptions = () => {
  return getLineBarBaseOptions;
};

const getSeriesItemOptions = (chartItem: chartDataType, index = 0) => {
  const cb = getSeriesItemDefaultOptions(chartItem.type);
  const defaultOptions = cb(index);
  if (chartItem?.options) {
    const mergeOptions = Object.assign({}, defaultOptions, chartItem?.options);
    return mergeOptions;
  } else {
    return defaultOptions;
  }
};

const getBaseChartOptions = () => {
  const cb = getChartBaseOptions();
  const baseOptions = cb(dataArr.value.length, props.showZoomLimit);
  if (props?.baseOptions) {
    const mergeOptions = Object.assign({}, baseOptions, props?.baseOptions);
    return mergeOptions;
  } else {
    return baseOptions;
  }
};

const getValueArr = (arr: chartDataItemType[], valueKey?: string) => {
  const keyName = valueKey ? valueKey : config.valueKey;
  return arr.map((item) => Number(item[keyName]));
};

const resizeHandler = () => {
  chartExample.resize();
};
const resizeHandlerOrigin = debounce(resizeHandler, 300);

const dataArr = ref<any[]>([]);
const seriesArr = ref<any[]>([]);
const legendDataArr = ref<string[]>([]);

const getOptions = computed(() => {
  const baseOptions = getBaseChartOptions();
  const options = {
    color: props.color,
    ...baseOptions,
    legend: {
      data: legendDataArr.value,
    },
    xAxis: {
      type: "category",
      data: dataArr.value,
      boundaryGap: props.isBoundaryGap,
      axisTick: {
        alignWithLabel: true,
      },
    },
    series: seriesArr.value,
  };
  return options;
});

watch(
  () => props.data,
  () => {
    init();
  },
  {
    deep: true,
  }
);

const chartDom = ref();
let chartExample: any = null;
const initChart = () => {
  if (chartExample) {
    // 若存在圖表例項,則先執行銷燬操作
    chartExample.dispose();
  }
  nextTick(() => {
    chartExample = ECharts.init(chartDom.value);
    const options = getOptions.value;
    chartExample.setOption(options, true);
    initResizerListener();
    initChartEvent();
  });
};

const initResizerListener = () => {
  window.removeEventListener("resize", resizeHandlerOrigin);
  window.addEventListener("resize", resizeHandlerOrigin);
};

const initChartEvent = () => {
  cancelClickEvent();
  chartExample.on("click", (params) => {
    emits("chart-click", params);
  });
};

const cancelClickEvent = () => {
  chartExample.off("click");
};

const init = () => {
  initSeriesData();
  initChart();
};

onMounted(() => {
  init();
});

onBeforeUnmount(() => {
  cancelClickEvent();
  window.removeEventListener("resize", resizeHandlerOrigin);
  chartExample.dispose();
});
</script>
<style lang="scss" scoped>
.chart-dom {
  height: 300px;
}
</style>

Step-3:頁面下的引用方式

<!--
/**
* Author: 前端小高
* Date: 2023-05-08 14:55
* Desc: ChartComp 圖表元件
*/
-->

<template>
  <my-chart :data="dataArr"></my-chart>
</template>

<script name="ChartComp" lang="ts" setup>
import { ref } from "vue";
import { chartDataType } from "@/common/MyChart/config";

const dataArr = ref<chartDataType[]>([
  {
    name: "測試資料列1",
    type: "line",
    data: [
      {
        name: "1.1",
        value: 1,
      },
      {
        name: "1.2",
        value: 2,
      },
      {
        name: "1.3",
        value: 3,
      },
      {
        name: "1.1",
        value: 1,
      },
    ],
  },
]);
</script>

頁面展示效果如下

圖表元件展示效果

程式碼Gitee地址

對比基礎版本的echarts封裝元件,2.0版本做了如下最佳化

  • 只需傳入渲染資料及展示資料型別,即可渲染
  • 其餘圖表配置可以按需傳入
  • (不足之處:當前只做了折線圖及柱形圖的支援,其他型別圖表各位可以按需擴充套件)

Remark: 如果有幫助到各位或者給各位封裝元件提供了一點思路,不妨點個贊吧。。。

相關文章