Vue 如何将网页转换成PDF实现步骤以及问题解决:

实现步骤:(只想了解vue实现的不用看方法一,直接看二)

方法1:

使用node.js和puppeteer(谷歌自动检测工具),由于第一种尝试结果不太理想,所以我直接粗略讲解:(原代码实现如下,依赖包并非所有都有用,puppeteer是必需要npm按照并且引入)

const puppeteer = require('puppeteer');

const useProxy = require('puppeteer-page-proxy');

const {delay} = require("bluebird");

const Promise = require("bluebird");

const ms = require("ms");

const fs = require('fs');



(async () => {

    const browser = await puppeteer.launch();

    // const browser = await puppeteer.launch({headless:false});

    // const page = await browser.newPage();

    const page = await browser.newPage();

    await page.goto('http://127.0.0.1:5173/',

    {waitUntil:'networkidle2'}

    );

    // await delay(ms("5s"));

    await page.pdf({path: './test123.pdf' , format: 'A4',printBackground:true});

    await browser.close();

})();

实现原理很简单,就是通过puppeteer自动打开对于网页,然后调用他的pdf方法保存,值得注意的是这样printBackground:true这个参数控制的是背景颜色的显示,如果不选true,背景色是不会渲染出来的,这样做的好处是生成简单,图片非常清晰,即使放大后依旧清晰,并且不会有太大的内存,我这三张图片的原图就3m多,生成文件是3.56m可见比较符合预期的。缺点:①生成的pdf是默认两页,我尝试很多手段解决,可能是学术不精,我在puppeteer的API文档为找到对应的参数设置,百度搜索等方式也未找到好的解决方案,至今不清楚原因,我试了其他网站,有些一页就能显示完,具体原因我不详做叙述了,反正笔者可能是陷入死胡同,尝试很多无果后选择了vue去解决。②由于这样需要打开新的窗口,就比如会有跳转操作,虽然有无头模式,但是我未去尝试,因为已经被①劝退了。

方法2:

方法参考来源:

Vue页面生成PDF的方法_vue生成pdf_愤怒小绵羊的博客-CSDN博客

在vue的项目中,先安装需要用到的两个依赖分别是html2canvas和jspdf

①npm install --save html2canvas

②npm install jspdf --save

①的作用是将我们需要转换成PDF的html页页面先转换成canvas(canvas是html的一个标签,在画图、特效甚至小游戏方面非常重要,了解可以去看相关系列知识,不详解)转换成canvas之后我们可以通过一系列参数去调整我们需要转换的pdf,这里是非常重要的点,因为在默认情况下,生成的canva是非常模糊的,待会细讲问题及解决

②的作用就是将canvas转换成的图片转换成需要的pdf并且导出,这里能做的事情不多,也相对较为简单

接下来讲的是具体实现步骤:(参考博主愤怒的小绵羊,非常感谢分享,可以先根据小绵羊的代码来做,由于我的代码场景和他不同,我的并不是一个示例代码。可能会存在无法执行,但是绵羊的是可以的 我试过了。)

<template>

<button @click="handleExport">导出</button>

<div ref="pdf" class="spec">

需要转换成pdf的结构或者图片

</div>

</template>

然后定义点击函数,并且拿到对应页面的pdf传递给downloadPDF函数,由于downloadPDF结构比价多,将其抽离出来pdf.js里面保存

const handleExport =()=>{

            console.log(proxy.$refs.pdf)

            downloadPDF(proxy.$refs.pdf)

        }

然后导入js,由于pdf.js内容较多就将其抽离了

import {downloadPDF} from "./pdf.js"

pdf.js内容如下:

import html2canvas from "html2canvas";

import jsPDF from "jspdf";

import compress from './compress.js';



function base64ToFile(dataURL) {

  var arr = dataURL?.split?.(',')

  let mime = arr[0].match(/:(.*?);/)[1]

  let bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);

  while (n--) {

    u8arr[n] = bstr.charCodeAt(n);

  }

  let filename = new Date().getTime() + "" + Math.ceil(Math.random() * 100) + "." + mime.split("/")[1]

  return (new File([u8arr], filename, { type: mime }))

}




export const downloadPDF = page => {

  html2canvas(page,{

    allowTaint: true, //开启跨域

    useCORS: true,

    scale: 2,

  }).then(function(canvas) {

    canvas2PDF(canvas);

  });

};

const canvas2PDF = canvas => {

  let contentWidth = canvas.width*0.2;

  let contentHeight = canvas.height*0.2;

  let imgHeight = contentHeight;

  let imgWidth = contentWidth;

  let pdf = new jsPDF("p", "pt");

  let sharePic

  sharePic = canvas.toDataURL("image/jpeg", 1)

  let fileba = base64ToFile(sharePic)

  compress(fileba)

    .then(res => {

      pdf.addImage(

        res.compressBase64,

        "JPEG",

        0,

        0,

        imgWidth,

        imgHeight

      );

      // console.log(pdf,999)

      pdf.save("导出.pdf");

    })

    .catch(err => {

    // error(err);

    });

};

到这里就已经可以将对应的html代码转换成pdf了,接下来我将讲解我遇到的问题已经解决方案:

问题及解决:

①打印出的PDF没有图片部分

我遇到的第一个问题就是打印出的pdf没有图片,我试了在nodejs+puppeteer情况下是可以直接打印,我猜测可能是转换成pdf的方式不同,可能puppeteer类似于截图(猜测观点),而vue这种先转换成canvas的形式是需要下载图片资源的,所有下载资源就存在跨域问题,一开始我是想通过配置代理的方式实现(后续会详细讲解跨域问题以及配置代理方法,帮助自己复习总结并且发出来),不过结果并不是很顺利,也可能是我操作的问题,于是我通过搜索发现,可以将图片转换base64的格式就能避开跨域问题了,http://nomad-public.oss-cn-shanghai.aliyuncs.com/size_chart/34e4debd-a8ea-4730-acd2-8a58cc7c6b5d.jpg?time=1677729826618,我大致观察了一下好像就是加了个时间戳,然后在图片前加上了image.setAttribute("crossorigin", "anonymous");据说是解决跨域策略的方法,接下来我就把将图片转换成base64的方法贴出来:

const downloadImage = (imgsrc) => {//下载图片地址和图片名(下载部分代码已经被我删除了,这是上一个需求用到了,我在此基础魔改了一下)

        var image = new Image();

        // 解决跨域 Canvas 污染问题,

        image.setAttribute("crossorigin", "anonymous");

        image.onload = function () {

        var canvas = document.createElement("canvas");

        canvas.width = image.width;

        canvas.height = image.height;

        var context = canvas.getContext("2d");

        context.drawImage(image, 0, 0, image.width, image.height);

        var url = canvas.toDataURL("image/png"); //将图片格式转为base64

        // console.log(url,"base64")

        };

        image.src = imgsrc + '?time=' + Date.now();  //注意,这里是灵魂,否则依旧会产生跨域问题

        console.log(image.src,"image.src")

        return image.src

        }

这个函数的作用就是传入一个url,函数就会将转换base64格式的图片url返回了(可能概念会有错,因为我base64和canvas理解较浅),这样再去转换成pdf就会发现可以看到图片了,顺便小提一嘴,如果是背景图片的格式可以在template中用模板字符串以动态形式添加:style="{'margin-top':'0','background-image':`url(${downloadImage('https://nomad-public.oss-cn-shanghai.aliyuncs.com/size_chart/929c3c47-e0b6-4765-bda9-5fb8c1c05191.jpg')})`}",这样就可以避开设置动态css样式了,这样就解决了第一个问题,pdf中无法显示图片。

②生成的PDF清晰度太低

当我打印出PDF之后,我又发现了一个问题,就是我的PDF清晰度太低了,像马赛克一样,于是我又去查询问题解决,有两种解决方案,一个增加scale(其实我们在css里面经常看到就是加大比例一样),我尝试了一下,确实可以,但是同时带来的是图片变得很大很大,由于需求是在一个A4纸大小,增进scale:4后字体等等清晰度是高了很多,但是纸张只能够展示出四分之一不到的内容(生成PDF的左上角),这样整个PDF不能全部展示在A4中,显然不如这样做,于是我又查到了一个参数dpi,整个参数的描述非常符合我的预期,但是就是没用,我也不知道是什么问题,可能是版本,因为我查到有一个html2canvas的参数中压根没有这一个参数,总之这条路(最简单)走不通了,于是在带我的大佬的点拨下,有了一个新的思路,就是在html转换成canvas的时候设置scale:2将转换的canvas画板变大,然后在转换成图片后缩小图片的大小,达到增加清晰度(其实和dpi实现思路一样,更麻烦了,因为dpi参数失效了)

html2canvas(page,{

    allowTaint: true, //开启跨域

    useCORS: true,

    scale: 2,

  }).then(function(canvas) {

    canvas2PDF(canvas);

  });

然后在此代码片段,缩小图片的大小,这个方法俗称先放大再缩小,先放大canvas(画板),在将其转换成图片后缩小图片的大小以增加清晰,这个步骤就是可以通俗理解成,你在一个巨大的画板下先画出需要的画面,由于画板很大,即使画的不是很清晰,在绘画完成后,将图片缩小到一个A4纸大小,那么在同样面积内像素就增加了很大,因为从一个巨大的画板都压缩在一个小小A4纸张上了,像素自然就高了(这是我个人理解)

const canvas2PDF = canvas => {

下面位置就是width*0.2

  let contentWidth = canvas.width*0.2;

  let contentHeight = canvas.height*0.2;

  let imgHeight = contentHeight;

  let imgWidth = contentWidth;

然后依旧发现清晰度偏低,然后又有了第二个思路,就html代码结构放大,比如原本放在600px的盒子里面,将盒子改成1200px,其实和前面思路一样,一个是增大画板以提升精度,现在这个就是在绘画时候花大一点,那么画板自热而然也会加大,也可以理解成html缩到原来600px的时候像素肯定也会加大,这两个思路在我看来很相似。好的目前生成的图片清晰度已经很高了,基本可以满足公司的需求了,接下来又有一个问题。

③生成的PDF文件过于庞大

由于之前使用的方法中,修改html代码css样式以加大清晰度,那么就会带来一个大小,原来的图片也被放大了,那么如果只是简单再去缩小,图片会非常大,我试过再没被处理的情况下,转换成jpeg的情况下生成pdf大小在13m左右,原图的大小才3m,可见问题之大,这样pdf在文件传输的过程会非常浪费资源,于是我就研究起来了压缩,首先我优化了代码结构,对文件大小改动很小,于是从图片着手,传入图片是20kb左右的时候生成的PDF都依旧有3.8m左右,于是在代码canvas.toDataURL("image/jpeg", 1)处我将参数1修改成0.92,1就是百分百还原图片,0.92就是牺牲清晰度换来文件大小,效果很显著,从3.8m降到了1m,很符合预期,但是我改动了图片清晰度,显然是拆东墙补西墙,我的领导也跟我说这个清晰度较低,希望维持清晰度的情况下尽可能讲到1m,在开始我的认知里面,是越清晰就越大,虽然我也在市面上见过压缩pdf的软件,但是免费情况下都是牺牲清晰度换来文件大小的降低,除了付费情况,所以我开始不知道怎么办,我又去请教带我的那个大佬,大佬直接跟我说,可以啊,随便像压到多小都可以,还不降低清晰度,于是我大佬给了一个.js压缩方法给我,我试了一下直接把3m的压缩到400kb,由于代码是大佬给我的,没经过允许前我就不公开展示了,我写的代码和参考文章代码都放上了,如果有类似需求可以私信我,我可以私发,感谢大佬的分享。方法大概是传入文件,然后将图片转换成base64,然后用canvas处理,然后经过一些我还没研究清楚的方法缩小,如果后续我在网上搜索到公开代码我会在文章中分享出来补充。

最终效果展示:

特别鸣谢:感谢好兄弟某可提出的宝贵意见,已经修改代码处为代码块,经验不足,还请见谅,还有啥不便阅读的欢迎指出

猜你喜欢

转载自blog.csdn.net/weixin_54515240/article/details/129319084