Echarts基础封装

2024/10/20

一、封装组件使用

1.基础组件封装

/components/BaseChart.vue

<template>
  <div ref="chartContainer" :style="{ width: props.width, height: props.height }" class="base-chart"></div>
</template>

<script setup lang="ts">
  import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
  import * as echarts from 'echarts'

  const props = defineProps({
    // 初始化额外配置
    initOpts: {
      type: Object,
      default: () => ({})
    },
    // 图表配置
    option: {
      type: Object,
      required: true,
      default: () => ({})
    },
    width: {
      type: String,
      default: '100%'
    },
    height: {
      type: String,
      default: '400px'
    },
    theme: {
      type: [String, Object],
      default: 'light'
    },
    animation: {
      type: Boolean,
      default: true
    }
  })

  const emit = defineEmits(['chart-init', 'chart-ready', 'chart-click', 'chart-dblclick'])

  const chartContainer = ref(null)
  let chartInstance: echarts.ECharts | null = null

  // 初始化图表
  const initChart = () => {
    if (!chartContainer.value) return

    // 销毁旧实例
    if (chartInstance) {
      chartInstance.dispose()
    }

    try {
      // 创建新实例
      chartInstance = echarts.init(chartContainer.value, props.theme, {
        renderer: 'canvas', // 'svg' 或 'canvas'
        ...props.initOpts
      })

      emit('chart-init', chartInstance)

      // 设置配置项
      chartInstance.setOption(props.option, {
        notMerge: false, // 是否不跟之前设置的 option 进行合并
        lazyUpdate: false // 在设置完 option 后是否不立即更新图表
      })

      // 绑定事件
      bindChartEvents()

      emit('chart-ready', chartInstance)
    } catch (error) {
      console.error('ECharts init error:', error)
    }
  }

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

    // 移除旧监听器
    chartInstance.off('click')
    chartInstance.off('dblclick')

    chartInstance.on('click', (params: echarts.ECElementEvent) => {
      emit('chart-click', params)
    })
    chartInstance.on('dblclick', (params: echarts.ECElementEvent) => {
      emit('chart-dblclick', params)
    })
  }

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

    chartInstance.setOption(props.option, true) // true 表示不合并旧配置
  }

  // 响应窗口大小变化
  const resizeChart = () => {
    if (!chartInstance) return

    chartInstance.resize()
  }

  // 销毁图表
  const disposeChart = () => {
    if (!chartInstance) return

    chartInstance.dispose()
    chartInstance = null
  }

  // 获取图表实例
  const getInstance = () => chartInstance

  // 监听属性变化
  watch(
    () => props.option,
    () => {
      updateChart()
    },
    { deep: true }
  )

  watch(
    () => props.theme,
    () => {
      initChart()
    }
  )

  // 监听容器尺寸变化
  const resizeObserver = new ResizeObserver(() => {
    resizeChart()
  })

  onMounted(() => {
    nextTick(() => {
      initChart()
    })
    if (chartContainer.value) {
      resizeObserver.observe(chartContainer.value)
    }
  })

  onUnmounted(() => {
    disposeChart()
    resizeObserver.disconnect()
  })

  defineExpose({
    getInstance,
    resize: resizeChart,
    dispose: disposeChart,
    update: updateChart
  })
</script>

<style scoped lang="scss">
  .base-chart {
    display: block;
  }
</style>

2.组件使用

通过给BaseChart组件传入option值,即可轻松使用啦

<template>
  <div style="width: 600px">
    <BaseChart :option="chartOption" :height="'400px'" />
  </div>
</template>

<script lang="ts" setup>
  import { ref } from 'vue'
  import BaseChart from '@/components/BaseChart.vue'

  // 图表配置
  const chartOption = ref({
    title: {
      text: '销售统计'
    },
    tooltip: {},
    xAxis: {
      data: ['一月', '二月', '三月', '四月', '五月', '六月']
    },
    yAxis: {},
    series: [
      {
        name: '销量',
        type: 'bar',
        data: [5, 20, 36, 10, 10, 20]
      }
    ]
  })

  // 图表事件处理
  const handleChartReady = chartInstance => {
    console.log('图表准备好了', chartInstance)
  }

  const handleChartClick = params => {
    console.log('点击了图表', params)
  }
</script>

二、封装hooks使用

1.创建 hooks

src/composables/useEChart.js

import { ref, onUnmounted, nextTick } from 'vue';
import * as echarts from 'echarts';

export function useEChart(containerRef, options = {}) {
  const chartInstance = ref(null);
  const { theme, initOpts } = options;

  // 初始化图表
  const initChart = () => {
    if (!containerRef.value) return;
    
    chartInstance.value = echarts.init(containerRef.value, theme, initOpts);
    return chartInstance.value;
  };

  // 设置配置项
  const setOption = (option, notMerge = false) => {
    if (!chartInstance.value) return;
    chartInstance.value.setOption(option, notMerge);
  };

  // 调整大小
  const resize = () => {
    if (!chartInstance.value) return;
    chartInstance.value.resize();
  };

  // 销毁实例
  const dispose = () => {
    if (!chartInstance.value) return;
    chartInstance.value.dispose();
    chartInstance.value = null;
  };

  // 获取实例
  const getInstance = () => chartInstance.value;

  // 自动清理
  onUnmounted(() => {
    dispose();
  });

  return {
    initChart,
    setOption,
    resize,
    dispose,
    getInstance
  };
}

2.使用hooks

<template>
  <div ref="chartContainer" style="width: 600px; height: 400px;"></div>
</template>

<script setup>
  import { ref, onMounted, watch } from 'vue';
  import { useEChart } from '@/composables/useEChart';

  const option = ref({
    title: {
      text: '销售统计'
    },
    tooltip: {},
    xAxis: {
      data: ['一月', '二月', '三月', '四月', '五月', '六月']
    },
    yAxis: {},
    series: [
      {
        name: '销量',
        type: 'bar',
        data: [5, 20, 36, 10, 10, 20]
      }
    ]
  })

const chartContainer = ref(null);
const { initChart, setOption, resize } = useEChart(chartContainer);

onMounted(() => {
  initChart();
  setOption(option.value);
});

watch( option, (newOption) => {
  setOption(newOption, true);
});
</script>