后端Nodejs + 前端Vue 实现 HTML 转 PDF 并导出(方案二:puppeteer nodejs express 实现)

本文档介绍了一个使用Node.js和Puppeteer库生成包含多个表格的PDF报告的方案。通过调用Express服务接口,结合Puppeteer抓取前端页面并转换为PDF,再利用pdf-lib合并PDF,实现快速生成带有分页的报告。同时,指出了该方案在排查错误和部署上的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

近期公司提出了一个新需求,希望将用户在前端填写的一系列数据生成一个报告给用户,报告大概有8个表格,表格涉及到分页。于是查询了资料,做出以下两个方案。
方案一请点击这里

方案二

puppeteer

生成页面的截图和PDF

Nodejs使用

使用express框架搭建简单的node服务,并且安装puppeteer、pdf-lib

  • 安装
npm install --save puppeteer
npm install --save pdf-lib
  • 相关代码
    直接调用node服务接口进行pdf的生成
    http://localhost:3000/report?reportId=test&dirName=test&reportName=test
const express = require('express');
const router = express.Router();
const puppeteer = require('puppeteer');
const fs = require('fs');
const { v4: uuidV4 } = require('uuid');
const { PDFDocument } = require('pdf-lib');

router.get('/', async (req, res, next) => {
  const { reportId, dirName = '', reportName } = req.query || {};
  if (!reportId) {
    res.send({
      code: 40000,
      msg: "请输入reportId"
    });
    return;
  }

  // const folder = `/app/pdfReport/${dirName}`; // 部署使用
  const folder = `d:/report/${dirName}`; // 本地测试使用
  fs.access(folder, err => {
    if (err) {
      fs.mkdir(folder, { recursive: true }, err => {
        if (err) throw err;
      });
    } else {
      console.log('dir exists');
    }
  });
  try {
    // 启动无头浏览器
    const browser = await puppeteer.launch({
      args: ['--no-sandbox', '--disable-setuid-sandbox'],
      headless: true,
    });

    const defaultConfig = {
      format: 'A4',
      printBackground: true,
      margin: {
        top: '2cm',
        right: '2cm',
        bottom: '2cm',
        left: '2cm',
      },
    };

    // 封面
    const reportCover = await browser.newPage();
    // 不再有网络连接时触发
    await reportCover.goto(`http://127.0.0.1:8080/report-cover?reportName=${reportName}`, { waitUntil: 'networkidle0' });
    const reportCoverBuffer = await reportCover.pdf({
      ...defaultConfig,
      displayHeaderFooter: false,
    });
    // 关闭页面
    reportCover.close();

    // 内容
    const reportContent = await browser.newPage();
    // 不再有网络连接时触发
    await reportContent.goto(`http://127.0.0.1:8080?reportId=${reportId}`, { waitUntil: 'networkidle0' });
    const pdfConfig = {
      ...defaultConfig,
      displayHeaderFooter: true,
      headerTemplate: `<div></div>`,
      footerTemplate: '<div style="width:100%;text-align:right;margin-right: 20px;font-size:10px"><span class="pageNumber"></span></div>',
    };
    const reportContentBuffer = await reportContent.pdf({ ...pdfConfig });
    // 关闭页面
    reportContent.close();
    // 关闭 chromium
    browser.close();

    // 合并pdf
    const pdfDoc = await PDFDocument.create();
    const coverDoc = await PDFDocument.load(reportCoverBuffer);
    const [coverPage] = await pdfDoc.copyPages(coverDoc, [0]);
    pdfDoc.addPage(coverPage);
    const reportDoc = await PDFDocument.load(reportContentBuffer);
    const reportPages = await pdfDoc.copyPages(reportDoc, reportDoc.getPageIndices());
    reportPages.forEach((page) => {
      pdfDoc.addPage(page);
    });
    const pdfBytes = await pdfDoc.save();
    fs.writeFileSync(`${folder}/${reportName || reportId || uuidV4()}.pdf`, pdfBytes);

    res.set({
      'Content-Type': 'application/pdf',
    });
    res.send(Buffer.from(pdfBytes));
  } catch (error) {
    res.send({
      code: 40001,
      msg: '页面渲染出错啦',
    });
  }

  // res.send({
  //   code: 200,
  // });
});
module.exports = router;

缺点
  1. 如果页面上有错误,难于进行排查
  2. 需要单独一个服务
优点
  1. 生成pdf的速度较快
  2. pdf文件可进行复制

由于代码量比较大,正文中只贴了关键代码,有兴趣的可以去这个代码仓库查看

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值