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

【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} 
{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\') // 纵向A4 const 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 * lineHeight pdf.text(item.name, 20, currentY) pdf.text(item.date, 80, currentY) pdf.text(item.status, 140, currentY) }) // 4. 截取饼图并添加到PDF const chartContainer = document.getElementById(\'attendanceChart\')?.parentNode if (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间距) // 添加饼图到PDF pdf.addImage( canvas.toDataURL(\'image/png\'), \'PNG\', imgX, imgY, imgProps.width, imgProps.height ) } catch (error) { console.error(\'图表截图失败:\', error) ElMessage.error(\'图表截图失败,请重试\') return } } // 5. 保存PDF try { pdf.save(\'人员出勤报表.pdf\') ElMessage.success(\'报表导出成功\') } catch (error) { console.error(\'PDF保存失败:\', error) ElMessage.error(\'报表导出失败\') }}

4. 样式优化

.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);}

三、完整组件实现

<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 - present const chartDom = document.getElementById(\'attendanceChart\') if (!chartDom) return const myChart = echarts.init(chartDom) const option = { tooltip: { trigger: \'item\', formatter: \'{a} 
{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 = 10 let startY = 40 pdf.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 * lineHeight pdf.text(item.name, 20, currentY) pdf.text(item.date, 80, currentY) pdf.text(item.status, 140, currentY) }) const chartContainer = document.getElementById(\'attendanceChart\')?.parentNode if (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 = 60 const imgY = startY + personnelData.value.length * lineHeight + 20 pdf.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. 完整的错误处理和用户体验优化

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