echarts封装

146 阅读1分钟

安装

npm i echarts -s

引用

//main.ts
import * as echarts from 'echarts'
const app = createApp(App)
app.config.globalProperties.$echarts = echarts

export default app

父组件

<template>
  <el-button @click="isShow = !isShow">点击</el-button>

  <div class="echarts-box">
    <!-- 
    theme:图表主题
    isEmpty:图表是否有数据
      -->
    <t-chart
      v-show="isShow"
      :options="options"
      theme="shine"
      :isEmpty="false"
      @chart="chart"
      @click="click"
      @dblclick="addData()"
      @mousedown="mousedown"
      @mousemove="mousemove"
      @mouseover="mouseover"
      @mouseout="mouseout"
      @globalout="globalout"
      @contextmenu="contextmenu"
    />
  </div>
</template>

<script lang="ts" setup>
import { ref, getCurrentInstance } from "vue";
const isShow = ref(true);
import TChart from "../components/TCharts.vue";
const options = ref({
  xAxis: {
    type: "category",
    data: ["Mon", "Tue", "Wed"],
  },
  yAxis: {
    type: "value",
  },
  series: [
    {
      data: [150, 230, 224],
      type: "line",
    },
  ],
});
//获取子组件图表实例
const chart = (chart) => {
  // console.log("实例", chart);
};
//双击添加数据
const addData = () => {
  options.value.xAxis.data.push(
    "test" + Math.random().toString(36).substring(2, 8)
  );
  options.value.series[0].data.push(Math.random() * 200);
};
const click = (e) => {
  console.log("click-----", e);
};
const mousedown = (e) => {
  console.log("mousedown-----", e);
};
const mousemove = (e) => {
  console.log("mousemove-----", e);
};
const mouseover = (e) => {
  console.log("mouseover-----", e);
};
const mouseout = (e) => {
  console.log("mouseout-----", e);
};
const globalout = (e) => {
  console.log("globalout-----", e);
};
const contextmenu = (e) => {
  console.log("contextmenu-----", e);
};
</script>

<style lang="scss" scoped>
.echarts-box {
  width: 500px;
  height: 500px;
  border: 1px solid red;
}
</style>

子组件

<template>
  <!-- v-bind="$attrs" 将父组件传递过来的属性都绑定到子组件的根组件上,可以在不需要prop接收的情况下通过getCurrentInstance().attrs.xxx或this.$attrs.xxx接收属性 -->
  <div class="t-chart" v-bind="$attrs">
    <div
      v-show="!formatEmpty"
      class="t-chart-container"
      :id="id"
      ref="echartRef"
    />
    <slot v-if="formatEmpty" name="empty">
      <el-empty v-bind="$attrs" :description="description" />
    </slot>
    <slot></slot>
  </div>
</template>

<script lang="ts" name="TChart" setup>
import { useResizeObserver } from "@vueuse/core";
import {
  getCurrentInstance,
  onMounted,
  defineProps,
  computed,
  ref,
  markRaw,
  nextTick,
  useAttrs,
  watch,
  onBeforeUnmount,
} from "vue";
import { debounce, toLine } from "@/utils/index";
import app from "@/main";
//接受传递过来的属性
const props = defineProps({
  options: {
    type: Object,
    default: () => ({}),
  },
  id: {
    type: String,
    default: () => Math.random().toString(36).substring(2, 8),
  },
  //   判断是否数据为空,可以是布尔值,也可以是函数
  isEmpty: {
    type: [Boolean, Function],
    default: false,
  },
  description: {
    type: String,
    default: "暂无数据",
  },
  theme: {
    type: String,
    default: "",
  },
});
//proxy提供了对当前组件内属性和方法的访问
console.log(getCurrentInstance());
const { proxy } = getCurrentInstance() as any;
const $echarts = app.config.globalProperties.$echarts;
const echartRef = ref<HTMLDivElement>();
const chart = ref();
const emits = defineEmits();
//获取所有父组件传递的非props的属性,形成键值对数组
const events = Object.entries(useAttrs());
console.log("event", events);

onMounted(() => {
  renderChart();
});
onBeforeUnmount(() => {
  // 取消监听
  // window.removeEventListener('resize', resizeChart)
  // 销毁echarts实例
  chart.value.dispose();
  chart.value = null;
});
//监听options变化
watch(
  () => props.options,
  async (nw) => {
    await nextTick();
    setOption(nw);
  },
  { deep: true }
);
//监听echarts主题变化
watch(
  () => props.theme,
  async () => {
    chart.value.dispose();
    renderChart();
  }
);
//计算是否展示图表
const formatEmpty = computed(() => {
  if (typeof props.isEmpty === "function") {
    return props.isEmpty(props.options);
  }
  return props.isEmpty;
});
//图表初始化
const renderChart = () => {
  //通过markRaw,将echarts实例标记为普通对象,减少响应式带来的损耗。init方法的第二个参数是:当前图表应用的主题 Object | String
  chart.value = markRaw($echarts.init(echartRef.value, props.theme));
  setOption(props.options);
  //返回chart实例
  emits("chart", chart.value);
  //监听图表事件
  events.forEach(([key, value]) => {
    if (key.startsWith("on") && !key.startsWith("onChart")) {
      //以on开头又不是onChart的方法,获取该方法名methodName
      const methodName = toLine(key).substring(3);
      //echart.on('xxx',function)为事件监听
      chart.value.on(methodName, (...args) => emits(methodName, ...args));
    }
  });
  //监听元素变化
  useResizeObserver(echartRef.value, resizeChart);
};
//设置图表函数,防抖
const setOption = debounce(
  async (options) => {
    if (!chart.value) return;
    chart.value.setOption(options, true, true);
    await nextTick();
    resizeChart();
  },
  300,
  true
);
//重绘图表函数
const resizeChart = debounce(
  () => {
    chart.value?.resize();
  },
  300,
  true
);
</script>

<style lang="scss" scoped>
.t-chart {
  position: relative;
  width: 100%;
  height: 100%;
  &-container {
    width: 100%;
    height: 100%;
  }
}
</style>

遇到的问题

  1. props.isEmpty()是函数时,要传递options对象?
  2. main.ts中引入的$echarts,无法在组件实例中访问到,最后只能export出去