文章目录
- 前言
- 一、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配置中启用
五、性能优化建议
- 懒加载图表:只在需要导出时才渲染图表
- 虚拟滚动:对于大数据量的表格,使用虚拟滚动技术
- 分页导出:数据量很大时,考虑分页导出
- Web Worker:将截图和PDF生成放在Web Worker中执行,避免阻塞UI
六、总结
本文详细介绍了在若依Vue3框架中实现报表功能的完整方案,包括:
- 使用ECharts实现数据可视化
- 使用html2canvas实现图表截图
- 使用jsPDF生成PDF文档
- 完整的错误处理和用户体验优化
通过本文的方案,你可以快速实现包含表格和图表的报表导出功能,并根据实际需求进行扩展和优化。