导入依赖
html2canvas依赖
npm install html2canvas
jspdf依赖
npm install jspdf
pdf导出
以导出横向,A4大小的pdf为例
规律:1. html2canvas 中,在保持jsPDF中的宽高不变的情况下,设置html2canvas中的 width 和 height 值越小,导出的pdf越显示不全(会被放大,只能看到局部),反之值越大,导出的pdf越显示完整(值也不能过大,过大在pdf中就显示的越小)。
2. jsPDF 中,在保持html2canvas中的宽高不变的情况下,pdf.addImage(pageData, ‘JPEG’, 5, yPosition, width, height) width 和 height 值越小,导出的pdf越显示完整,反之导出的pdf越显示不全。
总结:html2canvas 与 jsPDF 设置刚好相反,合理设置大小,才能使数据撑满整个pdf。
index.vue执行导出pdf页面
<a-spin :spinning="pdfConfirmLoading"><div ref="pdfDiv"><pdf-template :ref="'pdfTemplate' + i" v-for="i in arrNum"></pdf-template></div>
</a-spin><script>import {printPdf} from './utils/index'import pdfTemplate from './template/pdfTemplate'export default {name: 'courseTableList',components: {pdfTemplate},data() {return {pdfVisible: false,disableSubmit: false,arrNum: 1,pdfConfirmLoading: false,}},created() {},methods: {//导出pdfasync handleExportPdf() {this.handleElement()},handleElement() {if(this.selectionRows.length > 0) {this.pdfConfirmLoading = trueconst groupedData = this.selectionRows.reduce((result, item) => {// 检查是否已存在以该teacher为键的分组if (!result[item.instructor]) {result[item.instructor] = [];}// 将当前项添加到对应的分组中result[item.instructor].push(item);return result;}, {});// 转换成数组形式const groupedArray = Object.values(groupedData);let len = groupedArray.lengththis.arrNum = lenlet that = thisthis.pdfVisible = truethis.$nextTick(() => {for (let index = 0; index < groupedArray.length; index++) {const item = groupedArray[index];let num = index + 1let pageFooter = num + ' of ' + len;that.$refs['pdfTemplate' + num][0].setDataList(item, pageFooter);}this.pdfConfirmLoading = false})} else {this.$message.warning("请勾选数据")}},//确认导出pdfasync confirmExportPdf() {this.pdfConfirmLoading = trueawait printPdf(this.$refs.pdfDiv, "courseSchedule")this.pdfConfirmLoading = falsethis.pdfVisible = false},}}
</script>
pdfTemplate.vue模板,根据需求自定义创建
<template><div class="content" ref="pdfContent" :key="JSON.stringify(datasource)"><!-- 头部 --><div class="header"><div class="header_row1"><div class="header_row1_v"><div class="header_row1_left"><span>Regd. User: YiZhong College</span></div><div class="header_row1_right"><span>Christine Xia</span></div></div><div class="header_row1_middle"><span>Timetables</span></div></div><div class="header_row2"><div class="header_row2_v"><div class="header_row2_left"><span>School: FD - YiZhong Cambridge International School</span></div><div class="header_row2_right"><span>FDCC1</span></div></div><div class="header_row2_middle"><span>FD 2022-23 Teacher's Timetables</span></div></div><div class="header_line"></div></div><!-- 表格 --><div class="table_middle"><a-table :columns="columns" :data-source="datasource" bordered :pagination="false"><!-- 时间段 --><tamplate slot="timePeriodSlot" slot-scope="text, record"><span>{{record.period}}<br> {{text}} ~ {{record.endTime}}</span></tamplate></a-table></div><!-- 页脚 --><div class="footer"><div class="footer_line"></div><div class="footer_end"><div class="footer_end_datetime">{{this.currentTime}}</div><div class="footer_end_pages">{{this.pageNum}}</div></div></div></div>
</template><script>let columns = [{title: '教师',dataIndex: 'startTime',scopedSlots: { customRender: 'timePeriodSlot' },customCell: () => {return {style: {'min-width': '120px',},};},},{title: "Monday",children: [{title: '',dataIndex: 'monday',key: 'monday',customCell: () => {return {style: {'min-width': '180px',},};},},],},{title: "Tues",children: [{title: '',dataIndex: 'tuesday',key: 'tuesday',scopedSlots: { customRender: 'childrenRender' },customCell: () => {return {style: {'min-width': '180px',},};},},],},{title: "Wed",children: [{title: '',dataIndex: 'wednesday',key: 'wednesday',scopedSlots: { customRender: 'childrenRender' },customCell: () => {return {style: {'min-width': '180px',},};},},],},{title: "Thurs",children: [{title: '',dataIndex: 'thursday',key: 'thursday',scopedSlots: { customRender: 'childrenRender' },customCell: () => {return {style: {'min-width': '180px',},};},},],},{title: "Friday",children: [{title: '',dataIndex: 'friday',key: 'friday',scopedSlots: { customRender: 'childrenRender' },customCell: () => {return {style: {'min-width': '180px',},};},},],},
];export default {name: "pdfExport",data() {return {currentTime: '',pageNum: 'Page 1 of 1',datasource: [],}},computed: {columns() {return columns}},created() {this.getCurrentTime();},methods: {setDataList(list, pageFooter) {this.datasource = listif(this.datasource.length > 0) {this.columns[0].title = this.datasource[0].instructor}this.sortByStartTime()if(pageFooter) {this.pageNum = pageFooter}},sortByStartTime() {this.datasource.sort((a, b) => {const timeA = new Date(`2023/01/01 ${a.startTime}`);const timeB = new Date(`2023/01/01 ${b.startTime}`);return timeA - timeB;});},getCurrentTime() {const date = new Date();const month = date.getMonth() + 1;const day = date.getDate();const year = date.getFullYear();const hours = date.getHours();const minutes = date.getMinutes();const seconds = date.getSeconds();// 格式化为指定格式的字符串const dateString = `${this.formatNumber(month)}/${this.formatNumber(day)}/${year}`;const timeString = `${this.formatNumber(hours)}:${this.formatNumber(minutes)}:${this.formatNumber(seconds)}`;this.currentTime = `${dateString} ${timeString}`;},formatNumber(number) {return number < 10 ? `0${number}` : number;}}
}
</script><style scoped lang="less">.content {padding: 15px;color: black;}/**头部属性样式设置*/.header_row1 {display: flex;justify-content: center;font-size: 16px;margin-bottom: 3px;position: relative;}.header_row1 .header_row1_middle {font-weight: 700;position: absolute;}.header_row2 {display: flex;justify-content: center;font-size: 16px;margin-bottom: 15px;position: relative;}.header_row2 .header_row2_middle {font-size: 22px;font-weight: 700;position: absolute;}.header_row1_left,.header_row1_right,.header_row2_left,.header_row2_right {display: inline-block;}.header_row1_v,.header_row2_v {width: 100%;}.header_row1_left,.header_row2_left {float: left;}.header_row1_right,.header_row2_right {float: right;}.header_line {border: 1px solid black;margin-bottom: 5px;}/**中间属性样式设置*//* 将表格的标题行背景设置为白色 */.ant-table-thead {::v-deep & > tr > th {background: #fff;}}/* 将所有边框设置为黑色 */.table_middle {/deep/ .ant-table {color: black;font-size: 16px;}/deep/ .ant-table-bordered .ant-table-thead > tr:first-child > th:first-child {border-left: none;border-top: none;font-weight: 700;text-align: left;}/deep/ .ant-table-bordered .ant-table-thead > tr:not(:last-child) > th {border-bottom: 2px solid black;border-top: 2px solid black;font-weight: normal;text-align: center;}/deep/ .ant-table-bordered .ant-table-thead > tr > th{border-right: 2px solid black;border-bottom: 2px solid black;}/deep/.ant-table-bordered .ant-table-tbody > tr > td {border-right: 2px solid black;border-bottom: 2px solid black;}/deep/ .ant-table-bordered .ant-table-tbody > tr > td:first-child {border-left: 2px solid black;}/deep/ .ant-table-bordered.ant-table-empty .ant-table-placeholder {border: 2px solid black;border-top: 1px solid black;}}/**页脚属性样式设置*/.footer .footer_line {border: 1px solid black;}.footer {font-size: 16px;}.footer_line {margin-top: 75px;margin-bottom: 3px;}.footer_end {display: flex;justify-content: space-between;}
</style>
index.js pdf导出单页方法
export const printPdf = (dom, name = '文件') => {const printEle = domlet width = printEle.scrollWidth;let height = printEle.scrollHeight;html2canvas(printEle, {allowTaint: true, //允许跨域useCORS: true,width: width,height: height,background: '#FFFFFF', //如果指定的div没有设置背景色会默认成黑色scale: 2 // 按比例增加分辨率}).then(canvas => {let contentWidth = canvas.widthlet contentHeight = canvas.height//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高let pdf = new jsPDF('l', 'pt', 'a4')let imgWidth = pdf.internal.pageSize.getWidth()let imgHeight = (imgWidth / contentWidth) * contentHeightlet pageData = canvas.toDataURL('image/jpeg', 1.0)//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)//当内容未超过pdf一页显示的范围,无需分页pdf.addImage(pageData, 'JPEG', 5, 5, imgWidth, imgHeight)pdf.save(`${name}.pdf`)})
}
index.js pdf导出分页方法
import html2canvas from 'html2canvas'
import { jsPDF } from 'jspdf'export const printPdf = (dom, name = '文件') => {let contents = dom.getElementsByClassName("content")return exportToPDF(contents, name)
}//导出pdf
async function exportToPDF(contents, name = 'exportPdf') {// 创建一个空的横向A4大小的PDF文档对象const pdf = new jsPDF('l', 'pt', 'a4');let yPosition = 5; // 当前y坐标位置for (let i = 0; i < contents.length; i++) {const content = contents[i];let width = content.scrollWidth;let height = content.scrollHeight;// 使用html2canvas将content转换为canvasconst canvas = await html2canvas(content, {allowTaint: true, //允许跨域useCORS: true,width: width * 1.02,height: height,background: '#FFFFFF', //如果指定的div没有设置背景色会默认成黑色scale: 2 // 按比例增加分辨率})// 如果是下一个content的内容,则创建新的页码if (i > 0) {pdf.addPage();yPosition = 5;}let pageData = canvas.toDataURL('image/jpeg', 1.0)//宽度使用pdf的宽度let imgWidth = pdf.internal.pageSize.getWidth()//高度根据宽度的比列计算let imgHeight = (imgWidth / canvas.width) * canvas.height// 将canvas添加到PDF中pdf.addImage(pageData, 'JPEG', 5, yPosition, imgWidth, imgHeight)yPosition += pdf.internal.pageSize.getHeight();}// 输出PDF文件pdf.save(`${name}.pdf`)return new Promise((resolve, reject) => {resolve()})
}