使用html2Canvas+JsPDF生成pdf,并實現分頁添加頁眉頁尾
1.封裝方法htmlToPdfPage.ts
/**path: src/utils/htmlToPdfPage.tsname: 導出頁面為PDF格式 并添加頁眉頁尾
**/
/*** 封裝思路* 1.將頁面根據A4大小分隔邊距,避免內容被中間截斷* 所有元素層級不要太深,只有一個表格需要再深入判斷,向上統計高度* const parentElement = document.querySelector('.el-form');const childElements = parentElement.childNodes;const filteredChildElements = Array.from(childElements).filter((node) => node.nodeType === Node.ELEMENT_NODE);* 2.根據元素的層級關系,循環childElements計算元素的高度* 3.將頁面轉換成Canvas,根據canvas的高度來截取分頁圖片高度* 4.使用pdfjs截取canvas生成的圖片根據A4高度逐漸加入到pdf中,pdf.addPage(),pdf.addImage()addImage(圖片canvas資源,格式,圖片寬度的x軸,圖片高度的y軸,圖片寬度,圖片高度)
**/
import { uploadFile } from '@/api/common'
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'const A4_WIDTH = 595
const A4_HEIGHT = 842
const pdf = new JsPDF({unit: 'pt',format: 'a4',orientation: 'p'
})
// 轉換為canvas
const toCanvas = async (element:any) => {// canvas元素// debuggerconst canvas = await html2Canvas(element, {allowTaint: true, // 允許渲染跨域圖片scale: window.devicePixelRatio * 2, // 增加清晰度useCORS: true // 允許跨域})const canvasWidth = canvas.width // 獲取canavs轉化后的寬度const canvasHeight = canvas.height // 獲取canvas轉化后的高度const height = Math.floor(595 * canvasHeight / canvasWidth) // 根據595寬度比例計算canvas的高度// 轉化成圖片Dataconst canvasData = await canvas.toDataURL('image/jpeg', 1.0)return { canvasWidth, canvasHeight, height, data: canvasData }
}export const htmlToPdfPage: any = {async getPdf (title:any) {return new Promise((resolve, reject) => {html2Canvas(document.querySelector('#pdfPage') as any, {allowTaint: true, // 允許渲染跨域圖片scale: window.devicePixelRatio * 2, // 增加清晰度useCORS: true // 允許跨域}).then(async (canvas) => {// 內容的寬度const contentCanvasWidth = canvas.width// 內容高度const contentCanvasHeight = canvas.height// 按照a4紙的尺寸[595,842]計算內容canvas一頁高度const oneCanvasHeight = Math.floor(contentCanvasWidth * 842 / 595)// 未生成pdf的html頁面高度let remainingHeight = contentCanvasHeight// 頁面偏移let position = 0 // 上下邊距分別為10// 每頁寬度,A4比例下canvas內容高度const imgWidth = 595const imgHeight = 595 * contentCanvasHeight / contentCanvasWidth// ************************************ 計算頁碼 start ********************************************const headerDom: any = document.querySelector('#pdfPage-header')const { height: imgHeaderHeight, canvasHeight: headerCanvasHeight } = await toCanvas(headerDom)const footerDom: any = document.querySelector('#pdfPage-footer')const { height: imgFooterHeight, canvasHeight: footerCanvasHeight } = await toCanvas(footerDom)// 一頁高度減去頁眉頁尾后內容的高度const contentHeight = oneCanvasHeight - headerCanvasHeight - footerCanvasHeight// 總頁數const totalPage = Math.ceil(contentCanvasHeight / contentHeight)// ************************************ 計算頁碼 end ********************************************// canvas轉圖片數據const pageData = canvas.toDataURL('image/jpeg', 1.0)// 新建JsPDF對象const PDF = new JsPDF('' as any, 'pt', 'a4')let pageNumber = 1 // 頁數// 判斷是否分頁if (remainingHeight < oneCanvasHeight) {await addHeader(PDF, pageNumber, totalPage)PDF.addImage(pageData, 'JPEG', 0, imgHeaderHeight, imgWidth, imgHeight)await addFooter(PDF)position -= 842} else {while (remainingHeight > 0) {if (position === 0) {await addHeader(PDF, pageNumber, totalPage)PDF.addImage(pageData, 'JPEG', 0, imgHeaderHeight, imgWidth, imgHeight)await addFooter(PDF)} else {PDF.addImage(pageData, 'JPEG', 0, position + (pageNumber * imgHeaderHeight) + ((pageNumber - 1) * imgFooterHeight), imgWidth, imgHeight)await addHeader(PDF, pageNumber, totalPage)await addFooter(PDF)}position -= 842remainingHeight -= oneCanvasHeightpageNumber += 1if (remainingHeight > 0) {PDF.addPage()}}}// 保存文件--測試PDF.save(title + '.pdf')resolve(1)// 上傳文件--實現功能// const formData = new FormData()// formData.append('file', PDF.output('blob'), title + '.pdf')// uploadFile(formData).then((res:any) => {// resolve(res)// }).catch((err:any) => {// reject(err)// })})})}
}
// 添加頁眉
const addHeader = async (pdf: any, currentPage?: any, totalPage?: any) => {const headerDom: any = document.querySelector('#pdfPage-header')const newHeaderDom = headerDom.cloneNode(true)if (currentPage && totalPage) {newHeaderDom.querySelector('#pdfPage-page').innerText = currentPagenewHeaderDom.querySelector('#pdfPage-total').innerText = totalPage}document.documentElement.append(newHeaderDom)const { height: imgHeaderHeight, data: headerCanvas } = await toCanvas(newHeaderDom)// const imgHeaderHeight = 595 * headerHegith / headerWidthawait pdf.addImage(headerCanvas, 'JPEG', 0, 0, A4_WIDTH, imgHeaderHeight)
}
// 添加頁尾
const addFooter = async (pdf: any, currentPage?: any, totalPage?: any) => {const footerDom: any = document.querySelector('#pdfPage-footer')const newFooterDom = footerDom.cloneNode(true)if (currentPage && totalPage) {newFooterDom.querySelector('#footer-page').innerText = currentPagenewFooterDom.querySelector('#footer-total').innerText = totalPage}document.documentElement.append(newFooterDom)const { height: imgFooterHeight, data: footerCanvas } = await toCanvas(newFooterDom)// const imgFooterHeight = 595 * footerHegith / footerWidthawait pdf.addImage(footerCanvas, 'JPEG', 0, A4_HEIGHT - imgFooterHeight, A4_WIDTH, imgFooterHeight)
}
export default htmlToPdfPage
2.頁面調用
<template><div class="page"><button @click="exportToPdf">導出 PDF</button><!-- 頁眉 --><div class="page-header" id="pdfPage-header" v-if="isExportPdf">......<div class="row-between mt20">......<div v-if="isExportPdf"> 頁碼 Page: <span id="pdfPage-page">1</span> of <span id="pdfPage-total">5</span></div></div></div><!-- 內容 --><!-- 此案例通過page-one固定了每頁高度,動態數據可根據每頁高度獲取最近dom元素,添加margin-top,避免內容中間截斷 --><div class="page-content" id="pdfPage" v-loading="pageLoading"><div :class="isExportPdf ? 'page-flex page-one' : 'page-flex'"></div><div :class="isExportPdf ? 'page-flex page-one' : 'page-flex'"></div><div><!-- 頁尾 --><div class="page-footer" id="pdfPage-footer" v-if="isExportPdf">......<div v-if="isExportPdf"> 頁碼 Page: <span id="footer-page">1</span> of <span id="footer-total">5</span></div></div></div>
</template><script setup lang="ts">
import { ref, getCurrentInstance, reactive, computed, onMounted, nextTick } from 'vue'
import htmlToPdfPage from '@/utils/htmlToPdfPage'disabledFalg.value = route.query.type === 'view'
const isExportPdf = ref(false) // 是否為導出pdf狀態const exportToPdf = async () => {disabledFalg.value = trueisExportPdf.value = trueawait nextTick()setTimeout(async () => {htmlToPdfPage.getPdf('pdf-title').then((res:any) => {disabledFalg.value = falseisExportPdf.value = falseif(res) {// 業務邏輯處理}})}, 100)
}
</script><style lang="scss" scoped>.page{padding: 10px 20px;font-size: 15px;background-color: #ffffff;}.page-header {width: 960px;padding: 20px 50px 0 50px;margin: 0 auto;height: 150px;}.page-content {width: 960px;margin: 0 auto;padding: 0px 50px;font-family: Arial, Helvetica, sans-serif;background-color: #ffffff;}.page-footer {width: 960px;padding: 0px 50px 10px 50px;height: 90px;margin: 0 auto;}.page-one {height: 1118px;}.page-flex {display: flex;flex-direction: column;}