文章目錄
- 前言
- 一、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文檔
- 完整的錯誤處理和用戶體驗優化
通過本文的方案,你可以快速實現包含表格和圖表的報表導出功能,并根據實際需求進行擴展和優化。