当前位置: 首页 > 工具软件 > PDF Download > 使用案例 >

h5页面转PDF下载(包括pc端和移动端)

寿亦
2023-12-01

前情:需要在app内嵌的weixin项目将页面转成PDF并下载。

使用技术:html2canvas插件 + jspdf插件

实现思路:1)将h5页面用canvas画成图片

                  2)利用jspdf将图片插入pdf文件并下载

缺点:生成的pdf是由图片拼接而成的,不能实现复制

实现版本:

     第一版:将h5页面转成一张长图,再根据A4值的高度将长图截成多个页面

         缺点:使用起来不灵活。没办法在每个页面上插入页眉页脚

         实现代码:

         1)weixin项目的  pdftemplate.vue文件

       (由于是内嵌微信项目,所以在微信项目页面能操作dom)

<template>
  <view v-if="calData" ref="refContent">
    .......
  </view>
</template>
import { downloadPdf } from '@/common/utils/insPdfUtil' //实现转换的页面
export default {
  ......
  mounted(){
    if (this.calData && this.calData.userInfo && this.calData.userInfo.name) {
      this.htmlTitle = this.calData.userInfo.name + '的计划书'
    }
    //this.$refs.refContent.$el:需要转化的页面内容
    //this.htmlTitle:文件名
    downloadPdf(this.$refs.refContent.$el, this.htmlTitle)
  }
}

        2)insPdfUtil.js文件

import html2canvas from 'html2canvas'
import JsPDF from 'jspdf'

/**
 * 创建并下载pdf
 * @param {HTMLElement} targetDom dom元素
 * @param {String} title pdf保存名字
 * @param {function} callback 回调函数
 */
const downloadPdf = (targetDom, title, callback) => {
  html2canvas(targetDom, {
    useCORS: true
  }).then(function(canvas) {
    const contentWidth = canvas.width
    const contentHeight = canvas.height
    const pageHeight = (contentWidth / 592.28) * 841.89
    let leftHeight = contentHeight
    let position = 0
    const imgWidth = 595.28
    const imgHeight = (592.28 / contentWidth) * contentHeight
    const pageData = canvas.toDataURL('image/jpeg', 1.0)
    const PDF = new JsPDF('', 'pt', 'a4')

    if (leftHeight < pageHeight) {
      PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
    } else {
      while (leftHeight > 0) {
        PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
        leftHeight -= pageHeight
        position -= 841.89
        if (leftHeight > 0) {
          PDF.addPage() //添加pdf页树
        }
      }
    }

    PDF.save(title + '.pdf')  //pdf下载
    if (typeof callback === 'function') {
      callback()
    }
  })
}

export { downloadPdf }

   第二版:在h5页面上先设置好每一页要显示的数据存为对象传给insPdfUtil页面遍历生成PDF

 缺点:移动设备部分下载失败

知识点:html2canvas插件可以绘制$el虚拟dom,也可直接传真实DOM(注:需要注册到页面上,可以在页面上定义一个div占位,在操作dom元素将元素插入该位置)

实现代码:

 1)weixin项目的   pdftemplate.vue文件

1、使用ref获取虚拟dom的方式
2、使用操作dom的方式
3、动态生成dom,必须要挂载到页面上,可以先在页面写一个占位的标签
<template>
  <view v-if="calData">
    <!-- 使用ref获取虚拟dom的方式 -->
    <view ref="topHead"> ... </view>
    <!-- 使用操作dom的方式 -->
    <view class="topBody"> ... </view>
    <!-- 动态生成dom,必须要挂载到页面上,可以先在页面写一个占位的标签 -->
    <view class="perch"> ... </view>
    .......
  </view>
</template>
import { downloadPdf } from '@/common/utils/insPdfUtil' //实现转换的页面
export default {
  ......
  //--定义为数组遍历
  data(){
    refList: []
  }
  mounted(){
    if (this.calData && this.calData.userInfo && this.calData.userInfo.name) {
      this.htmlTitle = this.calData.userInfo.name + '的计划书'
    }
    
    //this.$refs.refContent.$el:需要转化的页面内容
    //this.htmlTitle:文件名

    this.refList.push(this.$refs.refContent.$el)

    let topBody = document.queryselect('.topBody')
    this.refList.push(topBody)

    //--动态生成dom,必须要挂载到页面上,可以先在页面写一个占位的标签
    let div = document.createElement("div")
    div.innerHtml = '动态生成的div'
    let perch = document.createElement("perch").appendChild(div)
    this.refList.push(div)

    downloadPdf(this.refList, this.htmlTitle, () => {回调函数})
  }
}

        2)insPdfUtil.js文件

import html2canvas from 'html2canvas'
import JsPDF from 'jspdf'

/**
 * 创建并下载pdf
 * html2canvas画是异步画图,所以这里需要用到promise.all
 * @param {HTMLElement} targetDom dom元素
 * @param {String} title pdf保存名字
 * @param {function} callback 回调函数
 */
const downloadPdf = (targetList, title, callback) => {
  var pdf = new JsPDF('', 'pt', 'a4', true)
  const promiseList = []
  targetList.forEach((item) => {
    promiseList.push(
      html2canvas(item, {
        useCORS: true,
        scale: 1
      })
    )
  })
  Promise.all(promiseList).then((resList) => {
    resList.forEach((item, index) => {
      var contentWidth = item.width
      var contentHeight = item.height
      // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
      var imgWidth = 595.28
      var imgHeight = (592.28 / contentWidth) * contentHeight
      var pageData = item.toDataURL('', 1)
      pdf.addImage(pageData, 'JPEG', 0, 10, imgWidth, imgHeight)
      if (index < resList.length - 1) {
        pdf.addPage()
      }

      if (index === resList.length - 1) {
        PDF.save(title + '.pdf')  //pdf下载
        if (typeof callback === 'function') {
          callback()
        }
      }
    })
  })
}

export { downloadPdf }

第三版:利用调后端上传文件的接口生成下载链接,PDF下载更改为a标签下载的方式

缺点:没有解决IOS下载失败的问题

实现代码:insPdfUtil.js文件

import html2canvas from 'html2canvas'
import JsPDF from 'jspdf'
//调接口
import constant from '@/common/constant'
import store from '@/store'

/**
 * 创建并下载pdf
 * html2canvas画是异步画图,所以这里需要用到promise.all
 * @param {HTMLElement} targetDom dom元素
 * @param {String} title pdf保存名字
 * @param {function} callback 回调函数
 */
const downloadPdf = (targetList, title, callback) => {
  var pdf = new JsPDF('', 'pt', 'a4', true)
  const promiseList = []
  targetList.forEach((item) => {
    promiseList.push(
      html2canvas(item, {
        useCORS: true,
        scale: 1
      })
    )
  })
  Promise.all(promiseList).then((resList) => {
    resList.forEach((item, index) => {
      var contentWidth = item.width
      var contentHeight = item.height
      // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
      var imgWidth = 595.28
      var imgHeight = (592.28 / contentWidth) * contentHeight
      var pageData = item.toDataURL('', 1)
      pdf.addImage(pageData, 'JPEG', 0, 10, imgWidth, imgHeight)
      if (index < resList.length - 1) {
        pdf.addPage()
      }

      if (index === resList.length - 1) {
        const file = pdf.output('blob')
        // 这样子后台就有名字了
        const fileObject = new File([file], title + '.pdf', { type: 'application/pdf' })
        uploadFileXHR(fileObject, (downloadUrl) => {
          // 利用a标签的download属性下载pdf,IOS不适用
          const a = document.createElement('a')
          a.setAttribute('href', downloadUrl)
          a.download = title + '.pdf'
          a.click()

          if (typeof callback === 'function') {
            callback()
          }
        })
      }
    })
  })
}

// --调后端接口的代码
const uploadFileXHR = (file, successUpload) => {
  const uploadUrl = `${constant.reqBaseUrl}/ins/file/tenantUploadFile`
  const data = {
    tenant: 'AKIAIOSFODNN7EXAMPLE',
    fileType: '5e25920b5b5b11e9bf97080027e99028',
    bucketName: 'papers'
  }
  const header = {
    token: store.getters.token
  }
  const formData = new FormData()
  for (const keys in data) {
    formData.append(keys, data[keys])
  }
  formData.append('upload', file)
  const xhr = new XMLHttpRequest()
  xhr.open('POST', uploadUrl, true)
  for (const keys in header) {
    xhr.setRequestHeader(keys, header[keys])
  }
  xhr.onreadystatechange = (ev) => {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        const data = JSON.parse(xhr.responseText)
        if (data.resCode === '0') {
          successUpload(data.data.preview_url)
        }
      } else {
        //
      }
    }
  }
  xhr.send(formData)
}

export { downloadPdf }

第四版:将后端生成的pdf文件地址通过webview通信传回APP,利用uniapp的uni.downloadFile和uni.saveFile下载文件

实现代码:1) insPdfUtil.js文件

.....
// 封装的webview通信代码
import { postmessageJumpOnApp } from '@/common/utils/insWebviewUtil'
.....

if (index === resList.length - 1) {
  const file = pdf.output('blob')
  // 这样子后台就有名字了
  const fileObject = new File([file], title + '.pdf', { type: 'application/pdf' })
  uploadFileXHR(fileObject, (downloadUrl) => {
    /* const a = document.createElement('a')
    a.setAttribute('href', downloadUrl)
    a.download = title + '.pdf'
    a.click() */
    
    // ----------------
    postmessageJumpOnApp(downloadUrl)

    if (typeof callback === 'function') {
      callback()
    }
  })
}

.....

  2) app项目页面 pdfpages.vue

<template>
  <view v-if="src">
    <view v-if="!pageData.customerVisible && show">
      <web-view :src="src" @message="message" />
      <!--  -->
    </view>
  </view>
  .......
</template>

<script>
......
methods: {
  message(e) {
    if (e &&e.detail && e.detail.data &&e.detail.data.length &&e.detail.data[0].jumpTo) {
      // 接受到weixin项目传过来的pdf文件地址
      uni.downloadFile({
        url: e.detail.data[0].jumpTo,
        success: (res) => {
          if (res.statusCode === 200) {
            var filePath = res.tempFilePath;
            uni.saveFile({
              tempFilePath: filePath,
              success: function (res) {
                var savedFilePath = res.savedFilePath;
                uni.showToast({
                  icon:'none',
                  mask:true,
                  title:'文件已保存并即将打开',
                  duration:1000
                })
                setTimeout(()=>{
                  uni.openDocument({
                    filePath: savedFilePath,
                    success: function (res) {
                      console.log('打开文档成功');
                    }
                  });
                },1000)
              },
              fail:(err)=>{
                uni.showToast({
                  icon:'none',
                  mask:true,
                  title:'文件下载失败',
                  duration:1000
                })
              }
            });
          }
        }
      })
      return
    }
  },
}
......

</script>
 类似资料: