当前位置: 首页 > news >正文

【web应用】若依框架前端报表制作与导出全攻略(ECharts + html2canvas + jsPDF)

文章目录

    • 前言
    • 一、ECharts准备工作
      • 1. 检查ECharts安装
      • 2. 导入ECharts
      • 3. 创建饼图组件
      • 4. 模板部分
    • 二、报表导出功能实现
      • 1. 安装依赖
      • 2. 导入依赖
      • 3. 完整导出函数实现
      • 4. 样式优化
    • 三、完整组件实现
    • 四、常见问题与解决方案
      • 1. 图表截图不完整或模糊
      • 2. 图表背景透明
      • 3. 导出PDF中文乱码
      • 4. 跨域图片问题
    • 五、性能优化建议
    • 六、总结


前言

在若依Vue3框架中,实现报表功能是常见的业务需求。报表通常需要包含表格数据和可视化图表,并支持导出为PDF格式。本文将详细介绍如何使用ECharts实现数据可视化,结合html2canvas和jsPDF实现报表导出功能,并提供完整的代码实现和优化建议。

一、ECharts准备工作

1. 检查ECharts安装

首先需要确认项目中是否已安装ECharts。在项目根目录下执行以下命令:

npm list echarts

如果看到类似以下输出,则表示已安装:

└── echarts@5.4.3

如果没有安装,可以通过以下命令安装:

npm install echarts --save

2. 导入ECharts

在Vue组件中导入ECharts:

import * as echarts from 'echarts'
import { ref, onMounted, onBeforeUnmount } from 'vue'

3. 创建饼图组件

下面是一个完整的饼图实现示例,包含数据加载、图表渲染和销毁逻辑:

export default {setup() {const numbers = ref(['加载中', '加载中'])onMounted(() => {// 模拟数据加载setTimeout(() => {numbers.value = ['10', '30']const present = parseInt(numbers.value[0])const total = parseInt(numbers.value[1])const absent = total - present// 获取DOM元素const chartDom = document.getElementById('attendanceChart')if (!chartDom) return// 初始化图表const myChart = echarts.init(chartDom)// 图表配置const option = {tooltip: {trigger: 'item',formatter: '{a} <br/>{b}: {c} ({d}%)'},legend: {top: '0%',left: 'center',textStyle: {color: '#A6CAF4',fontSize: 14}},series: [{name: '出勤统计',type: 'pie',radius: '100%',top: '20%',data: [{value: present,name: '出勤',itemStyle: {color: '#91CC75'}},{value: absent,name: '缺勤',itemStyle: {color: '#409EF0'}}],label: {show: false},labelLine: {show: false},emphasis: {label: {show: true,position: 'inside',formatter: '{b}: {d}%',color: '#fff',fontSize: 14},itemStyle: {shadowBlur: 10,shadowOffsetX: 0,shadowColor: 'rgba(0, 0, 0, 0.5)'}}}]}// 应用配置myChart.setOption(option)// 响应式调整window.addEventListener('resize', function() {myChart.resize()})// 组件卸载时清理onBeforeUnmount(() => {window.removeEventListener('resize', () => {})if (myChart) {myChart.dispose()}})}, 300)})return { numbers }}
}

4. 模板部分

<template><div class="chart-container"><!-- 饼图容器 --><div id="attendanceChart" style="width: 100%; height: 300px;"></div><!-- 导出按钮 --><button @click="exportTextAndChartAsPDF" class="export-btn">导出报表</button></div>
</template>

二、报表导出功能实现

1. 安装依赖

确保已安装html2canvas和jsPDF:

npm install html2canvas jspdf --save

2. 导入依赖

import html2canvas from 'html2canvas'
import { jsPDF } from 'jspdf'

3. 完整导出函数实现

const personnelData = ref([{ name: '张三', date: '2023-10-01', status: '出勤' },{ name: '李四', date: '2023-10-01', status: '缺勤' },{ name: '王五', date: '2023-10-02', status: '迟到' },
])const exportTextAndChartAsPDF = async () => {// 创建PDF文档const pdf = new jsPDF('p', 'mm', 'a4') // 纵向A4const lineHeight = 10 // 行高let startY = 40 // 初始Y坐标// 1. 添加标题pdf.setFontSize(16).setTextColor(0, 0, 0)pdf.text('人员出勤报表', 105, 15, { align: 'center' })// 2. 添加表格标题行pdf.setFontSize(12)pdf.text('姓名', 20, 30)pdf.text('日期', 80, 30)pdf.text('状态', 140, 30)// 3. 添加数据行personnelData.value.forEach((item, index) => {const currentY = startY + index * lineHeightpdf.text(item.name, 20, currentY)pdf.text(item.date, 80, currentY)pdf.text(item.status, 140, currentY)})// 4. 截取饼图并添加到PDFconst chartContainer = document.getElementById('attendanceChart')?.parentNodeif (chartContainer) {try {// 截图饼图区域const canvas = await html2canvas(chartContainer, {scale: 2, // 提高分辨率logging: false,useCORS: true, // 允许跨域图片backgroundColor: '#FFFFFF', // 背景设为白色scrollY: -window.scrollY, // 解决滚动位置问题windowWidth: document.documentElement.scrollWidth, // 完整宽度windowHeight: document.documentElement.scrollHeight // 完整高度})// 计算饼图在PDF中的位置const imgProps = { width: 80, height: 80 } // 自定义饼图大小(mm)const imgX = 60 // X坐标(居中偏左)const imgY = startY + personnelData.value.length * lineHeight + 20 // Y坐标(表格下方留20mm间距)// 添加饼图到PDFpdf.addImage(canvas.toDataURL('image/png'),'PNG',imgX,imgY,imgProps.width,imgProps.height)} catch (error) {console.error('图表截图失败:', error)ElMessage.error('图表截图失败,请重试')return}}// 5. 保存PDFtry {pdf.save('人员出勤报表.pdf')ElMessage.success('报表导出成功')} catch (error) {console.error('PDF保存失败:', error)ElMessage.error('报表导出失败')}
}

4. 样式优化

<style scoped>
.chart-container {position: relative;width: 100%;height: 100%;padding: 20px;background-color: #fff;border-radius: 4px;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}.export-btn {position: absolute;top: 10px;right: 10px;z-index: 10;padding: 8px 15px;background-color: #409EFF;color: white;border: none;border-radius: 4px;cursor: pointer;font-size: 14px;transition: all 0.3s;
}.export-btn:hover {background-color: #66b1ff;transform: translateY(-2px);box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}.export-btn:active {transform: translateY(0);
}
</style>

三、完整组件实现

<template><div class="chart-container"><!-- 饼图容器 --><div id="attendanceChart" style="width: 100%; height: 300px;"></div><!-- 导出按钮 --><button @click="exportTextAndChartAsPDF" class="export-btn">导出报表</button></div>
</template><script>
import * as echarts from 'echarts'
import { ref, onMounted, onBeforeUnmount } from 'vue'
import html2canvas from 'html2canvas'
import { jsPDF } from 'jspdf'
import { ElMessage } from 'element-plus'export default {setup() {const numbers = ref(['加载中', '加载中'])const personnelData = ref([{ name: '张三', date: '2023-10-01', status: '出勤' },{ name: '李四', date: '2023-10-01', status: '缺勤' },{ name: '王五', date: '2023-10-02', status: '迟到' },])onMounted(() => {// 模拟数据加载setTimeout(() => {numbers.value = ['10', '30']const present = parseInt(numbers.value[0])const total = parseInt(numbers.value[1])const absent = total - presentconst chartDom = document.getElementById('attendanceChart')if (!chartDom) returnconst myChart = echarts.init(chartDom)const option = {tooltip: {trigger: 'item',formatter: '{a} <br/>{b}: {c} ({d}%)'},legend: {top: '0%',left: 'center',textStyle: {color: '#A6CAF4',fontSize: 14}},series: [{name: '出勤统计',type: 'pie',radius: '100%',top: '20%',data: [{value: present,name: '出勤',itemStyle: {color: '#91CC75'}},{value: absent,name: '缺勤',itemStyle: {color: '#409EF0'}}],label: {show: false},labelLine: {show: false},emphasis: {label: {show: true,position: 'inside',formatter: '{b}: {d}%',color: '#fff',fontSize: 14},itemStyle: {shadowBlur: 10,shadowOffsetX: 0,shadowColor: 'rgba(0, 0, 0, 0.5)'}}}]}myChart.setOption(option)window.addEventListener('resize', function() {myChart.resize()})onBeforeUnmount(() => {window.removeEventListener('resize', () => {})if (myChart) {myChart.dispose()}})}, 300)})const exportTextAndChartAsPDF = async () => {const pdf = new jsPDF('p', 'mm', 'a4')const lineHeight = 10let startY = 40pdf.setFontSize(16).setTextColor(0, 0, 0)pdf.text('人员出勤报表', 105, 15, { align: 'center' })pdf.setFontSize(12)pdf.text('姓名', 20, 30)pdf.text('日期', 80, 30)pdf.text('状态', 140, 30)personnelData.value.forEach((item, index) => {const currentY = startY + index * lineHeightpdf.text(item.name, 20, currentY)pdf.text(item.date, 80, currentY)pdf.text(item.status, 140, currentY)})const chartContainer = document.getElementById('attendanceChart')?.parentNodeif (chartContainer) {try {const canvas = await html2canvas(chartContainer, {scale: 2,logging: false,useCORS: true,backgroundColor: '#FFFFFF',scrollY: -window.scrollY,windowWidth: document.documentElement.scrollWidth,windowHeight: document.documentElement.scrollHeight})const imgProps = { width: 80, height: 80 }const imgX = 60const imgY = startY + personnelData.value.length * lineHeight + 20pdf.addImage(canvas.toDataURL('image/png'),'PNG',imgX,imgY,imgProps.width,imgProps.height)} catch (error) {console.error('图表截图失败:', error)ElMessage.error('图表截图失败,请重试')return}}try {pdf.save('人员出勤报表.pdf')ElMessage.success('报表导出成功')} catch (error) {console.error('PDF保存失败:', error)ElMessage.error('报表导出失败')}}return {numbers,exportTextAndChartAsPDF}}
}
</script><style scoped>
.chart-container {position: relative;width: 100%;height: 100%;padding: 20px;background-color: #fff;border-radius: 4px;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}.export-btn {position: absolute;top: 10px;right: 10px;z-index: 10;padding: 8px 15px;background-color: #409EFF;color: white;border: none;border-radius: 4px;cursor: pointer;font-size: 14px;transition: all 0.3s;
}.export-btn:hover {background-color: #66b1ff;transform: translateY(-2px);box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}.export-btn:active {transform: translateY(0);
}
</style>

四、常见问题与解决方案

1. 图表截图不完整或模糊

  • 原因:html2canvas默认截图分辨率较低
  • 解决方案
    const canvas = await html2canvas(chartContainer, {scale: 2, // 提高截图分辨率windowWidth: document.documentElement.scrollWidth,windowHeight: document.documentElement.scrollHeight
    })
    

2. 图表背景透明

  • 原因:未设置背景色
  • 解决方案
    backgroundColor: '#FFFFFF' // 在html2canvas配置中设置
    

3. 导出PDF中文乱码

  • 原因:jsPDF默认不支持中文
  • 解决方案
    • 使用支持中文的字体(如ctex插件)
    • 或者将图表转为图片后插入PDF(本文采用的方法)

4. 跨域图片问题

  • 原因:图表中使用了跨域图片
  • 解决方案
    useCORS: true // 在html2canvas配置中启用
    

五、性能优化建议

  1. 懒加载图表:只在需要导出时才渲染图表
  2. 虚拟滚动:对于大数据量的表格,使用虚拟滚动技术
  3. 分页导出:数据量很大时,考虑分页导出
  4. Web Worker:将截图和PDF生成放在Web Worker中执行,避免阻塞UI

六、总结

本文详细介绍了在若依Vue3框架中实现报表功能的完整方案,包括:

  1. 使用ECharts实现数据可视化
  2. 使用html2canvas实现图表截图
  3. 使用jsPDF生成PDF文档
  4. 完整的错误处理和用户体验优化

通过本文的方案,你可以快速实现包含表格和图表的报表导出功能,并根据实际需求进行扩展和优化。

http://www.dtcms.com/a/273771.html

相关文章:

  • 界面组件DevExpress WPF中文教程:Grid - 如何检查节点?
  • Windows 应用程序的 UI 框架:WPF、WinUI 3 和 UWP的差异区别
  • VMware安装Centos 7
  • pandas销售数据分析
  • 十年架构心路:从单机到云原生的分布式系统演进史
  • 七牛云运维面试题及参考答案
  • MySQL 的语言体系
  • 【InnoDB存储引擎4】行结构
  • 报错 | “pnpm : 无法将“pnpm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,
  • day17 力扣654.最大二叉树 力扣617.合并二叉树 力扣700.二叉搜索树中的搜索 力扣98.验证二叉搜索树
  • 数据库迁移人大金仓数据库
  • 多表查询-2-多表查询概述
  • 黑马点评系列问题之P55优惠券秒杀 快捷键问题 Ctrl+D显示不出来老师给的界面
  • 第八章 STM32之IAP编程
  • mysql数据库导入导出命令
  • ARM架构CPU的市场和生态
  • 欢乐熊大话蓝牙知识26:想让设备秒连?Connection Interval 配得对吗?
  • 零碳园区:安科瑞EMS如何破解新能源消纳难与高耗能产业转型困局
  • 锁的艺术:从Mutex到ReentrantLock,掌握并发编程的脉搏
  • 大模型使用
  • Qt 实现新手引导
  • Windows解决 ping 127.0.0.1 一般故障问题
  • unity 有打击感的图片,怎么做动画,可以表现出良好的打击效果
  • STM32串口通信(寄存器与hal库实现)
  • 2025年7月11日学习笔记一周归纳——模式识别与机器学习
  • 高校智慧教室物联网系统设计与实现
  • 《磁力下载工具实测:资源搜索+高速下载一站式解决方案》
  • 串行数据检测器,检测到011,Y输出1,否则为0.
  • JavaScript加强篇——第五章 DOM节点(加强)与BOM
  • 网安系列【18】之SpringBoot漏洞