jspdf+html2canvas+iframe生成pdf文件

1,113 阅读3分钟

背景

在页面中不影响用户使用的情况下使用iframe嵌入另一个页面,并将页面打印传给后端(用户看不到该页面),完成最后需求在10s左右.(下面代码是基于嵌入iframe中的页面不是自己开发)

代码存在问题

在页面太长时,生成pdf隔断位置不合理,没有做具体判断,现在代码只是生成了pdf

需要注意的地方

获取iframe的内容用的是

document.getElementById(getIframe${self.getIframeId}).contentWindow

run是入口文件

代码思路

下载插件引入文件

npm install html2canvas
npm install jspdf
import jsPDF from "jspdf"
import html2canvas from 'html2canvas'

实现逻辑

  1. 创建iframe节点
// 创建iframe节点
    let iframe = document.createElement('iframe')
    // 设置iframe的src
    let [pathname] = window.location.pathname.split('/').filter(item => isNaN(Number(item)) === false && item !== '')
    iframe.src = `${window.location.protocol}//${location.host}/app/xxxx`
    iframe.style.width = '1000px'
    iframe.style.height = 'auto'
    iframe.style.zIndex = '-999'
    iframe.style.position = 'relative'
    iframe.style.top = '-100%'
    iframe.setAttribute('id', `getIframe${this.getIframeId}`)
    // 将iframe加到body上
    document.body.appendChild(iframe)
  1. 判断iframe是否加载完毕

    这个时候的判断只是判断iframe里面js文件css文件有没有加载完成,和页面中的接口加载完成否没有关系

    iframeLoaded(iframeEl, callback) {
    if (iframeEl.attachEvent) {
      iframeEl.attachEvent('onload', () => {
        if (callback && typeof (callback) === 'function') {
          callback.call(this)// window
        }
      })
    } else {
      iframeEl.onload = () => {
        if (callback && typeof (callback) === 'function') {
          callback.call(this)
        }
      }
    }
  },
  1. 判断iframe嵌入页面是否加载完毕

    使用定时器判断页面中的某一类是否加载完成,如果加载完成则说明页面渲染完成

 modificationPage() {
    let flag = 0;
    let timer = setInterval(() => {
      flag += 1;
      if (document.getElementById(`getIframe${self.getIframeId}`).contentWindow.document !== null
        && document.getElementById(`getIframe${self.getIframeId}`).contentWindow.document.querySelector('body') !== null
        && document.getElementById(`getIframe${self.getIframeId}`).contentWindow.document.querySelector('body').querySelector('.class') !== null
        && document.getElementById(`getIframe${self.getIframeId}`).contentWindow.document.querySelector('body').querySelector('.class').querySelector('.class-son') !== null
        && document.getElementById(`getIframe${self.getIframeId}`).contentWindow.document.querySelector('body').querySelector('.class').querySelector('.class-son').querySelector('span').innerHTML) {
        clearInterval(timer);
        clearTimeout(flag);
        setTimeout(() => {
          // alert(flag);

        }, 2000)
      } else {
        if (flag > 50) {
          clearInterval(timer);
          alert('流程归档失败,联系管理员');
          // count = false;
          localStorage.setItem('count', false)
          window.stopLoading();
          document.getElementById(`getIframe${self.getIframeId}`).remove()
        }
      }
    }, 500)
 },
  1. 按照需求将页面做修改,页面重绘加载

    将iframe的高设置为和iframe内容的高一致,header和side是不需要打印的,所以隐藏掉,等页面重绘完成后开始打印

          const iframe = document.getElementById(`getIframe${self.getIframeId}`).contentWindow
          let iframeContent = iframe.document.querySelector('body').querySelector('.class')
          let iframeHeader = iframe.document.querySelector('body').querySelector('.header')
          let iframeSide = iframe.document.querySelector('body').querySelector('.setting')
          iframeHeader.style.display = 'none'
          iframeSide.style.display = 'none'
          iframeContent.style.position = 'static'
          document.getElementById(`getIframe${self.getIframeId}`).style.height = `${iframeContent.scrollHeight}px`
          setTimeout(() => {
            self.producePdf()
          }, 5000)
  1. 开始打印页面,保存为pdf
producePdf() {
    // var jsPDF = jsPDF.jsPDF
    const _this = self
    const canvas_pdf = document.getElementById(`getIframe${this.getIframeId}`).contentWindow.document.documentElement
    console.log(canvas_pdf)
    canvas_pdf.style.background = '#FFFFFF'
    const contentWidth = canvas_pdf.scrollWidth
    const contentHeight = canvas_pdf.scrollHeight
    // 一页pdf显示html页面生成的canvas高度;
    const pageHeight = (contentWidth / 592.28) * 841.89
    // 未生成pdf的html页面高度
    let leftHeight = contentHeight
    // 页面偏移
    let position = 0
    // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
    const imgWidth = 595.28
    const imgHeight = (592.28 / contentWidth) * contentHeight
    const iframeScrollY = canvas_pdf.scrollTop
    const iframeScrollX = canvas_pdf.scrollLeft

    html2canvas(canvas_pdf, {
      width: contentWidth,
      height: contentHeight,
      allowTaint: true,
      useCORS: true,
      x: iframeScrollX,
      y: iframeScrollY
    }).then(function (canvas) {
      document.body.appendChild(canvas)
      const pageData = canvas.toDataURL('image/jpeg', 1) // 将canvas转换成img的src流
      
      // console.log("base64编码数据:", imgUrl);
      _this.url = pageData
      var pdf = new jsPDF('', 'pt', 'a4')

      // 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
      // 当内容未超过pdf一页显示的范围,无需分页
      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()
          }
        }
      }
      console.log(pdf)
      var datura = pdf.output('dataurlstring')
      self.upLoad(datura)
      document.getElementById(`getIframe${self.getIframeId}`).remove()
    })
  },
  1. 以formdata的格式传给后端

  upLoad(file) {
    let params = new FormData()
    params.append('file', self.convertBase64ToFile(file, `${this.getFileName ? this.getFileName : '归档文件'}`))
    params.append('tableName', this.getTableName)
    params.append('fieldName', this.getFieldName)
    params.append('documentId', this.getDocumentId)
    axios({
      url: `${window.location.protocol + '//' + location.host}/archive/file/uploadFile`,
      method: 'POST',
      disableSuccessMsg: true,
      timeout: 1000 * 60 * 30,
      data: params,
      responseType: 'blob'
    })
      .then((resp) => {
        
        if (self.getIsArchive) {
          self.archive()
        } else {
          window.stopLoading()
        }
      }).catch(() => {
        alert('上传失败')
        console.log('上传失败')
        window.stopLoading()
      })
  },
  convertBase64ToFile(urlData, filename) {
    var arr = urlData.split('base64,')
    var type = arr[0].match(/:(.*?);/)[1]
    var fileExt = type.split('/')[1]
    var bstr = atob(arr[1])
    var n = bstr.length
    var u8arr = new Uint8Array(n)
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n)
    }
    return new File([u8arr], filename + '.' + fileExt, {
      type: type
    })
  },

完整代码

import jsPDF from 'jspdf'
import html2canvas from 'html2canvas'
import axios from 'axios'
import { outputPDF } from './outputPDF'
let self = null
let pdfOption = {
  /**
   * @param {String} token
   */
  set setToken(token) {
    this.token = token
  },
  /**
   * @param {String} formId
   */
  set setFormId(formId) {
    this.formId = formId
  },
  /**
   * @param {String} documentId
   */
  set setDocumentId(documentId) {
    this.documentId = documentId
  },
  /**
   * @param {String} processId
   */
  set setProcessId(processId) {
    this.processId = processId
  },
  /**
   * @param {String} rowId
   */
  set setRowId(rowId) {
    this.rowId = rowId
  },
  /**
   * @param {String} tabId
   */
  set setTabId(tabId) {
    this.tabId = tabId
  },
  /**
   * @param {String} appId
   */
  set setAppId(appId) {
    this.appId = appId
  },
  /**
   * @param {String} menuId
   */
  set setMenuId(menuId) {
    this.menuId = menuId
  },
  /**
   * @param {String} xdaptenantid
   */
  set setXdaptenantid(xdaptenantid) {
    this.xdaptenantid = xdaptenantid
  },

  /**
   * @param {string} iframeId
   */
  set setIframeId(iframeId) {
    this.iframeId = iframeId
  },

  get getIframeId() {
    return this.iframeId
  },
  /**
   * @param {string} tableName
   */
  set setTableName(tableName) {
    this.tableName = tableName
  },

  get getTableName() {
    return this.tableName
  },
  /**
   * @param {string} fieldName
   */
  set setFieldName(fieldName) {
    this.fieldName = fieldName
  },

  get getFieldName() {
    return this.fieldName
  },
  /**
     * @param {string} processFormNo
     */
  set setProcessFormNo(processFormNo) {
    this.processFormNo = processFormNo
  },

  get getProcessFormNo() {
    return this.processFormNo
  },
  /**
* @param {string} sonTableName
*/
  set setSonTableName(sonTableName) {
    this.sonTableName = sonTableName
  },

  get getSonTableName() {
    return this.sonTableName
  },
  /**
   * @param {string} fileNumber
   */
  set setFileNumber(fileNumber) {
    this.fileNumber = fileNumber
  },

  get getFileNumber() {
    return this.fileNumber
  },
  /**
  * @param {string} relNo
  */
  set setRelNo(relNo) {
    this.relNo = relNo
  },

  get getRelNo() {
    return this.relNo
  },
  /**
  * @param {string} archiveProcessFormNo
  */
  set setArchiveProcessFormNo(archiveProcessFormNo) {
    this.archiveProcessFormNo = archiveProcessFormNo
  },

  get getArchiveProcessFormNo() {
    return this.archiveProcessFormNo
  },

  /**
  * @param {boolean} isArchive
  */
  set setIsArchive(isArchive) {
    this.isArchive = isArchive
  },

  get getIsArchive() {
    return this.isArchive
  },
  /**
  * @param {boolean} fileName
  */
  set setFileName(fileName) {
    this.fileName = fileName
  },

  get getFileName() {
    return this.fileName
  },
  //
  get getToken() {
    return this.token
  },

  get getFormId() {
    return this.formId
  },

  get getDocumentId() {
    return this.documentId
  },

  get getProcessId() {
    return this.processId
  },

  get getRowId() {
    return this.rowId
  },

  get getTabId() {
    return this.tabId
  },

  get getAppId() {
    return this.appId
  },

  get getMenuId() {
    return this.menuId
  },

  get getXdaptenantid() {
    return this.xdaptenantid
  },
  produceIframe() {
    // 创建iframe节点
    let iframe = document.createElement('iframe')
    // 设置iframe的src
    let [pathname] = window.location.pathname.split('/').filter(item => isNaN(Number(item)) === false && item !== '')
    iframe.src = `${window.location.protocol}//${location.host}/app/xxxx`
    iframe.style.width = '1000px'
    iframe.style.height = 'auto'
    iframe.style.zIndex = '-999'
    iframe.style.position = 'relative'
    iframe.style.top = '-100%'
    iframe.setAttribute('id', `getIframe${this.getIframeId}`)
    // 将iframe加到body上
    document.body.appendChild(iframe)
  },
  producePdf() {
    // var jsPDF = jsPDF.jsPDF
    const _this = self
    const canvas_pdf = document.getElementById(`getIframe${this.getIframeId}`).contentWindow.document.documentElement
    console.log(canvas_pdf)
    canvas_pdf.style.background = '#FFFFFF'
    const contentWidth = canvas_pdf.scrollWidth
    const contentHeight = canvas_pdf.scrollHeight
    // 一页pdf显示html页面生成的canvas高度;
    const pageHeight = (contentWidth / 592.28) * 841.89
    // 未生成pdf的html页面高度
    let leftHeight = contentHeight
    // 页面偏移
    let position = 0
    // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
    const imgWidth = 595.28
    const imgHeight = (592.28 / contentWidth) * contentHeight
    const iframeScrollY = canvas_pdf.scrollTop
    const iframeScrollX = canvas_pdf.scrollLeft

    html2canvas(canvas_pdf, {
      width: contentWidth,
      height: contentHeight,
      allowTaint: true,
      useCORS: true,
      x: iframeScrollX,
      y: iframeScrollY
    }).then(function (canvas) {
      document.body.appendChild(canvas)
      const pageData = canvas.toDataURL('image/jpeg', 1) // 将canvas转换成img的src流
      
      // console.log("base64编码数据:", imgUrl);
      _this.url = pageData
      var pdf = new jsPDF('', 'pt', 'a4')

      // 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
      // 当内容未超过pdf一页显示的范围,无需分页
      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()
          }
        }
      }
      console.log(pdf)
      var datura = pdf.output('dataurlstring')
      self.upLoad(datura)
      document.getElementById(`getIframe${self.getIframeId}`).remove()
    })
  },
  /**
   * @param {Element} iframeEl iframe的dom节点
   * @param {function} callback 加载完毕后要执行的函数
   * @describe 判断iframe是否加载完毕
   */
  iframeLoaded(iframeEl, callback) {
    if (iframeEl.attachEvent) {
      iframeEl.attachEvent('onload', () => {
        if (callback && typeof (callback) === 'function') {
          callback.call(this)// window
        }
      })
    } else {
      iframeEl.onload = () => {
        if (callback && typeof (callback) === 'function') {
          callback.call(this)
        }
      }
    }
  },

  /**
   * @describe  页面首次加载完成后将不需要打印的东西隐藏掉
   */
  modificationPage() {
    let flag = 0;
    let timer = setInterval(() => {
      flag += 1;
      if (document.getElementById(`getIframe${self.getIframeId}`).contentWindow.document !== null
        && document.getElementById(`getIframe${self.getIframeId}`).contentWindow.document.querySelector('body') !== null
        && document.getElementById(`getIframe${self.getIframeId}`).contentWindow.document.querySelector('body').querySelector('.class') !== null
        && document.getElementById(`getIframe${self.getIframeId}`).contentWindow.document.querySelector('body').querySelector('.class').querySelector('.class-son') !== null
        && document.getElementById(`getIframe${self.getIframeId}`).contentWindow.document.querySelector('body').querySelector('.class').querySelector('.class-son').querySelector('span').innerHTML) {
        clearInterval(timer);
        clearTimeout(flag);
        setTimeout(() => {
          // alert(flag);
          const iframe = document.getElementById(`getIframe${self.getIframeId}`).contentWindow
          let iframeContent = iframe.document.querySelector('body').querySelector('.class')
          let iframeHeader = iframe.document.querySelector('body').querySelector('.header')
          let iframeSide = iframe.document.querySelector('body').querySelector('.setting')
          iframeHeader.style.display = 'none'
          iframeSide.style.display = 'none'
          iframeContent.style.position = 'static'
          document.getElementById(`getIframe${self.getIframeId}`).style.height = `${iframeContent.scrollHeight}px`
          setTimeout(() => {
            self.producePdf()
          }, 5000)
        }, 2000)
      } else {
        if (flag > 50) {
          clearInterval(timer);
          alert('流程归档失败,联系管理员');
          // count = false;
          localStorage.setItem('count', false)
          window.stopLoading();
          document.getElementById(`getIframe${self.getIframeId}`).remove()
        }
      }
    }, 500)
 },
  upLoad(file) {
    let params = new FormData()
    params.append('file', self.convertBase64ToFile(file, `${this.getFileName ? this.getFileName : '归档文件'}`))
    axios({
      url: `${window.location.protocol + '//' + location.host}/uploadFile`,
      method: 'POST',
      data: params,
      responseType: 'blob'
    })
      .then((resp) => {
        
        if (self.getIsArchive) {
          self.archive()
        } else {
          window.stopLoading()
        }
      }).catch(() => {
        alert('上传失败')
        console.log('上传失败')
        window.stopLoading()
      })
  },
  convertBase64ToFile(urlData, filename) {
    var arr = urlData.split('base64,')
    var type = arr[0].match(/:(.*?);/)[1]
    var fileExt = type.split('/')[1]
    var bstr = atob(arr[1])
    var n = bstr.length
    var u8arr = new Uint8Array(n)
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n)
    }
    return new File([u8arr], filename + '.' + fileExt, {
      type: type
    })
  },
  //
  archive() {
    let params = {
      
    }
    axios({
      url: `${window.location.protocol + '//' + location.host}/forwardDirection`,
      method: 'POST',
      data: params,
      responseType: 'blob'
    })
      .then((resp) => {
        window.stopLoading()
        
      }).catch(() => {
        alert('归档 失败')
        console.log('归档失败')
        window.stopLoading()
      })
  },
  /**
   * @param {String} token token
   * @param {String} formId
   * @param {String} documentId
   * @param {String} processId
   * @param {String} rowId
   * @param {String} tabId
   * @param {String} appId
   * @param {String} menuId
   * @param {String} xdaptenantid
   * @describe 执行
   */
  run(token,
    formId, documentId,
    processId, rowId, tabId,
    appId, menuId, xdaptenantid,
    tableName, fieldName, processFormNo,
    sonTableName, fileNumber, relNo,
    archiveProcessFormNo, isArchive, fileName) {
    self = this
    
    this.setToken = token
    this.setFormId = formId
    this.setDocumentId = documentId
    this.setProcessId = processId
    this.setRowId = rowId
    this.setTabId = tabId
    this.setAppId = appId
    this.setMenuId = menuId
    this.setXdaptenantid = xdaptenantid
    this.setTableName = tableName
    this.setFieldName = fieldName
    this.setProcessFormNo = processFormNo
    this.setSonTableName = sonTableName
    this.setFileNumber = fileNumber
    this.setRelNo = relNo
    this.setArchiveProcessFormNo = archiveProcessFormNo
    this.setIsArchive = isArchive
    this.setIframeId = new Date().getTime()
    this.setFileName = fileName;
    let obj = {
      title: "请稍候",
      tips: "归档文件正在生成中,请等待1-2分钟,请勿关闭页面"
    };
    let params = {
      
    }
    let count = 0;
    let flag = setInterval(() => {
      count+=1;
      axios({
        url: `${window.location.protocol + '//' + location.host}/getApproveStatus`,
        method: 'POST',
        timeout: 1000 * 60 * 30,
        data: params,
      })
        .then((resp) => {
          
          if(resp.data.data.status==='COMPLETED'){
            clearInterval(flag);
            window.startLoading(obj);
            this.produceIframe()
            this.iframeLoaded(document.getElementById(`getIframe${this.getIframeId}`).contentWindow, this.modificationPage)
          }
        }).catch(() => {
          clearInterval(flag);
        })
        if(count>10){
          clearInterval(flag);
        }
    }, 2000);

  }

}

export default pdfOption


//startLoading.js

function startLoad(w){
	var showLoadingOnOff = false;
	var loadingBox = null;
	var loadingTxt = null;
	var loadingTips = null;
	var timer = null;
	var oW = null;
	function startLoading(objInfo){
		
		var mTitle = objInfo && objInfo.title || "请稍候";
		var tips = objInfo && objInfo.tips || "加载中...";

		if(showLoadingOnOff == false){
			loadingBox = document.createElement("div");
			loadingBox.style.cssText = `width:420px;height:120px;position:absolute;top:50%;left:50%;margin-left:-60px;margin-top:-60px;background:rgba(0,0,0,0.5);border-radius:10px;overflow:hidden;z-index: 99999;`;

			loadingTips = document.createElement("p");
			loadingTips.style.cssText = `white-space:nowrap;height:30px;text-align:center;margin:30px auto 20px;color:white;`;		

			loadingTxt = document.createElement("p");
			loadingTxt.style.cssText = `text-align:center;color:#f5f5f5;font-size:15px;`;

			loadingBox.appendChild(loadingTips);
			loadingBox.appendChild(loadingTxt);
			document.body.appendChild(loadingBox);
		}
		loadingTxt.innerHTML = mTitle;
		loadingTips.innerHTML = tips;
		showLoadingOnOff = true;
		loadingBox.style.display = "block";

		oW = loadingTips.offsetWidth;
		loadingTips.style.width = "0px";
		loadingTips.style.overflow = "hidden";
		
		var iW = 0;
		timer = setInterval(function(){
			iW += 16;
			if(iW >= oW){
				iW=0;
			}
			loadingTips.style.width = iW+"px";
		},100);
	}

	function stopLoading(){
		clearInterval(timer);
		loadingBox.style.display = "none";
		loadingTips.style.width = oW+"px";
		timer = null;
	}
	w.startLoading = startLoading;
	w.stopLoading = stopLoading;

}

export default startLoad