阅读视图

发现新文章,点击刷新页面。

Vue 封装 Echarts 组件

为了方便在不同页面使用 echarts,可以封装一个组件。如果不封装,也可以手动实例化 echarts,并且额外处理监听容器尺寸变化的功能。

<script setup lang="ts">
import { useResizeObserver } from "@vueuse/core";
import type { EChartsOption } from "echarts";
import { init, type ECharts, type ECElementEvent } from "echarts/core";

const props = defineProps<{
  /** Echarts 图表配置选项 */
  options?: EChartsOption;
  /** 图表渲染器类型,默认为 svg */
  renderer?: "canvas" | "svg";
}>();
const emit = defineEmits<{
  chartClick: [event: ECElementEvent];
}>();

/** 图表容器元素的引用 */
const container = useTemplateRef("figure-element");
/** Echarts 图表实例 */
const chart = shallowRef<ECharts>();

defineExpose({
  container,
  chart,
});

/**
 * 监听图表配置变化并更新图表
 */
watchEffect(() => {
  if (!chart.value || !props.options) return;
  chart.value.setOption(props.options);
});

/**
 * 监听容器尺寸变化并自动调整图表大小
 */
useResizeObserver(container, () => {
  if (!chart.value || chart.value.isDisposed()) return;
  chart.value.resize();
});

/**
 * 初始化图表实例
 */
watch(container, (container) => {
  if (!container) return;

  const instance = init(container, undefined, {
    renderer: props.renderer || "svg",
    locale: "ZH",
  });

  /** 绑定图表点击事件 */
  instance.on("click", (event) => {
    emit("chartClick", event);
  });

  chart.value = instance;
  onWatcherCleanup(() => instance.dispose());
});
</script>

<template>
  <figure ref="figure-element" :class="$style.figure" />
</template>

<style module>
.figure {
  overflow: hidden;
}
</style>

然后在页面中使用。

<script setup lang="ts">
import type { EChartsOption } from "echarts";
import { BarChart, LineChart, PieChart, ScatterChart } from "echarts/charts";
import {
  GridComponent,
  LegendComponent,
  TitleComponent,
  TooltipComponent,
} from "echarts/components";
import { use } from "echarts/core";
import { UniversalTransition } from "echarts/features";
import { SVGRenderer } from "echarts/renderers";
import EchartsContainer from "@/components/Echarts/EchartsContainer.vue";

use([
  GridComponent,
  LineChart,
  BarChart,
  SVGRenderer,
  PieChart,
  ScatterChart,
  UniversalTransition,
  TitleComponent,
  TooltipComponent,
  LegendComponent,
]);

/** 基础柱状图配置 */
const barChartOption: EChartsOption = {
  tooltip: {
    trigger: "axis",
    axisPointer: {
      type: "shadow",
    },
  },
  xAxis: {
    type: "category",
    data: ["一月", "二月", "三月", "四月", "五月", "六月"],
    axisTick: {
      alignWithLabel: true,
    },
  },
  yAxis: {
    type: "value",
  },
  series: [
    {
      name: "销售额",
      type: "bar",
      data: [120, 200, 150, 80, 70, 110],
    },
  ],
};

/** 折线图配置 */
const lineChartOption: EChartsOption = {
  tooltip: {
    trigger: "axis",
  },
  legend: {
    data: ["新用户", "活跃用户"],
  },
  xAxis: {
    type: "category",
    data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
  },
  yAxis: {
    type: "value",
  },
  series: [
    {
      name: "新用户",
      type: "line",
      data: [120, 132, 101, 134, 90, 230, 210],
      smooth: true,
      itemStyle: {
        color: "#67C23A",
      },
    },
    {
      name: "活跃用户",
      type: "line",
      data: [220, 182, 191, 234, 290, 330, 310],
      smooth: true,
      itemStyle: {
        color: "#E6A23C",
      },
    },
  ],
};

/** 饼图配置 */
const pieChartOption: EChartsOption = {
  tooltip: {
    trigger: "item",
    formatter: "{a} <br/>{b}: {c} ({d}%)",
  },
  legend: {
    orient: "vertical",
    left: "left",
  },
  series: [
    {
      name: "产品分类",
      type: "pie",
      radius: "50%",
      data: [
        { value: 1048, name: "电子产品" },
        { value: 735, name: "服装配饰" },
        { value: 580, name: "家居用品" },
        { value: 484, name: "食品饮料" },
        { value: 300, name: "其他" },
      ],
    },
  ],
};

/** 散点图配置 */
const scatterChartOption: EChartsOption = {
  tooltip: {
    trigger: "item",
  },
  xAxis: {
    type: "value",
    name: "X轴",
  },
  yAxis: {
    type: "value",
    name: "Y轴",
  },
  series: [
    {
      name: "数据点",
      type: "scatter",
      data: Array.from({ length: 50 }, () => [Math.random() * 100, Math.random() * 100]),
    },
  ],
};

/** 处理图表点击事件 */
const handleChartClick = (chartType: string) => {
  ElMessage.info(`点击了${chartType}图表`);
};
</script>

<template>
  <div :class="$style.container">
    <!-- 柱状图 -->
    <EchartsContainer
      :class="$style.chart"
      :options="barChartOption"
      @chart-click="handleChartClick('柱状图')"
    />

    <!-- 折线图 -->
    <EchartsContainer
      :class="$style.chart"
      :options="lineChartOption"
      @chart-click="handleChartClick('折线图')"
    />

    <!-- 饼图 -->
    <EchartsContainer
      :class="$style.chart"
      :options="pieChartOption"
      @chart-click="handleChartClick('饼图')"
    />

    <!-- 散点图 -->
    <EchartsContainer
      :class="$style.chart"
      :options="scatterChartOption"
      @chart-click="handleChartClick('散点图')"
    />
  </div>
</template>

<style module>
.container {
  padding: 2rem;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

.chart {
  height: 30rem;
}
</style>

❌