需求:前端生成一个模板并盖章的pdf文件(单个文件自动下载+多文件生成压缩包下载以及window.print()),一个pdf多个分页进行处理(保证图片、表、文字能完整展示,截断问题解决)

目录

第一章、解析需求

第二章、工具说明以及注意事项

2.1 生成pdf文件用到的组件库

2.2 注意事项

第三章、代码思路(pdf单页+分页)

3.1 工具导入

3.2  难点:通过html2canvas和jspdf生成pdf文件

3.2.1 涉及到函数参数解释

3.2.2 实现思路

3.2.3 输出pdf遇到的问题

3.2.4 提供解决2.2多页截断问题的思路

3.2.5 最终效果

第四章、多个文件实现压缩包解压

4.1 生成zip实现

4.1.1 实现

4.1.2 实现效果 

第五章、 window.print()使用

5.1 window.print()说明

5.2 代码使用

第六章、总结

6.1 盖章的pdf效果图

6.2 完整代码

第一章、解析需求

  • 模板——页面,数据肯定就得从后端渲染得到,然后就是动态渲染页面
  • 盖章——也就是一个章图片,至于位置呢,开发者通过样式决定
  • pdf文件——这里我分成了两种情况,用window打印下载生成的pdf文件以及其他方法
  • 难点——如何生成多个pdf文件,并压缩下载;如果一个pdf多页如何处理

第二章、工具说明以及注意事项

2.1 生成pdf文件用到的组件库

  • html2canvas: 将Dom转化为图片
  • jspdf:将图片转化为pdf
  • jszip: 将多个文件打压成一个zip的压缩包
  • file-saver:将文件导出

2.2 注意事项

使用上面这几个组件库的时候需要注意的是html2canvas这个插件是先将我们的dom/html页面先转化成了一个canvas的画布图片,如果我们的pdf只有一页,我们使用这套插件的时候都没有问题,百度上大部分的解决方案我们都可以使用,但是当我们想输出多页pdf的时候,问题就出现了,jspdf插件会直接针对图片进行截断,不能识别每一个dom元素,就会造成在分页的过程中,html中的图片、表格、文字…出现截断的情况,同时打印出来的pdf排版也不美观

第三章、代码思路(pdf单页+分页)

3.1 工具导入

npm install html2canvas
npm install jspdf
npm install jszip
npm install file-saver

3.2  难点:通过html2canvas和jspdf生成pdf文件

3.2.1 涉及到函数参数解释

  • const PDFres = await downloadPDF(dom, name, a4width, a4height)

dom:需要生成pdf的html的dom元素
name: 生成pdf的文件名称
a4width:pdf的宽度
a4height: pdf的高度 (注意这里小编这么命名是要用到的宽高是a4纸的宽高)
  • html2canvas(dom,{scale,dpi})
html2canvas(dom, { // 截图所需要的dom元素的名称id/class…
    // 可配置项放在这里
    scale: 4, // 提高渲染的比例
    dpi: window.devicePixelRatio * 4 // 设备像素比
})
  • new JsPDF('', 'pt', 'a4') 三个参数:new JsPDF('参数1','参数2','参数3')

        - 参数1: l/p l:横向 p:纵向 ,默认纵向
        - 参数2:测量单位("pt","mm", "cm", "m", "in" or "px") ; 建议用pt  1(pt) = 1/72(inch-英寸) 1(inch) = 2.54(cm)
        - 参数3:pdf的格式,默认'a4',
这里有很多格式可选:a0 - a10、b0 - b10、c0 - c10、dl、letter、government-letter、legal、junior-legal、ledger、tabloid、credit-card我们主要知道的为:a3:[841.896,1190.55],a4 :[595.28,841.89],以及自定义格式,自定义格式参数为:[pdfX, pdfY] ——理解:pdfX:pdf的宽度 pdfY:pdf的高度

  •  pdf.addImage(image, format, x, y, width, height, alias, compression, rotation)
    - image:表示要插入的图片资源,可以是图片文件的路径或者base64编码字符串。
    - format:表示要插入的图片格式,包括:'JPEG','PNG', 'GIF','BMP', 'TIFF', 'RAW', 'JPEG2000'。 注:正常pdf的a4纸距离四边 2.54cm(上下) 3.14cm(左右)
    - x:图片在PDF中的x轴坐标,单位为pt(点) 1(pt) = 1/72(inch) 1(inch) = 2.54(cm)
    - y:图片在PDF中的y轴坐标,单位为pt(点)
    - width:图片在PDF中的宽度,单位为pt(点)
    - height:图片在PDF中的高度,单位为pt(点)
    - alias(可选):指定图片资源的别名。
    - compression(可选):指定图片的压缩质量,取值为0-1之间的浮点数。
    - rotation(可选):指定图片的旋转角度,取值范围为0-360之间的整数。
  • pdf.addPage() 添加空白页
  • pdf.deletePage(targetPage) 删除目标页,参数为要删除的页
  • pdf.save(`${name}.pdf`) 保存为pdf文档,参数为自定义pdf文件名称

3.2.2 实现思路

  • 有了以上了解后:我们需要得到一个pdf需要有4个基本值——需要输出pdf的html页面生成pdf的名字(当然代码写的过程中可以写死就不需要了)、生成pdf纸的宽、高
  • 值准备好了,我们利用html2canvas将dom页面转换成图片,然后对比pdf纸的宽高等比缩放内容的宽高,从而在pdf上不失真的显示
  • pdf生成:单页与分页,两个高度对比,html页面的实际高度以及pdf的页面高度,如果html页面高度小于pdf一页的高度,那么直接渲染输出,否则pdf页面添加,同时html页面的高度递减(可理解成偏移量也要跟着变化)
  • 最后生成pdf文件
  • 代码如下——(具体还有不清楚的可看小编的注释
downloadPDF = (dom, name, a4width, a4height) => {
  return new Promise((resolve, reject) => {
    // 打印纸的宽高赋值
    const domWidth = dom.offsetWidth
    const domHeight = dom.offsetHeight
    setTimeout(() => {
      // 需要答应的dom元素赋值
      const _downDOM = dom
      // 导出之前先将滚动条置顶,不然会出现数据不全的现象
      window.pageYoffset = 0
      document.documentElement.scrollTop = 0
      document.body.scrollTop = 0
      html2canvas(_downDOM, { // 两个参数:所需要截图的元素id,截图后要执行的函数, canvas为截图后返回的最后一个canvas
        scale: 4, // 按比例增加分辨率 (2=双倍).
        dpi: window.devicePixelRatio * 4 // 设备像素比
      }).then((canvas) => {
        const contentWidth = domWidth
        const contentHeight = domHeight
        // 一页pdf显示html页面生成的canvas高度;
        const pageHeight = (contentWidth / a4width) * a4height
        // 未生成pdf的html页面高度
        let leftHeight = contentHeight
        // 页面偏移
        let position = 0
        // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
        const imgWidth = a4width
        const imgHeight = (a4width / contentWidth) * contentHeight
        // const pageData = new Image()
        // 设置图片跨域访问
        // pageData.setAttribute('crossOrigin', 'Anonymous')
        const pageData = canvas.toDataURL('image/jpeg', 1.0)
        const pdf = new JsPDF('', 'pt', 'a4')
        /*
          new JsPDF('', 'pt', 'a4') 三个参数:new JsPDF('参数1','参数2','参数3')
          · 参数1:'l'/'p'  l:横向 p:纵向
          · 参数2:测量单位("pt","mm", "cm", "m", "in" or "px") -- 建议用pt  1(pt) = 1/72(inch) 1(inch) = 2.54(cm)
          · 参数3:pdf的格式,默认'a4',
              ---- 这里有很多格式可选:
                  a0 - a10
                  b0 - b10
                  c0 - c10
                  dl
                  letter
                  government-letter
                  legal
                  junior-legal
                  ledger
                  tabloid
                  credit-card
              ---- 我们主要知道的为:a3 --> [841.896,1190.55],a4 --> [595.28,841.89],以及自定义格式
              ---- 自定义格式参数为:[pdfX, pdfY] --理解:pdfX:pdf的宽度 pdfY:pdf的高度
        */
        // 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
        // 当内容未超过pdf一页显示的范围,无需分页
        if (leftHeight < pageHeight) {
          pdf.addImage(pageData, 'JPEG', 1.25, 0, imgWidth, imgHeight)
          /*
            pdf.addImage(image, format, x, y, width, height, alias, compression, rotation)
            1、image:表示要插入的图片资源,可以是图片文件的路径或者base64编码字符串。
            2、format:表示要插入的图片格式,包括:‘JPEG’, ‘PNG’, ‘GIF’, ‘BMP’, ‘TIFF’, ‘RAW’, ‘JPEG2000’。
            注:正常pdf的a4纸距离四边 2.54cm(上下) 3.14cm(左右)
            3、x:图片在PDF中的x轴坐标,单位为pt(点)。 1(pt) = 1/72(inch) 1(inch) = 2.54(cm)
            4、y:图片在PDF中的y轴坐标,单位为pt(点)。
            5、width:图片在PDF中的宽度,单位为pt(点)。
            6、height:图片在PDF中的高度,单位为pt(点)。
            7、alias(可选):指定图片资源的别名。
            8、compression(可选):指定图片的压缩质量,取值为0-1之间的浮点数。
            9、rotation(可选):指定图片的旋转角度,取值范围为0-360之间的整数。
          */
        } else {
          while (leftHeight > 0) {
            pdf.addImage(pageData, 'JPEG', 1.25, position, imgWidth, imgHeight)
            leftHeight -= pageHeight
            position -= a4height // 841.89
            // 避免添加空白页
            if (leftHeight > 0) {
              pdf.addPage()
            }
          }
        }
        // 这里返回文件 用来处理多个下载 打包zip
        resolve({ PDF: pdf, name: name })
        // 直接单个pdf可直接调用下面方法
        // pdf.save(name)
      })
    }, 10)
  })
}

3.2.3 输出pdf遇到的问题

可以注意的是,小编在代码中添加这么一段代码:

// 导出之前先将滚动条置顶,不然会出现数据不全的现象
window.pageYoffset = 0 // 相对于窗口显示区左上角的位置为0
document.documentElement.scrollTop = 0 // 滚动条垂直位置为0
document.body.scrollTop = 0 // 让页面置顶

原因:小编在将下载pdf的按钮放在页面最下方的时候,输出pdf,因为位置问题,导致pdf的输出不全,小编发现置顶之后肯定不会出错,所以放入置顶的一段代码

3.2.4 提供解决2.2多页截断问题的思路

  • 确定问题点:首先上面的函数是直接将html页面转换成了一张图片,对该图片处理的时候我们并不能知道这里面的元素:图片、表格、文字的位置,需要解决完整的内容不被截断而且还能完美定位布局就有些困难,所以我们需要在html转换成图片之前就对元素开始做定位的处理,从而在pdf多页输出的时候正常输出也不会有影响
  • 需要添加的点我们已经找到了,那么实现的方法呢?小编的思路提供参考:

        - 小编将html的页面进行了处理,对一些可能出现截断的父元素、子元素盒子添加了类名 'page_re',从而通过计算每一个盒子高度的累计和来判断是否超出pdf页面,超出了则在下一个盒子计算它距离第一页最后一个盒子的距离,公式:当前容器移动到下一页需要的距离:当前容器的高度减去超出当前pdf页的高度,超出当前pdf页的高度:到当前盒子的累计和减去pdf高度

// 处理页面分页问题
deal (dom) {
  const list = document.querySelectorAll('.page_re') // 获取每一个page_re盒子
  /*
    备注:
      要对每一个转成pdf可分割不可分割去元素添加page_re的类标签,
    目的:
      为了计算转成pdf后的距离处理
    缺点:写样式时需要自己留意哪些盒子需要加一个类标签,从而计算高度时是一个独立的盒子
  */
  let margin = 0 // 计算每一页 最后一个容器距离底部的距离
  let pdHeight = 0 // 容器高度的累加值
  const pageHeight = A4_HEIGHT / A4_WIDTH * dom.offsetWidth// 设置a4纸转成html时的的高度,从而计算html转图片后放入a4纸时的格式
  list[0].style.marginTop = MARGIN_TOP // 给第一个盒子一个高度
  // i 从1开始, 0 为 封面---->我写的内容没有封面,从第一个盒子开始计算高度
  for (let i = 0; i < list.length; i++) {
    const height = parseFloat(window.getComputedStyle(list[i]).height) // 每一个page_re盒子的高度
    pdHeight += height // 累加每个容器的高度
    // 当容器的高度累加大于pdf一页的高度时,说明这个容器需要放到下一页。
    if (pdHeight > pageHeight) {
      margin = height - (pdHeight - pageHeight) // 计算距离下一页的高度 当前容器的高度减去超出当前pdf页的高度就是当前容器移动到下一页需要的距离
      list[i].style.marginTop = margin + 'px' // 下一页的第一个盒子距离上一页的高度
      i -= 1 // list[i] 表示当前容器应显示在下一页,所以当下一页计算容器高度累加时应加上list[i]的高度, 这里可以试试用while
      pdHeight = 10 // 进入下一页重新计算累加高度,这里每个page_re
      // 元素下的第一个子元素都会有10px的paddingTop,所以初始高度为10 这里也可以动态获取
    }
  }
},

html部分:

3.2.5 最终效果

分页成功:(这里图片表示章,其余表示内容排版,盖章的pdf文件具体效果看最后一章节的效果图)

第四章、多个文件实现压缩包解压

4.1 生成zip实现

4.1.1 实现

该内容模块就不难了,将生成的每一个pdf的promise放到promises数组中,如果数组中只有一个值,只需要输出一个pdf,否则将多个pdf整合生成zip,代码如下:

// 生成zip
zipChange (promises) {
  Promise.all(promises).then(async (pdfs) => {
    const zip = new JSZip()
    promises.forEach(async (item, index) => {
      const { PDF, name } = item
      if (promises.length === 1) {
        PDF.save(`${name}.pdf`)
      } else {
        await zip.file(`${name}.pdf`, PDF.output('blob'))
      }
    })
    if (promises.length > 1) {
      zip.generateAsync({ type: 'blob' }).then((content) => {
        FileSaver.saveAs(content, '内容' + '.zip')
      })
    }
  })
}

4.1.2 实现效果 

 

第五章、 window.print()使用

5.1 window.print()说明

window.print()打印是浏览器自带的打印,实现原理是将html转换为pdf可以在线预览打印或者导出pdf,在任何网页上可通过Ctil+p快捷键调出浏览器打印程序,它可将整个网页打印出来,在我们开发中,其实并不需要将所有页面打印出来,或者只需要局部的页面做打印,那我我们就自己实现window.print()打印功能,具体可以看小编接手的项目的实现如下

5.2 代码使用

第六章、总结

6.1 盖章的pdf效果图

6.2 完整代码

demo_pdf: 这是一个纯前端生成盖章pdf模板的demo

路径:/pdf 

如果代码运行不了、有不同的思路评论区可以留言小编,及时给予回复!!!有用就点个赞吧!!! 

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值