近期公司提出了一个新需求,希望将用户在前端填写的一系列数据生成一个报告给用户,报告大概有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文件可进行复制

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

Logo

前往低代码交流专区

更多推荐