表格图表切换,图表无法展示问题复盘

news2025/4/3 0:57:04
项目背景

103项目CPC卡使用模块在原有的表格展示数据的基础之上,增加环状饼图图表展示,采用tab切换的方式实现

问题描述

图表无法设置宽高,导致饼图无法渲染

具体代码
// 入口页

<el-tabs type="card" class="cts_flex_tabs height-calc">
    <el-tab-pane label="CPC卡使用情况">
        <CtsTable :table-id="tableId"
                :version="version"
                ref="cpcCtsTable"
                class="cpc-use-table"
                :spanMethod="spanMethod"
                :loading="loading"
                :table-data="tableData"
                :add-row-class-name="addRowClassName"
                :columns="columns" />
        
    </el-tab-pane>
    <el-tab-pane label="CPC卡库存情况分析">
        <cpc-inventory ref="cpcInventoryRef" />
    </el-tab-pane>
</el-tabs>

// cpc-inventory组件

<div class="cpc-inventory" ref="inventoryRef">
	<div class="pie-chart" ref="pieChart"></div>
</div>

<style lang="less" scoped>
.cpc-inventory {
    height: 100%;
    width: 100%;
    .pie-chart {
        height: 100%;
        width: 100%;
    }
}
</style>

页面在初始化时会同时加载两个tab,但是CPC卡库存情况分析图表tab中的元素具体的宽高值是无法获取到的(display: none隐藏了),如下图:

在这里插入图片描述

onMounted钩子函数中无论是否使用nextTick,打印出的inventoryRefoffsetHeight都是0,尽管设置了元素的宽高均为100%,但是由于元素没有渲染出来,导致无法获取实际高度,而是高度为0。原本是想通过获取到父元素inventoryRef的具体宽高值,然后设置图表容器pieChart的宽高从而实现图表的正常渲染。

注意:页面初始化时,CPC卡库存情况分析图表组件中的onMounted会执行,但此时无法获取父元素宽高。
解决方案
采用ResizeObserver

ResizeObserver 是浏览器提供的一个 API,用于监听 DOM 元素尺寸的变化(如宽度、高度、边框、内边距等)。它比传统的 window.resizeMutationObserver 更高效,专门用于监测元素的尺寸变化。

代码实现
<div class="cpc-inventory" ref="inventoryRef">
	<div class="pie-chart" ref="pieChart"></div>
</div>

const setupResizeObserver = () => {
	if (!inventoryRef.value) return;
	if (resizeObserver.value) {
		resizeObserver.value.disconnect();
	}
	resizeObserver.value = new ResizeObserver((entries) => {
		for (const entry of entries) {
			const { height } = entry.contentRect;
			if (height > 0 && !chart) {
				initPieChart();
			}
		}
	});
	resizeObserver.value.observe(inventoryRef.value);
};
onMounted(() => {
	setupResizeObserver();
});
onBeforeUnmount(() => {
	if (resizeObserver.value) {
		resizeObserver.value.disconnect();
	}
});
代码解释:

在页面初始化时,钩子函数onMounted中调用setupResizeObserver函数,通过resizeObserver.value.observe(inventoryRef.value)监听inventoryRef元素,当切换tab到CPC卡库存情况分析图表展示组件时,此时能够监测到inventoryRef元素有了宽高值,那么由于设置了宽高为100%,因此子元素pieChart也就有了宽高,就可以进行图表的渲染,至此也就解决了上述的问题。

问题回顾

上面的是解决方案中的一种,还有另外一种方案:为两个tab添加name属性,在图表组件中添加v-if判断,代码如下:

<el-tabs type="card" class="cts_flex_tabs height-calc" v-model="activeTab" @click="...">
    <el-tab-pane label="CPC卡使用情况" name="use">
        <CtsTable :table-id="tableId"
                :version="version"
                ref="cpcCtsTable"
                class="cpc-use-table"
                :spanMethod="spanMethod"
                :loading="loading"
                :table-data="tableData"
                :add-row-class-name="addRowClassName"
                :columns="columns" />
        
    </el-tab-pane>
    <el-tab-pane label="CPC卡库存情况分析" name="inventory">
        <cpc-inventory ref="cpcInventoryRef" v-if="activeTab == 'inventory'" />
    </el-tab-pane>
</el-tabs>

这种方案是通过v-if判断,使得cpc-inventory组件的钩子函数onMounted在页面初始化加载时不会执行,只有切换到此tab时才会执行。然后我们可以在nextTick中进行图表初始化渲染。此方案的劣势是用户从表格切换到图表时,每次都要执行图表的初始化过程。方案1只有初次加载图表时才会进行初始化过程,后续切换只是完成数据更新,用户无法看到图标初始化过程。

在这里插入图片描述

注意

Node.js 或特殊环境不支持或者无法识别**ResizeObserver**,会有报错信息:Cannot find name 'ResizeObserver'. Did you mean 'resizeObserver'?

解决方案:

  1. 确保 tsconfig.json 包含 DOM 类型(推荐)
    {
      "compilerOptions": {
        "lib": ["dom", "es6"] // 确保包含 "dom"
      }
    }
    
  2. 有时方案1没生效,则需要安装 @types/resize-observer-browser(适用于某些特殊环境)

    npm install --save-dev @types/resize-observer-browser
    # 或
    yarn add --dev @types/resize-observer-browser
    

    在页面中引入

    <script>
    ....
    import ResizeObserver from "resize-observer-polyfill";
    ....
    
    </script>
    
适用场景

表格展示与图表展示通过tab组件切换(或者通过v-show),而不是通过v-if的方式动态设置显示与隐藏

完整代码分享
// 父组件

<el-tabs type="card" class="cts_flex_tabs height-calc">
    <el-tab-pane label="CPC卡使用情况">
        <CtsTable :table-id="tableId"
                :version="version"
                ref="cpcCtsTable"
                class="cpc-use-table"
                :spanMethod="spanMethod"
                :loading="loading"
                :table-data="tableData"
                :add-row-class-name="addRowClassName"
                :columns="columns" />
        
    </el-tab-pane>
    <el-tab-pane label="CPC卡库存情况分析">
        <cpc-inventory ref="cpcInventoryRef" />
    </el-tab-pane>
</el-tabs>

...
// 查询获取列表数据
listRefresh(pageIndex = 1) {
  this.loading = true
  let params = {
    ...api.getCpcUseInfoReportList,
    data: {
        ...this.searchForm,
        startHour: parseInt(this.searchForm.startHour.split(':')[0], 10),
        endHour: parseInt(this.searchForm.endHour.split(':')[0], 10),
    },
  }
  request(params)
      .then((data) => {
        this.loading = false
        this.isBtnLoader = false
        this.tableData = data.data
        this.$refs.cpcInventoryRef.updateData(this.tableData, this.searchForm.cityCode);
      })
      .catch((error) => {
        this.loading = false
        this.isBtnLoader = false
        this.$message.error(error.message)
      })
},
// 图表组件

<template>
	<div class="cpc-inventory" ref="inventoryRef">
		<div class="pie-chart" ref="pieChart"></div>
	</div>
</template>

<script lang='ts'>
import useCommon from "@/hooks/use-common";
import {
	ref,
	reactive,
	defineComponent,
	onMounted,
	computed,
	toRefs,
	watch,
	onBeforeUnmount,
} from "@vue/composition-api";
import useResizeSearch from "@/hooks/use-resizeSearch";
import api from "@/api";
import request from "@/axios/fetch";
import * as echarts from "echarts";
import ResizeObserver from "resize-observer-polyfill";

export default defineComponent({
	name: "CpcInventory",
	components: {},
	props: {},
	setup(props, { emit }) {
		const { proxy } = useCommon(); // 作为this使用
		const { isXLCol } = useResizeSearch();
		const pieChart = ref(null);
		let chart = null;
		const inventoryRef = ref(null);
		const resizeObserver = ref(null);
		const chartData = ref([]); // 饼图数据
		const centerShowInfo = reactive({
			name: "浙江省中心",
			kc: 0,
			pb: 0,
		});
		const setCenterShwoInfo = ({ city, inventory, matching }) => {
			centerShowInfo.name = city;
			centerShowInfo.kc = inventory;
			centerShowInfo.pb = matching;
		};
		const initPieChart = () => {
			if (pieChart.value) {
				chart = echarts.init(pieChart.value);
				const option = {
					tooltip: {
						trigger: "item",
						renderMode: "html", // 启用 HTML 模式
						formatter: ({ data }) => {
							return `
                        <div style="font-size: 14px;">
                            <p style="font-weight: bold; margin: 0 0 5px 0;">${data.name}</p>
                            <p style="margin: 2px 0;">库存: ${data.value}</p>
                            <p style="margin: 2px 0;">配比: ${data.pb}</p>
                            <p style="margin: 2px 0;">建议调入量: ${data.jytrl}</p>
                        </div>
                        `;
						},
						// 定义富文本样式
						rich: {
							name: {
								fontSize: 16,
								fontWeight: "bold",
								color: "#333",
								padding: [0, 0, 5, 0],
							},
							value: {
								color: "#666",
								fontSize: 14,
							},
							ratio: {
								color: "#ff9900",
								fontSize: 14,
							},
							suggest: {
								color: "#ff0000",
								fontSize: 14,
								fontWeight: "bold",
							},
						},
					},
					legend: {
						orient: "vertical",
						bottom: "bottom",
					},
					series: [
						{
							name: "",
							type: "pie",
							radius: ["30%", "40%"],
							data: chartData.value,
							padAngle: 0,
							// value 字段是必需的,用于确定扇形区域的大小。
							// 可以通过 formatter 自定义标签内容,展示其他字段。
							label: {
								show: true,
								position: "outside", // 标签显示在外部
								formatter: function ({ data }) {
									if (data.jytrl > 0) {
										return `{name|${data.name}}\n{value|库存: ${data.value}} {ratio|配比: ${data.pb}} {desc|建议调入量: ${data.jytrl}}`;
									} else if (data.jytrl < 0) {
										return `{negativeName|${data.name}}\n{negativeValue|库存: ${data.value}} {negativeRatio|配比: ${data.pb}} {negativeDesc|建议调入量: ${data.jytrl}}`;
									}
									return `{zeroName|${data.name}}\n{zeroValue|库存: ${data.value}} {zeroRatio|配比: ${data.pb}} {zeroDesc|建议调入量: ${data.jytrl}}`;
								},
								rich: {
									name: {
										fontSize: 14,
										fontWeight: "bold",
										lineHeight: 24,
										color: "#F56C6C",
									},
									value: {
										fontSize: 12,
										color: "#F56C6C",
										lineHeight: 20,
									},
									ratio: {
										fontSize: 12,
										color: "#F56C6C",
										lineHeight: 20,
									},
									desc: {
										fontSize: 12,
										color: "#F56C6C",
										lineHeight: 20,
									},
									negativeName: {
										fontSize: 14,
										fontWeight: "bold",
										lineHeight: 24,
										color: "#67C23A",
									},
									negativeValue: {
										fontSize: 12,
										color: "#67C23A",
										lineHeight: 20,
									},
									negativeRatio: {
										fontSize: 12,
										color: "#67C23A",
										lineHeight: 20,
									},
									negativeDesc: {
										fontSize: 12,
										color: "#67C23A",
										lineHeight: 20,
									},
									zeroName: {
										fontSize: 14,
										fontWeight: "bold",
										lineHeight: 24,
										color: "#333",
									},
									zeroValue: {
										fontSize: 12,
										color: "#333",
										lineHeight: 20,
									},
									zeroRatio: {
										fontSize: 12,
										color: "#333",
										lineHeight: 20,
									},
									zeroDesc: {
										fontSize: 12,
										color: "#333",
										lineHeight: 20,
									},
								},
							},
							labelLine: {
								show: true, // 显示引导线
							},
							emphasis: {
								itemStyle: {
									shadowBlur: 10,
									shadowOffsetX: 0,
									shadowColor: "rgba(0, 0, 0, 0.5)",
								},
							},
						},
					],
					graphic: {
						type: "text",
						left: "center",
						top: "center",
						style: {
							text: [
								`{title|${centerShowInfo.name}}`,
								`{value|库存: ${centerShowInfo.kc}}`,
								`{desc|配比: ${centerShowInfo.pb}}`,
							].join("\n"), // 用 \n 换行
							rich: {
								// 定义每一行的样式
								title: {
									fontSize: 24,
									fontWeight: "bold",
									color: "#333",
									lineHeight: 36,
									textAlign: "center",
								},
								value: {
									fontSize: 16,
									color: "#333",
									lineHeight: 30,
									textAlign: "center",
								},
								desc: {
									fontSize: 16,
									color: "#333",
									lineHeight: 30,
									textAlign: "center",
								},
							},
						},
					},
				};
				chart.setOption(option);
			}
		};
		const updateData = (val, isSingleCity) => {
			if (val.length) {
				chartData.value = val
					.filter((ele) => ele.city)
					.map((item) => ({
						value: item.inventory,
						name: item.city,
						pb: item.matching,
						jytrl: item.suggest,
					}));
				if (isSingleCity) {
					// 单区域
					const totalInfo = val.find((ele) => ele.area == "合计");
					setCenterShwoInfo(totalInfo);
				} else {
					// 全域
					const totalInfo = val.find((ele) => ele.area == "总计");
					setCenterShwoInfo({ ...totalInfo, city: "浙江省中心" });
				}
			} else {
				chartData.value = [];
				setCenterShwoInfo({ city: "", inventory: 0, matching: 0 });
			}

			if (chart) {
				chart.clear();
				initPieChart();
			}
		};

		const setupResizeObserver = () => {
			if (!inventoryRef.value) return;
			if (resizeObserver.value) {
				resizeObserver.value.disconnect();
			}
			resizeObserver.value = new ResizeObserver((entries) => {
				for (const entry of entries) {
					const { height } = entry.contentRect;
					if (height > 0 && !chart) {
						initPieChart();
					}
				}
			});
			resizeObserver.value.observe(inventoryRef.value);
		};

		onMounted(() => {
			setupResizeObserver();
		});
		onBeforeUnmount(() => {
			if (resizeObserver.value) {
				resizeObserver.value.disconnect();
			}
			if (chart) {
				chart.dispose();
				chart = null;
			}
		});
		return {
			pieChart,
			inventoryRef,
			updateData,
		};
	},
});
</script>

<style lang="less" scoped>
.cpc-inventory {
    height: 100%;
    width: 100%;
    .pie-chart {
        height: 100%;
        width: 100%;
    }
}
</style>

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

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

相关文章

AI赋能python数据处理、分析与预测操作流程

以数据集预测鱼类种类(Species)开展以下研究。数据格式如下: 以下是一个系统的分析思路及推荐的机器学习算法: 1. 数据预处理与探索性分析 缺失值与异常值处理: 检查数据完整性(如Roach类中Weight=0的记录需修正或删除)。 通过箱线图或Z-Score检测异常值,判断是否需…

基于74LS192的十进制两位数正向计时器(proteus仿真)

在数字电路设计中&#xff0c;计时器是一个非常常见的应用。今天&#xff0c;我将分享一个基于 74LS192 双向计数器 的十进制两位数正向计时器电路设计。这个电路可以实现从 00 到 99 的十进制正向计数&#xff0c;并通过两个七段数码管显示结果。 最终效果如图&#xff1a; 各…

算法-前缀和与差分

一、前缀和&#xff08;Prefix Sum&#xff09; 1. 核心思想 前缀和是一种预处理数组的方法&#xff0c;通过预先计算并存储数组的前缀和&#xff0c;使得后续的区间和查询可以在**O(1)**时间内完成。 2. 定义 给定数组 nums&#xff0c;前缀和数组 prefixSum 的每个元素 p…

React(六)React过渡动画-CSS编写方式

React过渡动画 react-transition-group介绍 在开发中&#xff0c;我们想要给一个组件的显示和消失添加某种过渡动画&#xff0c;提高用户体验→可通过react-transition-group实现。React曾为开发者提供过动画插件 react-addons-css-transition-group&#xff0c;后由社区维护…

第十五章:Python的Pandas库详解及常见用法

在数据分析领域&#xff0c;Python的Pandas库是一个不可或缺的工具。它提供了高效的数据结构和数据分析工具&#xff0c;使得数据处理变得简单而直观。本文将详细介绍Pandas库的基本功能、常见用法&#xff0c;并通过示例代码演示如何使用Pandas进行数据处理。最后&#xff0c;…

libva基础

Libva&#xff08;Lib Video Acceleration&#xff09;是一个开源的库&#xff0c;实现了 **VA-API**&#xff08;Video Acceleration API&#xff09;&#xff0c;旨在为视频处理提供跨平台的硬件加速支持。 1、核心功能与作用 硬件加速抽象层&#xff1a;Libva 作为中间层&…

c++游戏开发第一期

以后我将要发c游戏开发的教程&#xff0c;可能更得比较慢。&#xff08;目测几个星期一更&#xff09;。 今天先讲个配置编译器。 我用的是Visual studio 2022和EasyX。 安装studio&#xff1a; 首先找到下载链接&#xff08;点我&#xff09;下拉找到下面图片的东西。 下完…

Elasticsearch:人工智能时代的公共部门数据治理

作者&#xff1a;来自 Elastic Darren Meiss 人工智能&#xff08;AI&#xff09;和生成式人工智能&#xff08;GenAI&#xff09;正在迅速改变公共部门&#xff0c;从理论探讨走向实际应用。正确的数据准备、管理和治理将在 GenAI 的成功实施中发挥关键作用。 我们最近举办了…

低功耗LPWAN模块开发指南:远距离无线通信与边缘计算融合实战‌

在远程资产追踪、野外环境监测等场景中&#xff0c;稳定可靠的长距离通信与超低功耗是系统设计的核心挑战。eFish-SBC-RK3576通过 ‌原生双UART接口 USB OTG扩展能力‌ &#xff0c;可无缝集成主流LPWAN模组&#xff08;LoRa/NB-IoT&#xff09;&#xff0c;实现“数据采集-边…

【超详细教程】2025年3月最新Pytorch安装教程(同时讲解安装CPU和GPU版本)

目录 一、前言二、pytorch简介三、安装准备工作3.1、下载Anaconda 四、判断是否有NVIDIA显卡五、安装pytorch-CPU版本六、安装pytorch-GPU版本6.1、查看CUDA显卡驱动版本6.2、安装CUDA6.3、安装CuDNN&#xff08;加速器&#xff09;6.4、安装pytorch-GPU6.5 其他方法安装注意 七…

虚拟电商-话费充值业务(二)话费充值对接供应商模块开发

一、对接供应商模块开发 供应商对接模块chongba_recharge_supplier主要负责的就是调用外部的供应商系统进行充值下单&#xff0c;这种调用是一种基于HTTP协议的调用。 此外在供应商对接模块中主要是实现的业务逻辑有&#xff1a; 1&#xff1a;余额或押金不足情况下的失败轮…

c#winform,倒鸭子字幕效果,typemonkey字幕效果,抖音瀑布流字幕效果

不废话 直接上效果图 C# winform 开发抖音的瀑布流字幕。 也是typemonkey插件字幕效果 或者咱再网上常说的倒鸭子字幕效果 主要功能 1&#xff0c;软件可以自定义添加字幕内容 2&#xff0c;软件可以添加字幕显示的时间区间 3&#xff0c;可以自定义字幕颜色&#xff0c;可以随…

游戏被外挂攻破?金融数据遭篡改?AI反作弊系统实战方案(代码+详细步骤)

一、背景与需求分析 随着游戏行业与金融领域的数字化进程加速,作弊行为(如游戏外挂、金融数据篡改)日益复杂化。传统基于规则的防御手段已难以应对新型攻击,而AI技术通过动态行为分析、异常检测等能力,为安全领域提供了革命性解决方案。本文以游戏反作弊系统和金融数据安…

晶晨S905L3A(B)-安卓9.0-开启ADB和ROOT-支持IPTV6-支持外置游戏系统-支持多种无线芯片-支持救砖-完美通刷线刷固件包

晶晨S905L3A(B)-安卓9.0-开启ADB和ROOT-支持IPTV6-支持外置游戏系统-支持多种无线芯片-支持救砖-完美通刷线刷固件包 适用型号&#xff1a;M401A、CM311-1a、CM311-1sa、B863AV3.1-M2、B863AV3.2-M、UNT403A、UNT413A、M411A、E900V22C、E900V22D、IP112H等等晶晨S905L3A(B)处…

AI来了,新手如何着手学习软件开发?

AI时代新手学习软件开发的7步进化指南 &#xff08;附具体工具与避坑策略&#xff09; 一、建立“人机协作”学习观 AI是教练&#xff0c;不是替身 正确姿势&#xff1a;用AI辅助理解概念&#xff08;如让DeepSeek 、ChatGPT用生活案例解释递归&#xff09;&#xff0c;但坚持手…

《K230 从熟悉到...》矩形检测

《K230 从熟悉到...》矩形检测 《庐山派 K230 从熟悉到...》矩形检测 矩形检测技术是一种广泛应用于电子图像处理的核心技术。它通过识别和分析图像中的矩形结构&#xff0c;为各种应用提供基础支持。从传统图像处理算法到现代深度学习技术&#xff0c;矩形检测的实现途径多种多…

3. 第三放平台部署deepseek

有时候我们会发现使用deepseek服务器&#xff0c;异常卡顿&#xff0c;这是由于多方面原因造成的&#xff0c;比如说访问人数过多等。想要解决这个问题&#xff0c;我们可以选择第三方平台进行部署 第三方平台 我们可以选择的第三方平台很多&#xff0c;比如硅基流动、秘塔搜索…

【C++指针】搭建起程序与内存深度交互的桥梁(下)

&#x1f525;&#x1f525; 个人主页 点击&#x1f525;&#x1f525; 每文一诗 &#x1f4aa;&#x1f3fc; 往者不可谏&#xff0c;来者犹可追——《论语微子篇》 译文&#xff1a;过去的事情已经无法挽回&#xff0c;未来的岁月还可以迎头赶上。 目录 C内存模型 new与…

IEEE PDF Xpress校验出现 :字体无法嵌入问题以及pdf版本问题

文章目录 问题描述一、字体嵌入问题首先查看一下&#xff0c;哪些字体没有被嵌入查看window的font文件夹里的字体下载字体的网站修复字体嵌入问题 二、pdf版本不对 问题描述 在处理IEEE的camera ready的时候&#xff0c;提交到IEEE express的文件没有办法通过validate&#xf…

cookie详解

一、cookie出现原因 http是无状态的&#xff0c;浏览器无法记录当前是哪个人浏览的&#xff0c;所以出现了cookie 作用&#xff1a;会话状态管理&#xff08;用户登录状态、购物车、游戏分数&#xff09;、个性化设置&#xff08;主题、自定义设置&#xff09;、浏览器行为跟…