当前位置: 首页>前端>正文

前端生成pdf解决方案

方案一:html2canvas + jspdf

缺点:不能获取页面上的文字,超链接等(用途有限)

savePdf=(element, title) => {  //html2canvas+jspdf实现方案
       html2canvas(element, {
         useCORS: true, // 【重要】开启跨域配置
         allowTaint: true, // 允许跨域图片
         taintTest: false, // 是否在渲染前测试图片
         scale: 2,
       }).then((canvas) => {
         const contentWidth = canvas.width;
         const contentHeight = canvas.height;

         // 一页pdf显示html页面生成的canvas高度;
         const pageHeight = contentWidth / 596 * 842;
         // 未生成pdf的html页面高度
         let leftHeight = contentHeight;
         // 页面偏移
         let position = 0;

         // 每页的尺寸[595,842],html页面生成的canvas在pdf中图片的宽高
         const imgWidth = 596;
         const imgHeight = 596 / contentWidth * contentHeight;

         const pageData = canvas.toDataURL('image/jpeg', 1.0);

         const pdf = new JsPDF('', 'pt', 'a4');

         // 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(842)
         // 当内容未超过pdf一页显示的范围,无需分页
         if (leftHeight < pageHeight) {
           pdf.addImage(pageData, 'JPEG', 20, 0, imgWidth, imgHeight);
         } else {
           while (leftHeight > 0) {
             pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
             leftHeight -= (pageHeight + 16);// 16是页面上每页的margin
             position -= 842 + 16;// 16是页面上每页的margin
             // 避免添加空白页
             console.log(leftHeight)
             if (leftHeight > 300) {
               pdf.addPage();
             }
           }
         }
         console.log(pdf)
         pdf.save(`${title}.pdf`);
       });
     }

方案二: 使用puppeteer + node端实现

能够获取页面上的文字,超链接等。但是出现问题难以调试
官方文档:https://zhaoqize.github.io/puppeteer-api-zh_CN/

const puppeteer = require('puppeteer');
const fs = require('fs');
const send = require('koa-send');
const path = require('path');
const moment = require('moment')

// 生成pdf
async function generatePdf(pdfname, date, cookies, postlen) {
  const browser = await puppeteer.launch({
    args: ['--disable-dev-shm-usage', '--no-sandbox'],
    ignoreHTTPSErrors: true,
    headless: true,
  });
  const page = await browser.newPage();
  const waitUntil = 'networkidle0'; // 代表页面的接口都返回后再渲染
  await page.setCookie(...cookies);

  await page.goto(url, {// 导航到的地址
    waitUntil, // 不再有网络连接时触发
    timeout: 30 * 60 * 60 * 1000,
  });
   await page.waitForSelector('#post' + postlen);// 最后的元素加载完成后再生成对应的的pdf,保证页面渲染结束
  await page.addStyleTag({// 跟需要转pdf的页面修改样式,可以隐藏一些不想展示的内容
    content: `
        html {
            -webkit-filter: opacity(1) !important;
        }
       `,
  });


  await page.pdf({
    path: `${pdfname + date}.pdf`, // 名称
    printBackground: true, // 展示背景图
    width: 596, // 每页画的宽度
    height: 843, // 每页画的高度
  });
  await browser.close();
  return pdfname;
}


class ApiController {
  async getPdf(ctx) {
    const dateTime = moment().format('YYYYMMDD')
    const { start_day } = ctx.query;
//可以传一些需要渲染页面所需的参数,类似登录的token、页面需要的参数等(无法通过其他方式传值,需要转pdf的页面直接从cookie里边读取想要的信息即可)
    const cookies = [
      {
        name: 'token',
        value: ctx.cookies.get('token'),
        domain: 'localhost',
        path: '/',
      },
      {
        name: 'start_day',
        value: start_day,
        domain: 'localhost',
        path: '/',
      },
    ];
    const retPdf = await generatePdf(pdfPreName, dateTime, cookies)
      .then((pdfname) => {
        ctx.body = {
          result_code: 0,
          url: `${pdfname}${dateTime}.pdf`,
          state: 'success',
        };
      });
    console.log('数据', retPdf);
  }

  async downloadPdf(ctx) {
    const fileName = ctx.query.name;
    const filePath = path.resolve(`${__dirname}`, `../../${fileName}`);
    ctx.attachment(filePath);
    await send(ctx, fileName);
    setTimeout(() => {
      fs.unlink(fileName, (err) => {
        console.log('文件已被删除');
      });
    }, 15000);
  }
}

fix1: 开始的时候生成的pdf都是只有一页,并且生成的是从页面左上角开始的,并不是我们想要的。经过多番修改,发现是因为页面的外部元素含有min-height min-width等样式导致页面的宽高出现问题。
fix2: 项目部署,包安装问题,puppeteer依赖的包无法安装,需要sre同学支持安装。
fix3: 字体问题,需要sre同学协助安装字体,或者引动通过cdn方式引用字体包
fix4: 导出pdf部分样式问题,兼容处理page.addStyleTag({content:`#app{....}`})
fix4: 无头浏览器登录问题,cookie值的传递page.setCookie(...cookies)
fix5: 数据量过大,导致丢页现象,可以通过page.waitForSelector或者page.waitForTimeout(25000)延时处理
fix6: 无固定页数的数据渲染,添加整页页尾图片的情况(尾部图片只在最后一页展示且不切隔)可以通过给最后一页设置样式 page-break-before: always;还可以通过添加页尾的方式处理(也可以通过相同方式添加封面):

const pdfBuffer = await page.pdf({
    printBackground: true,
    width: 596,
    height: 843,
    fullPage: true,
    displayHeaderFooter: true,
    footerTemplate: `<div>
      <img src="a.png" />
        </div>`,
    margin: { top: margin, bottom: margin }
  });

fix7:pdf的压缩:

  const { PDFDocument } = require('pdf-lib');
  ...
  const pdfDoc = await PDFDocument.load(pdfBuffer);
  const compressedPdfBytes = await pdfDoc.save({ useObjectStreams: true });
  fs.writeFileSync(path, compressedPdfBytes);

https://www.xamrdz.com/web/2v81994495.html

相关文章: