前端导出大量数据到PDF方案
前端导出大量数据到PDF的方案:
1. 纯前端方案
jsPDF + autoTable(最常用)
import jsPDF from 'jspdf';
import 'jspdf-autotable';function exportToPDF(data) {const doc = new jsPDF();// 设置表格列const columns = ['ID', '姓名', '邮箱', '部门'];// 转换数据const rows = data.map(item => [item.id,item.name,item.email,item.department]);// 生成表格doc.autoTable({head: [columns],body: rows,styles: { fontSize: 8 },margin: { top: 10 }});doc.save('数据导出.pdf');
}
分页处理大量数据
function exportLargeDataToPDF(data, chunkSize = 500) {const doc = new jsPDF();const columns = ['ID', '姓名', '邮箱'];for (let i = 0; i < data.length; i += chunkSize) {if (i !== 0) {doc.addPage(); // 添加新页}const chunk = data.slice(i, i + chunkSize);const rows = chunk.map(item => [item.id, item.name, item.email]);doc.autoTable({head: [columns],body: rows,startY: 20,styles: { fontSize: 7 },pageBreak: 'auto'});}doc.save('大数据导出.pdf');
}
2. 性能优化方案
虚拟滚动 + 分批处理
async function exportHugeData(data, batchSize = 1000) {const doc = new jsPDF();let currentPage = 1;for (let i = 0; i < data.length; i += batchSize) {const batch = data.slice(i, i + batchSize);// 显示进度updateProgress(i, data.length);if (i > 0) {doc.addPage();currentPage++;}// 使用Web Worker处理数据转换const rows = await processBatchInWorker(batch);doc.autoTable({head: [['ID', '姓名', '邮箱']],body: rows,startY: 20,styles: { fontSize: 6 },margin: { left: 5, right: 5 }});// 让出主线程避免阻塞await new Promise(resolve => setTimeout(resolve, 0));}doc.save('超大数据导出.pdf');
}
3. 服务端辅助方案
前端生成 + 服务端合并
// 前端:分批生成PDF片段
async function generatePDFChunks(data, chunkSize = 2000) {const chunks = [];for (let i = 0; i < data.length; i += chunkSize) {const chunk = data.slice(i, i + chunkSize);const pdfBlob = await generateChunkPDF(chunk);chunks.push(pdfBlob);}// 发送到服务端合并const finalPDF = await mergePDFsOnServer(chunks);downloadBlob(finalPDF, '合并报表.pdf');
}
4. 基于Canvas的方案
html2canvas + jsPDF
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';async function exportDivToPDF(elementId, filename = '导出.pdf') {const element = document.getElementById(elementId);const canvas = await html2canvas(element, {scale: 2,useCORS: true,logging: false});const imgData = canvas.toDataURL('image/png');const pdf = new jsPDF({orientation: 'portrait',unit: 'mm',format: 'a4'});const imgProps = pdf.getImageProperties(imgData);const pdfWidth = pdf.internal.pageSize.getWidth();const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);pdf.save(filename);
}
5. 流式处理方案
使用PDFKit(Node.js环境)
// 适用于Electron或Node.js环境
const PDFDocument = require('pdfkit');
const fs = require('fs');function createStreamingPDF(data, outputPath) {const doc = new PDFDocument({ autoFirstPage: false });doc.pipe(fs.createWriteStream(outputPath));let rowCount = 0;const rowsPerPage = 40;data.forEach((item, index) => {if (rowCount % rowsPerPage === 0) {if (index > 0) doc.addPage();addTableHeader(doc);}addTableRow(doc, item, rowCount % rowsPerPage);rowCount++;});doc.end();
}
6. 最佳实践建议
内存管理
function optimizedExport(data) {// 1. 数据分片const chunks = chunkArray(data, 1000);// 2. 清理不需要的引用let currentChunkIndex = 0;function processNextChunk() {if (currentChunkIndex >= chunks.length) return;const chunk = chunks[currentChunkIndex];processChunk(chunk);// 释放内存chunks[currentChunkIndex] = null;currentChunkIndex++;// 非阻塞处理setTimeout(processNextChunk, 100);}processNextChunk();
}
进度反馈
function exportWithProgress(data) {const total = data.length;let processed = 0;showProgressBar();const interval = setInterval(() => {updateProgressBar((processed / total) * 100);if (processed >= total) {clearInterval(interval);showCompletionMessage();}}, 100);
}
7. 推荐方案选择
- 中小数据量(< 10,000行):jsPDF + autoTable
- 大数据量(10,000-100,000行):分页处理 + 进度反馈
- 超大数据量(> 100,000行):服务端生成或流式处理
- 复杂排版需求:html2canvas + jsPDF
- 最高性能要求:Web Worker + 分批处理
实际项目中,建议根据数据量大小和性能要求选择合适的方案,并始终提供进度反馈以改善用户体验。
