Vue 封装 Echarts 组件

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

html 复制代码
<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>

然后在页面中使用。

html 复制代码
<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>
相关推荐
leoZ2311 小时前
Claude 驱动的全栈开发:Spring Boot + Vue 的 BS 架构实践
vue.js·spring boot·架构
荒诞英雄4 小时前
从 17MB Vendor 大包到按需加载:Vite 多入口 Vue 组件库首屏优化实践
vue.js·vite
jsonbro5 小时前
手把手带你实现发布订阅模式
前端·vue.js·设计模式
Cobyte7 小时前
23.Vue Vapor 组件 attrs 的实现
前端·javascript·vue.js
斯特凡今天也很帅7 小时前
xml页面可以打开,不报错,但是显示数据的地方也不显示,我是怎么解决的
xml·vue.js
柯克七七9 小时前
给老项目加了 TypeScript,本来只想自己爽,结果全公司代码审查标准被我抬高了
前端·vue.js·typescript
自然 醒11 小时前
前端如何实现在线预览office常见文件功能?
前端·vue.js
肉肉不吃 肉11 小时前
前端调试跨域如何解决
前端·vue.js
卓怡学长12 小时前
w268基于springboot + vue 物流系统
java·数据库·vue.js·spring boot·spring·intellij-idea
迅速的自行车1 天前
从一段模板说起
前端·javascript·vue.js