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

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

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

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,以下是实现基于 Node.js 和 Vue 的 WebSocket 一对一聊天功能的示例代码: Node.js 后端代码(使用 `ws` 模块实现 WebSocket 服务器): ```javascript const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); // 用于存储已连接的 WebSocket 客户端 const clients = new Map(); wss.on('connection', (ws) => { // 生成唯一的客户端 ID,用于标识客户端 const clientId = Date.now().toString(); // 将新连接的客户端加入 clients 中 clients.set(clientId, ws); console.log(`Client ${clientId} connected`); // 发送欢迎消息给客户端 ws.send(`Welcome, client ${clientId}!`); // 监听客户端发送的消息 ws.on('message', (message) => { console.log(`Received message from client ${clientId}: ${message}`); // 解析消息,获取目标客户端 ID 和消息内容 const { to, content } = JSON.parse(message); // 从 clients 中获取目标客户端的 WebSocket 连接 const targetClient = clients.get(to); if (targetClient) { // 如果目标客户端存在,向其发送消息 targetClient.send(`Client ${clientId}: ${content}`); } else { // 如果目标客户端不存在,向当前客户端发送错误消息 ws.send(`Error: client ${to} not found`); } }); // 监听客户端关闭连接事件 ws.on('close', () => { // 从 clients 中移除已关闭连接的客户端 clients.delete(clientId); console.log(`Client ${clientId} disconnected`); }); }); ``` Vue 前端代码: ```html <template> <div> <h2>WebSocket Chat</h2> <div v-if="connected"> <div> <label>Target Client ID:</label> <input v-model="toClientId" /> </div> <div> <label>Message:</label> <input v-model="message" /> </div> <button @click="sendMessage">Send</button> </div> <div v-else> <p>Connecting...</p> </div> <div> <p v-for="msg in messages">{{ msg }}</p> </div> </div> </template> <script> import WebSocket from 'isomorphic-ws'; export default { data() { return { ws: null, connected: false, toClientId: '', message: '', messages: [], }; }, mounted() { // 连接 WebSocket 服务器 this.ws = new WebSocket('ws://localhost:8080'); // 监听连接成功事件 this.ws.addEventListener('open', () => { console.log('Connected to WebSocket server'); this.connected = true; }); // 监听接收消息事件 this.ws.addEventListener('message', (event) => { console.log(`Received message: ${event.data}`); this.messages.push(event.data); }); // 监听连接关闭事件 this.ws.addEventListener('close', () => { console.log('Disconnected from WebSocket server'); this.connected = false; }); },

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值