工作中接到打印特定数据的需求,由于当前系统已在使用并且需在当前页打印部分数据,如果直接调用浏览器的打印需要整页打印,这并不满足打印部分数据的需求,于是曲线救国通过生成 pdf 的方式满足业务需求,入手了 html2canvas 以及 jspdf 。
html2canvas 可以基于前端将页面上任一节点通过 canvas 生成图片,而 jspdf 则可以通过将图片生成特定大小的 pdf 文件。
话不多说,上代码:
节点代码段:
<template>
<div id="pdfDom">
<div class="pdfPage" :id="'pdfPage' + index" v-for="(taskPage, index) in tasks" :key="index">
<span v-for="(item) in taskPage"
:key="item.id"
class="list-item">
<barcode v-bind:value="item.taskSn"
:width=1.5></barcode>
<p>任务号:{{item.taskSn}}</p>
<p>时间:{{item.time}}</p>
<div style="display:flex">
<div style="width:50%">机盘数:</div>
<div style="width:50%">机盘人:</div>
</div>
<div style="display:flex">
<div style="width:50%">手盘数: </div>
<div style="width:50%">手盘人:</div>
</div>
<p>备注:</p>
</span>
</div>
</div>
</template>
<script>
import VueBarcode from 'vue-barcode'
export default {
name: 'print',
props: {
taskList: Array
},
computed: {
tasks () {
let arr = []
let taskList = this.taskList.slice()
let length = Math.ceil(taskList.length / 200)
for (let i = 0; i < length; i++) {
arr[i] = taskList.splice(0, 200)
}
return arr
}
},
components: {
'barcode': VueBarcode
},
data () {
return {
}
},
mounted () {
console.log(this.tasks)
},
methods: {
}
}
</script>
<style scoped>
.pdfPage {
/* */
position: fixed;
top:0;
background:#fff;
left:-5500px;
width: 1050px;
font-size: 0;
}
.list-item {
display: inline-block;
font-size: 12px;
box-sizing: border-box;
width: 25%;
height: 299px;
padding: 0 10px;
}
</style>
html2canvas 与 jspdf 使用:
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'
import { times } from '../utils/utils'
export function getPdf (title = 'Demo') {
let length = document.querySelector('#pdfDom').getElementsByClassName('pdfPage').length
let arr = []
for (let i = 0; i < length; i++) {
arr[i] = new Promise((resolve) => {
html2Canvas(document.querySelector(`#pdfPage${i}`), {
allowTaint: true,
scale: 2 // 设置以调高清晰度,可参考 canvas 如何生成高清图原理
}).then(function (canvas) {
let arrObj = []
let contentWidth = canvas.width
let contentHeight = canvas.height
let pageHeight = (contentWidth / 592.28) * 841.89
let leftHeight = contentHeight
let position = 0
let imgWidth = 595.28
let imgHeight = (592.28 / contentWidth) * contentHeight // 这里是一整个 pdfPage 节点的高度,例子中的 pdfPage 实际是四页 a4 纸大小
let pageData = canvas.toDataURL('image/jpeg', 1.0) // canvas 将页面生成图片 database64 数据
if (leftHeight < pageHeight) {
arrObj.push({
pageData: pageData,
imgWidth: imgWidth,
imgHeight: imgHeight
})
} else {
while (leftHeight > 0) {
arrObj.push({
pageData: pageData,
position: position,
imgWidth: imgWidth,
imgHeight: imgHeight
})
leftHeight -= pageHeight
position -= 841.89
if (leftHeight > 0) {
arrObj.push({
addPage: true
})
}
}
}
resolve(arrObj)
})
})
}
Promise.all(arr).then(function (data) {
let PDF = new JsPDF('', 'pt', 'a4') // pdf 设置 a4 可以将刚刚每一个 pdfPage 节点,按照 a4 的大小分为 4 页
data.forEach((item) => {
item.forEach(i => {
if (i.addPage) {
PDF.addPage()
} else {
PDF.addImage(i.pageData, 'JPEG', 0, i.position || 0, i.imgWidth, i.imgHeight)
}
})
})
PDF.save(title + `_${times(new Date())}.pdf`)
})
}
这里生成的是每页 A4 纸大小的 pdf,宽高可以参考 592.28 以及 841.89,但由于页面元素没有控制到百分百为 a4 页面大小,生成超长图片的话会在一定的偏移,所以这里我在写节点页面的时候控制每 200 个元素为四页,每四页的大小的元素生成一次图片,jspdf 通过 a4 的设置,将图片分割成四页,以此控制存在的偏移量。
出来的 pdf 效果图如下:
另外在用此类方法满足业务需求时,有遇到一个需要将横向节点(如下图)打印出来,此时可以利用 canvas 将 html2canvas 生成的图片旋转处理,再重新生成图片:
// canvas 旋转关键代码可参考以下
ctx.translate(rotateWidth, 0)
ctx.rotate(90 * Math.PI / 180)
ctx.drawImage(img, 0, 0, rotateHeight, rotateWidth)
let pageData = rotateImageCavas.toDataURL('image/jpeg', 1.0)
html2canvas 支持有限,有部分 css 不支持,例如 box-shadow、position:absolute 等,毕竟是 cavas 画图,支持有限,使用 css 写节点时需尽量避开 canvas 不支持实现的 css。