前端导出大量数据到PDF方案

前端导出大量数据到PDF的方案:

1. 纯前端方案

jsPDF + autoTable(最常用)

javascript 复制代码
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');
}

分页处理大量数据

javascript 复制代码
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. 性能优化方案

虚拟滚动 + 分批处理

javascript 复制代码
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. 服务端辅助方案

前端生成 + 服务端合并

javascript 复制代码
// 前端:分批生成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

javascript 复制代码
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环境)

javascript 复制代码
// 适用于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. 最佳实践建议

内存管理

javascript 复制代码
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();
}

进度反馈

javascript 复制代码
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 + 分批处理

实际项目中,建议根据数据量大小和性能要求选择合适的方案,并始终提供进度反馈以改善用户体验。

相关推荐
mldong2 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排3 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60614 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角5 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!5 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰5 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi6 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen7 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马8 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒8 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端