记录--Vue3 封装 ECharts 通用组件

news2024/7/4 15:49:15

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

按需导入的配置文件

配置文件这里就不再赘述,内容都是一样的,主打一个随用随取,按需导入。

import * as echarts from "echarts/core";
// 引入用到的图表
import { LineChart, type LineSeriesOption} from "echarts/charts";
// 引入提示框、数据集等组件
import {
  TitleComponent,
  TooltipComponent,
  GridComponent,
  LegendComponent,
  type TooltipComponentOption,
  type TitleComponentOption,
  type GridComponentOption,
  type LegendComponentOption
} from "echarts/components";
// 引入标签自动布局、全局过渡动画等特性
import { LabelLayout } from "echarts/features";
// 引入 Canvas 渲染器,必须
import { CanvasRenderer } from "echarts/renderers";

import type { ComposeOption } from "echarts/core";

// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type ECOption = ComposeOption<
  | LineSeriesOption
  | GridComponentOption
  | TitleComponentOption
  | TooltipComponentOption
  | LegendComponentOption
>;

// 注册必须的组件
echarts.use([
  LineChart,
  TitleComponent,
  TooltipComponent,
  GridComponent,
  CanvasRenderer,
  LabelLayout,
  LegendComponent
]);

export default echarts;

基本封装

DOM结构和实例化

<script setup lang="ts">
import { Ref, onMounted, onBeforeUnmount } from "vue";
import { type EChartsType } from "echarts/core";

interface Props {
  option: ECOption;
  theme?: Object | string; // 主题
}

const props = withDefaults(defineProps<Props>(), {
  theme: null
});

const chartRef = ref<Ref<HTMLDivElement>>(null);
const chartInstance = ref<EChartsType>();

// 绘制
const draw = () => {
  if (chartInstance.value) {
    chartInstance.value.setOption(props.option, { notMerge: true });
  }
};

// 初始化
const init = () => {
  if (!chartRef.value) return;

  // 校验 Dom 节点上是否已经挂载了 ECharts 实例,只有未挂载时才初始化
  chartInstance.value = echarts.getInstanceByDom(chartRef.value);
  if (!chartInstance.value) {
    chartInstance.value = echarts.init(
        chartRef.value,
        props.theme,
        { renderer: "canvas" }
    );

    draw();
  }
};

watch(props, () => {
  draw();
});

onMounted(() => {
  init();
});

onBeforeUnmount(() => {
  // 容器被销毁之后,销毁实例,避免内存泄漏
  chartInstance.value?.dispose();
});
</script>

<template>
  <div id="echart" ref="chartRef" :style="{ width: '100px', height: '120px' }" />
</template>

chartRef:当前的 DOM 节点,即 ECharts 的容器;

chartInstance:当前 DOM 节点挂载的 ECharts 实例,可用于调用实例上的方法,注册事件,自适应等;

draw:用于绘制 ECharts 图表,本质是调用实例的 setOption 方法;

init:初始化,在此获取 DOM 节点,挂载实例,注册事件,并调用 draw 绘制图表。

Cannot read properties of undefined (reading 'type')

请注意,上述代码目前还不能正常运行,这里会遇到第一个坑 —— 图表无法显示,这是 React 中没有碰到的:

 出现这种问题是因为,我们使用 ref 接收了 echarts.init 的实例。这会导致 chartInstance 被代理成为响应式对象,影响了 ECharts 对内部属性的访问。Echarts 官方 FAQ 也阐述了该问题:

所以,我们有两种解决方法:

  1. 使用 shallowRef 替换 ref
  2. 使用 ref + markRaw

shallowRef 和 ref() 不同之处在于,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。

而 markRaw 则会将一个对象标记为不可被转为代理。返回该对象本身。在有些值不应该是响应式的场景中,例如复杂的第三方类实例或 Vue 组件对象,这很有用。

 我们这里使用 markRaw 对 init 进行包裹:

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

窗口防抖自适应

这里和 React 中就差不多了,主要安利一个 Vue 官方团队维护的 hooks 库:vueuse 。和 React 中的 ahooks 一样,封装了很多实用的 hooks,我们可以使用 useDebounceFn 来优化自适应函数:

import { useDebounceFn } from "@vueuse/core";

// 窗口自适应并开启过渡动画
const resize = () => {
  if (chartInstance.value) {
    chartInstance.value.resize({ animation: { duration: 300 } });
  }
};

// 自适应防抖优化
const debouncedResize = useDebounceFn(resize, 500, { maxWait: 800 });

onMounted(() => {
  window.addEventListener("resize", debouncedResize);
});

onBeforeUnmount(() => {
  window.removeEventListener("resize", debouncedResize);
});

额外监听宽高

目前,图标的大小还是写死的,现在我们支持 props 传递宽高来自定义图表大小:

interface Props {
  option: ECOption;
  theme?: Object | string;
  width: string;
  height: string;
}

<template>
  <div
    id="echart"
    ref="chartRef"
    :style="{ width: props.width, height: props.height }"
  />
</template>

请注意:在使用时,我们必须指定容器的宽高,否则无法显示,因为图表在绘制时会自动获取父容器的宽高。

flex/grid 布局下 resize 失效的问题

这个问题刚遇到着实有点蛋疼,摸了蛮久,而 bug 触发的条件也比较奇葩,但也比较常见:

  1. 在父组件中,复用多个 ECharts 组件;
  2. 使用了 flex 或 grid 这种没有明确给定宽高的布局;

此时会发现:当前窗口放大,正常触发 resize, 图表会随之放大。但是,此时再缩小窗口,虽然也会触发 resize,但是图表的大小却缩不回来了......

一开始还以为是我封装的写法有问题,直到搜到了ECharts 官方的 issues 才发现原来不止我一个遇到了😂

我的理解是:首先,无论什么布局 echarts 取的都是 dom 的 clientWidth 和 clientHeight 作为容器宽高。其次,由于 flex、grid 这种布局可以不需要显示地指定 width、height,这就导致 echarts 在自适应的过程中无法明确地获取到容器的宽高,所以即便触发了 resize 事件,但是重绘的图表还是之前默认的宽高。

解决方案

给每个 flex-itemgrid-item 自适应的宽或者高都设置一个最小值(我项目中的宽是自适应的,高度是固定的):

.chart-item {
    flex: 1;
    min-width: 30vh;
    height: 300px;
}

这里不得不吐槽下,早在2017年就有人提出过这个问题,2020年终于给出了解释,但是现在都2023了,这个问题还没有得到解决,issues 还 open 着 ☹️

绑定鼠标事件

我们可以给图表中的一些组件添加额外的交互,比如给 title 鼠标 hover 事件等,记得在需要使用事件的组件上添加 triggerEvent: true 属性。

我们演示鼠标移入 title 显示 y轴 name,鼠标移出 title 隐藏 y轴 name 的需求:

interface Props {
  // 略...
  onMouseover?: (...args: any[]) => any;
  onMouseout?: (...args: any[]) => any;
}

const init = () => {
    // 略......

    // 绑定 mousehover 事件:
    if (props.onMouseover) {
      chartInstance.value.on("mouseover", (event: Object) => {
        props.onMouseover(event, chartInstance.value, props.option);
      });
    }
    
    // 绑定 mouseout 事件:
    if (props.onMouseout) {
      chartInstance.value.on("mouseout", (event: Object) => {
        props.onMouseout(event, chartInstance.value, props.option);
      });
    }
  }
};
在上述注册的回调事件中,我们将 ECharts 实例和传入的 option 重新传出去,这样可以就在外面重新配置 option 并调用实例的方法进行图表的重绘了:
import Chart from "@/components/BaseChart/index.vue";
import type { EChartsType } from "echarts/core";
import type { ECOption } from "@/components/BaseChart/config";
import type { YAXisOption } from "echarts/types/dist/shared";

// 鼠标移入,显示y轴 name
const onMouseover = (chart: EChartsType, option: ECOption) => {
  (option.yAxis as YAXisOption).nameTextStyle.color = "#ccc";
  // 重绘图表
  chart.setOption(option);
};

// 鼠标移出,隐藏y轴 name
const onMouseout = (chart: EChartsType, option: ECOption) => {
  (option.yAxis as YAXisOption).nameTextStyle.color = "transparent";
  chart.setOption(option);
};

<template>
    <Chart
        width="100%"
        height="305px"
        :option="{
            // 略......
            title: {
                text: "标题",
                triggerEvent: true
            },
        }"
        :on-mouseover="onMouseover"
        :on-mouseout="onMouseout"
    />
</template>

展示 loading 动画

支持受控的 loading 动画

interface Props {
  // 略...
  loading?: boolean; // 受控
}

const props = withDefaults(defineProps<Props>(), {
  theme: null,
  loading: false
});

watch(
  () => props.loading,
  loading => {
    loading
      ? chartInstance.value.showLoading()
      : chartInstance.value.hideLoading();
  }
);

暴露实例方法

对父组件暴露获取 ECharts 实例的方法,让父组件可直接通过实例调用原生函数。

defineExpose({
  getInstance: () => chartInstance.value,
  resize,
  draw
});

顺便提一下, defineExpose 是在 <script setup> 才能使用的编译器宏,用来显式指定需要暴露给父组件的属性。

完整代码

太长了,贴出来没人会细看,有需要的直接自取,亲测有效,启动项目就能看到,快去魔改吧 ☞github

本文转载于:

https://juejin.cn/post/7245183742264377401

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/670073.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ctfshow web入门 php特性 web93-97

1.web93 intval($num,0),0代表根据变量类型进行使用哪一种进制进行取整 可以使用8进制&#xff0c;正负数&#xff0c;小数点 payload: 010574 4476.0 4476.0 2.web94 过滤了0&#xff0c;不能使用8进制了&#xff0c;还可以使用小数点&#xff0c;正负数等 payload&#xff1…

【Java算法题】剑指offer_算法之02动态规划

对于动态规划问题&#xff0c;我将拆解为如下五步曲&#xff0c;这五步都搞清楚了&#xff0c;才能说把动态规划真的掌握了&#xff01; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 JZ42 连续子数组的…

CTFshow-pwn入门-栈溢出pwn35-pwn36

pwn35 首先还是先下载pwn文件拖进虚拟机加上可执行权限&#xff0c;使用checksec命令查看文件的信息。 chmod x pwn checksec pwn32位的我们直接拖进ida中反编译&#xff1a; // main int __cdecl main(int argc, const char **argv, const char **envp) {FILE *stream; // […

阿里云 OSS介绍

1、什么是阿里云 OSS&#xff1f; OSS 为 Object Storage Service&#xff0c;即对象存储服务。是阿里云提供的海量、安全、低成本、高可靠的云存储服务。 OSS 具有与平台无关的 RESTful API 接口&#xff0c;可以在任意应用、任意时间、任意地点 存储与访问 任何类型的数据。…

软考:软件工程:软件可行性分析,需求分析,ER实体图,数据流图,状态转换图,数据字典

软考&#xff1a;软件工程: 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准备的 &#xff08;1&#…

微信小程序——分页组件的创建与使用

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

使用Aspose.Words将word转PDF并且去水印。

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;Java、工具类、转换、word转pdf、Aspose.Words、后端☀️每日 一言&#xff1a;只要思想不滑坡&#xff0c;办法总比困难多。 前言 在我们日常开发中经常会有将word文档转为PDF的场景&#xff0…

深入理解循环队列----循环数组实现ArrayDeque

我们知道队列这种数据结构的物理实现方式主要还是两种&#xff0c;一种是链队列&#xff08;自定义节点类&#xff09;&#xff0c;另一种则是使用数组实现&#xff0c;两者各有优势。此处我们将要介绍的循环队列其实是队列的一种具体实现&#xff0c;由于一般的数组实现的队列…

IP地址分类

IP地址是TCP/IP协议中非常关键的要素之一&#xff0c;它用于标识网络中的设备和主机。根据使用版本的不同&#xff0c;IP地址分为IPv4和IPv6两种类型。 IPv4&#xff08;Internet Protocol version 4&#xff09;是IP地址的第四个版本&#xff0c;采用32位二进制数来表示IP地址…

JavaWeb之EL表达式和JSTL标签库

文章目录 EL表达式基本介绍EL表达式搜索域数据的顺序EL表达式输出Bean的普通属性&#xff0c;数组属性&#xff0c;List集合属性&#xff0c;Map集合属性EL表达式 - 运算关系运算逻辑运算算数运算empty运算三元运算. 点运算 和 [] 中括号运算符 EL表达式的11个隐含对象EL获取四…

Java中Thread 类的五种基本用法(简介)

目录 一.线程创建 Lambda创建一个线程 基础格式 举例 运行结果 二.线程中断 第一种:设置变量方法 举例 运行结果 第二种:interrupted&#xff08;&#xff09;方法 举例 运行结果 三.线程等待 举例 运行结果 四.线程休眠 举例 五.获取线程实例 举例 运行结果 …

如何用梯度下降法求解数学建模的拟合问题——以logistics增长问题为例

引言 众所周知的是&#xff0c;在大学课程中一般只会教授一种拟合方法(也即参数估计方法)——最小二乘法。这是一种直接求解的方法&#xff0c;非常的有效&#xff0c;不仅是损失最小解&#xff0c;而且是最大似然解。只不过&#xff0c;有一个缺点&#xff0c;它只能解决线性…

Jenkins配置仅合并代码后触发流水线

使用GitLabJenkins集成&#xff0c; 使用Jenkins的Generic WebHook插件&#xff1b;此插件可以作为各个工具间集成使用的通用方式&#xff0c;但是遇到些场景需要写些代码。关于 “合并代码后触发Pipeline”的配置方式&#xff0c; 其实思路简单&#xff0c;实现和让我描述起来…

电脑怎样连接打印机?分享4个简单操作!

为了更方便学习&#xff0c;我买了一个打印机来打印需要用的资料&#xff0c;但是操作了半天还是没连接上&#xff0c;想请问一下有经验的朋友是怎么将打印机与电脑进行连接的呢&#xff1f; 在现代人的工作和生活中&#xff0c;打印机是一个重要的设备。我们可以利用打印机进行…

一文搞懂String、StringBuffer、StringBuilder三者的对比以及扩容机制

String:不可变的字符序列&#xff1b;底层使用char[]存储StringBuffer:可变的字符序列&#xff1b;线程安全的&#xff0c;效率低&#xff1b;底层使用char[]存储StringBuilder:可变的字符序列&#xff1b;jdk5.0新增的&#xff0c;线程不安全的&#xff0c;效率高&#xff1b;…

行为型设计模式09-中介者模式

&#x1f9d1;‍&#x1f4bb;作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github &#x1f389;公众号&#xff1a;猫十二懿 中介者模式 1、中介者模式介绍 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为设计模…

【MySQL数据库】MySQL 高级SQL 语句一

[TOC](MySQL 高级SQL 语句 一、MySQL 高级SQL 语句1.1select -显示表格中一个或数个字段的所有数据记录1.2distinct不显示重复的数据记录1.3where有条件查询1.4and、or且 或1.5in 显示已知的值的数据记录1.6between 显示两个值范围内的数据记录1.7通配符&#xff0c;通常通配符…

都2023年了,JavaScript ES6后的新(lao)特性,你用起来了吗?

前言 JavaScript ES6 指的是 ECMAScript 6&#xff0c;它是 JavaScript 语言第六版的规范。ES6 包含了很多新特性和语法糖&#xff0c;涵盖了从 ES6 开始至今所增加的所有特性。 因此&#xff0c;ES6 新特性是指从 ES6 开始新增到当前时刻所有的新特性&#xff0c;包括但不限…

FPGA XDMA 中断模式实现 PCIE X8 HDMI视频采集 提供工程源码和QT上位机源码

目录 1、前言2、我已有的PCIE方案3、PCIE理论4、总体设计思路和方案视频采集和缓存XDMA简介XDMA中断模式QT上位机及其源码 5、vivado工程详解6、上板调试验证7、福利&#xff1a;工程代码的获取 1、前言 PCIE&#xff08;PCI Express&#xff09;采用了目前业内流行的点对点串…

四肽-21——改善皮肤紧实感、光滑感和弹性

简介 四肽-21是一种来源于皮肤自身的四胜肽&#xff0c;它结构独特、能高效的促进细胞外基质合成&#xff0c;从而减少各种皱纹和改善皮肤衰老现象。与市场上非常受欢迎的基肽(Matrixyl&#xff09;相比&#xff0c;四肽-21效果更为突出。 Tetrapeptide-21 is a type of tetra…