首页VueVite+Vue3 + Echarts 封装

Vite+Vue3 + Echarts 封装

分类Vue时间2024-05-23 18:47:55发布RustStream浏览82
摘要:Echarts封装 <template> <div ref="chartRef" class="echarts-container"></div> </template> <script> import * as echarts from "echarts/core"; import { onMounted, onUnmounted, ref, watch } from "vue"; // 引入柱状图图表,图表后缀都为 Chart // import { BarChart, PieChart } from "echarts/charts"; // 标签自动布局、全局过渡动画等特性 import { DatasetComponent, GridComponent, LegendComponent, TitleComponent, TooltipComponent, } from "echarts/components"; import { LabelLayout, UniversalTransition } from "echarts/features"; import { SVGRenderer } from "echarts/renderers"; // 必须显式注册组件 echarts.use([ TitleComponent, TooltipComponent, GridComponent, LegendComponent, DatasetComponent, LabelLayout, UniversalTransition, SVGRenderer, ] ; // 图表类型映射表 const CHART_MODULES = { bar: async ( =</script><!--autointro-->...

Echarts封装

<template>
  <div ref="chartRef" class="echarts-container"></div>
</template>

<script>
import * as echarts from "echarts/core";
import { onMounted, onUnmounted, ref, watch } from "vue";
// 引入柱状图图表,图表后缀都为 Chart
// import { BarChart, PieChart } from "echarts/charts";
// 标签自动布局、全局过渡动画等特性
import {
  DatasetComponent,
  GridComponent,
  LegendComponent,
  TitleComponent,
  TooltipComponent,
} from "echarts/components";
import { LabelLayout, UniversalTransition } from "echarts/features";
import { SVGRenderer } from "echarts/renderers";

// 必须显式注册组件
echarts.use([
  TitleComponent,
  TooltipComponent,
  GridComponent,
  LegendComponent,
  DatasetComponent,
  LabelLayout,
  UniversalTransition,
  SVGRenderer,
]);

// 图表类型映射表
const CHART_MODULES = {
  bar: async () => (await import(/* @vite-ignore */ "echarts/charts")).BarChart,
  line: async () =>
    (await import(/* @vite-ignore */ "echarts/charts")).LineChart,
  pie: async () => (await import(/* @vite-ignore */ "echarts/charts")).PieChart,
  scatter: async () =>
    (await import(/* @vite-ignore */ "echarts/charts")).ScatterChart,
  // gauge: async () =>
  //   (await import(/* @vite-ignore */ "echarts/charts")).GaugeChart,
  // radar: async () =>
  //   (await import(/* @vite-ignore */ "echarts/charts")).RadarChart,
  // treemap: async () =>
  //   (await import(/* @vite-ignore */ "echarts/charts")).TreemapChart,
  custom: () => Promise.resolve(null), // 自定义类型不需要图表
};

// 已加载图表缓存
const loadedCharts = new Set();

export default {
  props: {
    type: {
      type: String,
      default: "line",
      validator: (v) => Object.keys(CHART_MODULES).includes(v),
    },
    data: {
      type: [Array, Object],
      required: true,
    },
    options: {
      type: Object,
      default: () => ({}),
    },
    theme: {
      type: [String, Object],
      default: "light",
    },
    colors: {
      type: Array,
      default: () => ["#5470c6", "#91cc75", "#fac858", "#ee6666", "#73c0de"],
    },
    loading: Boolean,
    autoResize: {
      type: Boolean,
      default: true,
    },
    initOptions: {
      type: Object,
      default: () => ({}),
    },
  },

  emits: ["ready", "click"],

  setup(props, { emit }) {
    const chartRef = ref(null);
    let chartInstance = null;
    let observer = null;

    // 动态加载图表模块
    const loadChartModule = async (type) => {
      try {
        if (loadedCharts.has(type)) return;
        const moduleLoader = CHART_MODULES[type];
        if (!moduleLoader) return;
        const chartModule = await moduleLoader();
        if (chartModule) {
          echarts.use(chartModule);
          loadedCharts.add(type);
        }
      } catch (error) {
        console.error(`[ECharts] Failed to load ${type} chart:`, error);
        emit("error", error);
        if (type != "line") {
          await loadChartModule("line");
        }
      }
    };

    // 基础配置模板
    const baseOptions = {
      color: props.colors,
      tooltip: { trigger: "axis" },
      grid: {
        containLabel: true,
        left: 16,
        right: 16,
        top: 40,
        bottom: 16,
      },
    };

    // 图表类型模板
    const chartTemplates = {
      line: {
        xAxis: { type: "category" },
        yAxis: { type: "value" },
        series: [{ type: "line" }],
      },
      bar: {
        xAxis: { type: "category" },
        yAxis: { type: "value" },
        series: [{ type: "bar" }],
      },
      pie: {
        tooltip: { trigger: "item" },
        series: [
          {
            type: "pie",
            radius: "50%",
            emphasis: { focus: "scale" },
          },
        ],
      },
      scatter: {
        xAxis: { type: "value" },
        yAxis: { type: "value" },
        series: [{ type: "scatter" }],
      },
    };

    // 数据标准化处理
    const normalizeData = (rawData) => {
      if (props.type === "pie") {
        return rawData.map((item) => ({
          name: item.name || item.label,
          value: item.value || item.data,
        }));
      }
      return rawData;
    };

    // 生成最终配置
    const generateOptions = () => {
      const normalizedData = normalizeData(props.data);
      const template =
        props.type === "custom" ? {} : chartTemplates[props.type];

      return {
        ...baseOptions,
        ...template,
        dataset: { source: normalizedData },
        ...props.options,
      };
    };

    // 初始化图表
    const initChart = async () => {
      await loadChartModule(props.type);
      if (!chartRef.value) return;

      // 确保销毁旧实例
      if (chartInstance) {
        chartInstance.dispose();
        chartInstance = null;
      }

      chartInstance = echarts.init(chartRef.value, props.theme, {
        renderer: "svg",
        ...props.initOptions,
      });

      updateChart();
      bindChartEvents();
      setupResizeObserver();
      emit("ready", chartInstance);
    };

    // 更新图表
    const updateChart = () => {
      if (!chartInstance) return;

      const options = generateOptions();
      chartInstance.setOption(options, true);

      props.loading
        ? chartInstance.showLoading("default")
        : chartInstance.hideLoading();
    };

    // 响应式容器尺寸变化
    const setupResizeObserver = () => {
      if (props.autoResize) {
        if (typeof ResizeObserver !== "undefined") {
          observer = new ResizeObserver(() => {
            chartInstance && chartInstance.resize();
          });
          observer.observe(chartRef.value);
        } else {
          window.addEventListener("resize", handleResize);
        }
      }
    };

    // 窗口 resize 处理
    const handleResize = () => {
      chartInstance && chartInstance.resize();
    };

    // 事件绑定
    const bindChartEvents = () => {
      if (!chartInstance) return;

      chartInstance.on("click", (params) => {
        emit("click", params);
      });
    };

    // 生命周期
    onMounted(async () => {
      await initChart();
    });

    onUnmounted(() => {
      observer?.disconnect();
      chartInstance?.dispose();
      window.removeEventListener("resize", handleResize);
    });

    // 监听变化
    watch(() => props.data, updateChart, { deep: true });
    watch(
      () => props.type,
      (newType, oldType) => {
        if (oldType != newType) {
          initChart();
        }
      }
    );
    watch(() => props.loading, updateChart);
    watch(() => props.options, updateChart, { deep: true });

    return { chartRef };
  },
};
</script>

<style scoped>
.echarts-container {
  min-width: 500px;
  min-height: 300px; /*必须明确高度*/
  border: 1px dashed #999;
}
</style>

简单使用

<template>
  <h1>Echarts 使用</h1>
  <div>
    <a-space>
      <a-button type="primary" @click="currentType = 'line'"
        >显示折线图</a-button
      >
      <a-button danger @click="currentType = 'bar'">显示柱状图</a-button>
    </a-space>
    <hr />
    <EChartsWrapper
      :type="currentType"
      theme="light"
      :colors="['#1890ff', '#52c41a']"
      :options="{
        title: {
          text: '柱状图&折线图切换Demo',
          subtext: '示例',
        },
        legend:
          currentType == 'bar'
            ? {
                bottom: '2%',
                data: [
                  'Email',
                  'Union Ads',
                  'Video Ads',
                  'Direct',
                  'Search Engine',
                ],
              }
            : {
                top: '10%',
              },
        series: [
          {
            name: 'Email',
            type: currentType,
            stack: 'Total',
            data: [120, 132, 101, 134, 90, 230, 210],
          },
          {
            name: 'Union Ads',
            type: currentType,
            stack: 'Total',
            data: [220, 182, 191, 234, 290, 330, 310],
          },
          {
            name: 'Video Ads',
            type: currentType,
            stack: 'Total',
            data: [150, 232, 201, 154, 190, 330, 410],
          },
          {
            name: 'Direct',
            type: currentType,
            stack: 'Total',
            data: [320, 332, 301, 334, 390, 330, 320],
          },
          {
            name: 'Search Engine',
            type: currentType,
            stack: 'Total',
            data: [820, 932, 901, 934, 1290, 1330, 1320],
          },
        ],
        grid:
          currentType == 'bar'
            ? {
                top: '20%',
                left: '3%',
                right: '4%',
                bottom: '10%',
                containLabel: true,
              }
            : {
                top: '25%',
                left: '3%',
                right: '4%',
                bottom: '5%',
                containLabel: true,
              },
      }"
      :loading="false"
      @click="handleChartClick"
    />
  </div>
  <div class="demo">
    <div>
      <EChartsWrapper
        type="bar"
        theme="light"
        :data="barData"
        :colors="['#1890ff', '#52c41a']"
        autoResize
        :options="{
          title: {
            text: '苹果销量柱状图',
            subtext: 'Demo',
          },
          grid: {
            top: '25%',
            left: '3%',
            right: '4%',
            bottom: '5%',
            containLabel: true,
          },
        }"
        :loading="false"
        :initOptions="{
          locale: 'EN',
        }"
        @click="handleChartClick"
      />
    </div>
    <div>
      <EChartsWrapper
        type="pie"
        :data="pieData"
        :options="{
          title: {
            text: `饼状图Demo`,
            left: 'center',
          },
          legend: {
            left: 'center',
            top: '10%',
            pageTextStyle: {
              overflow: 'truncate',
            },
          },
        }"
        @click="handleChartClick"
      />
    </div>
  </div>
  <div>
    <EChartsWrapper
      type="pie"
      :data="pieData"
      :options="{
        title: {
          text: `饼状图Demo`,
          left: 'center',
        },
        legend: {
          left: 'center',
          top: '10%',
          pageTextStyle: {
            overflow: 'truncate',
          },
        },
      }"
      @click="handleChartClick"
    />
  </div>
</template>

<script>
import { ref } from "vue";
import EChartsWrapper from "./echarts.vue";

export default {
  components: { EChartsWrapper },
  setup() {
    let currentType = ref("line");
    const barData = ref([
      ["Product", "Sales", "aaa"],
      ["iPhone", 1230, 300],
      ["iPad", 880, 150],
      ["Mac", 660, 200],
      ["Apple Watch", 200, 200],
    ]);
    const pieData = ref([
      { name: "Matcha Latte", value: 93.7 },
      { name: "Milk Tea", value: 55.1 },
      { name: "Cheese Cocoa", value: 82.5 },
      { name: "Walnut Brownie", value: 39.1 },
    ]);

    const handleChartClick = (params) => {
      console.log("Chart clicked:", params);
    };

    return { barData, handleChartClick, pieData, currentType };
  },
};

/**
 * 
 * // 二维数组格式(带表头)
[
  ['Product', 'Sales'],
  ['iPhone', 1000],
  ['iPad', 800]
]

// 对象数组格式(饼图专用)
[
  { name: 'iPhone', value: 1000 },
  { name: 'iPad', value: 800 }
]
 */
</script>

<style>
.demo {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin-top: 10px;
}
</style>

本文链接:https://blog.smallhao.fun/?id=24 转载需授权!

分享到:

Chen’Blog版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

JavaScriptVueVite
Canvas添加水印 Canvas实现简单页面动画

游客 回复需填写必要信息
召唤伊斯特瓦尔