VUE3+TypeScript项目,使用html2Canvas+jspdf生成PDF并实现--分页--页眉--页尾
使用html2Canvas+JsPDF生成pdf,并实现分页添加页眉页尾
1.封装方法htmlToPdfPage.ts
/**
path: src/utils/htmlToPdfPage.ts
name: 导出页面为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元素
// debugger
const 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的高度
// 转化成图片Data
const 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 = 595
const 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 -= 842
remainingHeight -= oneCanvasHeight
pageNumber += 1
if (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 = currentPage
newHeaderDom.querySelector('#pdfPage-total').innerText = totalPage
}
document.documentElement.append(newHeaderDom)
const { height: imgHeaderHeight, data: headerCanvas } = await toCanvas(newHeaderDom)
// const imgHeaderHeight = 595 * headerHegith / headerWidth
await 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 = currentPage
newFooterDom.querySelector('#footer-total').innerText = totalPage
}
document.documentElement.append(newFooterDom)
const { height: imgFooterHeight, data: footerCanvas } = await toCanvas(newFooterDom)
// const imgFooterHeight = 595 * footerHegith / footerWidth
await 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 = true
isExportPdf.value = true
await nextTick()
setTimeout(async () => {
htmlToPdfPage.getPdf('pdf-title').then((res:any) => {
disabledFalg.value = false
isExportPdf.value = false
if(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;
}