安装插件
npm install vue3-print-nb --save
vue3 引入
import print from 'vue3-print-nb' // 打印插件app.use(print)
使用
这里使用的是对象配置方式
对象配置方式——在js中定义一个对象,对象中可配置打印区域相关属性,在需要打印的单据内容最外面的div设置唯一的id,id值为js对象中的id值,在打印弹框里的打印按钮设置自定义属性v-print,该属性值为打印区域的对象
<a-button type="primary" @click="printing" v-print="printObj">打印</a-button>const printObj = { id: "mypdf", // 这里是要打印元素的ID popTitle: " ", // 打印的标题 extraCss: "", // 打印可引入外部的一个 css 文件 extraHead: "", // 打印头部文字 preview: false, // 是否启动预览模式,默认是false previewTitle: '中铜国际贸易集团有限公司', // 打印预览的标题 previewPrintBtnLabel: '预览结束,开始打印', // 打印预览的标题下方的按钮文本,点击可进入打印 zIndex: 10002, // 预览窗口的z-index,默认是20002,最好比默认值更高 previewBeforeOpenCallback() { console.log('正在加载预览窗口!') }, previewOpenCallback() { console.log('已经加载完预览窗口,预览打开了!') }, // 预览窗口打开时的callback beforeOpenCallback() { console.log('开始打印之前!') }, // 开始打印之前的callback openCallback() { console.log('执行打印了!') }, // 调用打印时的callback closeCallback() { console.log('关闭了打印工具!') }, // 关闭打印的callback(无法区分确认or取消) clickMounted() { console.log('点击v-print绑定的按钮了') },}
分页分为俩种情况
第一种:打印单据有2个以上,打印时需要自动分页打印,且每一页内容不超出一页(固定的数据)
单据内容的最外层的div设置样式page-break-before:always,即可在打印时自动分页
@media print { .section { page-break-before: always; /* 在每个部分之前始终开始新页面 */ // margin: 20px 0; /* 为了使打印页面更清晰,可以添加一些上下间距 */ margin-top: 0; }}
第二种:打印单据有2个以上,打印时需要自动分页打印,内容连续且不固定,
如图所示打印 一行被截断 不满足需求
自动分页 : 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度,
当内容未超过pdf一页显示的范围,无需分页
通过class="paging"的容器进行分割,考虑到每张A4纸高度固定,所以通过判断每个class为paging的容器高度累加,大于纸张高度时,就给上一个class为paging的容器加上style=“page-break-pageBreakBefore:always”
// 动态计算 分页const PAGE_HEIGHT = 1100 // A4纸高度const printing = () => { const splitDoms = document.getElementsByClassName('paging') console.log(splitDoms) let startY = 0 // 占用A4纸的高度,从每页第一个poetry div的top值开始累加 for (let i = 0; i < splitDoms.length; i++) { const splitDom = splitDoms[i] const splitValue = splitDom.getBoundingClientRect() console.log(splitDom.getBoundingClientRect()) if (startY === 0) { startY = splitValue.top } const pageHeight = splitValue.bottom - startY // 当加上当前div的高度大于A4纸高度时,给前一个div加上分页标识 if (pageHeight > PAGE_HEIGHT) { console.log(i) startY = 0 if (i > 0) { splitDoms[i - 1].style.pageBreakBefore = 'always'; // 给前一个元素添加分页符 } } }}
动态计算后的分页展示
打印功能完整代码(安装好包,导入包后,可直接复制粘贴使用)
<template> <div> <a-button type="primary" style="margin-right: 10px;" @click="printing" v-print="printObj">打印</a-button> <!-- <a-button type="primary" style="margin-right: 10px;" @click="downPdf">下载PDF</a-button> --> </div> <div style="overflow-y: auto;padding: 20px;"> <div class="main" id="mypdf"> <div class="title fd">客商评价明细表</div> <div class="fd"> 供应商名称: <span>xxxxxxxxxxxxxxxxxxx</span> 编号:xxxxxxxxxxxxxxxxx </div> <table border="1" class="techniques" style="margin-top: 1px;width: 100%;"> <tr> <td style="width: 130px;text-align: center;">拟开展业务</td> <td colspan="3">xxxxxxxxxxxxxxxxxxxxxx</td> <td class="tec" :colspan="5">评价明细情况</td> </tr> <tr> <!-- <td style="width: 130px;text-align: center;">因素</td> --> <td style="width: 130px;text-align: center;">主要指标</td> <td style="width: 100px;text-align: center;">指标类型</td> <td style="width: 150px;">评分标准</td> <td style="width: 100px" class="tec">标准分数</td> <td v-for="item in 5" class="tec">姓名{{ item }}</td> </tr> <tbody v-for="item in baseList" class="paging" style="height: 1px;"> <tr> <!-- <td style="width: 130px;text-align: center;">{{ item.factor }}</td> --> <td style="width: 130px;text-align: center;">{{ item.factor }}</td> <td style="width: 100px;text-align: center;">否决项</td> <td>{{ item.scoringcriteria }}</td> <td style="width: 100px;text-align: center;"> <div style="display: flex;"> <div style="display: flex; align-items: center;"> <div class="tag"> <!-- <check-outlined class="tagIcon" /> --> </div> <div>是</div> </div> <div style="display: flex; align-items: center;"> <div class="tag"> <!-- <check-outlined class="tagIcon" /> --> </div> <div>否</div> </div> </div> </td> <td v-for="item in item.peo" class="tec">{{ item }}</td> </tr> </tbody> <tbody v-for="item in orditemLsit" :key="item.id" class="paging"> <tr v-for="(val, index) in item.scoringcriteria" :key="val.id"> <td style="width: 130px;text-align: center;" v-if="index === 0" :rowspan="item.scoringcriteria.length"> {{ item.factor }} </td> <td style="width: 100px;text-align: center;" v-if="index === 0" :rowspan="item.scoringcriteria.length"> 5 </td> <td style="width: 130px;">{{ val.title }}</td> <td style="width: 100px;" class="tec">{{ val.score }}</td> <template v-for="items in 5"> <td class="tec" v-if="index === 0" :rowspan="item.scoringcriteria.length">{{ items }}</td> </template> </tr> </tbody> </table> </div> </div></template><script setup>let baseList = [ { factor: '公司及董监高失信', mainindex: '公司及董监高是否被列为失信执行人员', score: '否决项', scoringcriteria: '公司及董监高被列为失信执行人员', standardScore: '', peo: 5 }, { factor: '成立年限', mainindex: '成立年限', score: '否决项', scoringcriteria: '公司成立年限小于3年', standardScore: '', peo: 5 }, { factor: '信用评价', mainindex: '第三方征信系统的评级', score: '否决项', scoringcriteria: '偿债能力预警(对应征信报告的第4级),如无第三方征信报告,则可从客商资产质量、资产负债率、营运管理、竞争力情况等方面进行综合评价,如资产质量较差,偿债能力较差,或存在大量诉讼案件或最近2年有重大行政处罚记录,需警惕', standardScore: '', peo: 5 }, { factor: '净资产', mainindex: '最新年度或半年度净资产', score: '否决项', scoringcriteria: '最新年度或半年度净资产总额小于500万元', standardScore: '', peo: 5 }, { factor: '业务范围', mainindex: '业务范围', score: '否决项', scoringcriteria: '有与公司同类自营贸易业务的民营企业', standardScore: '', peo: 5 }, { factor: '经营性现金净流量', mainindex: '经营性现金净流量', score: '否决项', scoringcriteria: '连续三年经营性现金净流量为负', standardScore: '', peo: 5 }, { factor: '所有者权益与实收资本关系', mainindex: '所有者权益与实收资本关系', score: '否决项', scoringcriteria: '所有者权益总额连续三年小于实收资本金额', standardScore: '', peo: 5 }, { factor: '境内供应商未提供最近三年审计报告', mainindex: '境内供应商未提供最近三年审计报告', score: '否决项', scoringcriteria: '境内供应商未提供最近三年审计报告', standardScore: '', peo: 5 },]let orditemLsit = [ { factor: '成立年限', score: '5', scoringcriteria: [ { title: '成立年限5年(含)以上', score: '5' }, { title: '成立年限3年(含)至5年', score: '3' }, { title: '成立年限3年(不含)以下', score: '1' }, ], standardScore: '', peo: 5 }, { factor: '实缴注册资本金', score: '5', scoringcriteria: [ { title: '8000万元(含) 以上 ', score: '5' }, { title: '4000万元(含) 至8000万元 ', score: '4' }, { title: '1000万元(含) 至4000万元 ', score: '3' }, { title: '1000万元(不含)以下至4000万元 ', score: '1' }, ], standardScore: '', peo: 5 }, { factor: '企业性质', score: '5', scoringcriteria: [ { title: '国有A股上市,央企三级以上公司 以上 ', score: '5' }, { title: '上市公司,省属大型国企', score: '4' }, { title: '国有非上市', score: '3' }, { title: '其它(非国企、非上市)', score: '1' }, ], standardScore: '', peo: 5 }, { factor: '企业性质', score: '5', scoringcriteria: [ { title: '国有A股上市,央企三级以上公司 以上 ', score: '5' }, { title: '上市公司,省属大型国企', score: '4' }, { title: '国有非上市', score: '3' }, { title: '其它(非国企、非上市)', score: '1' }, ], standardScore: '', peo: 5 }, { factor: '企业性质', score: '5', scoringcriteria: [ { title: '国有A股上市,央企三级以上公司 以上 ', score: '5' }, { title: '上市公司,省属大型国企', score: '4' }, { title: '国有非上市', score: '3' }, { title: '其它(非国企、非上市)', score: '1' }, ], standardScore: '', peo: 5 },]const printObj = { id: "mypdf", // 这里是要打印元素的ID popTitle: " ", // 打印的标题 extraCss: "", // 打印可引入外部的一个 css 文件 extraHead: "", // 打印头部文字 preview: false, // 是否启动预览模式,默认是false previewTitle: '中铜国际贸易集团有限公司', // 打印预览的标题 previewPrintBtnLabel: '预览结束,开始打印', // 打印预览的标题下方的按钮文本,点击可进入打印 zIndex: 10002, // 预览窗口的z-index,默认是20002,最好比默认值更高 previewBeforeOpenCallback() { console.log('正在加载预览窗口!') }, previewOpenCallback() { console.log('已经加载完预览窗口,预览打开了!') }, // 预览窗口打开时的callback beforeOpenCallback() { console.log('开始打印之前!') }, // 开始打印之前的callback openCallback() { console.log('执行打印了!') }, // 调用打印时的callback closeCallback() { console.log('关闭了打印工具!') }, // 关闭打印的callback(无法区分确认or取消) clickMounted() { console.log('点击v-print绑定的按钮了') },}const PAGE_HEIGHT = 1100 // A4纸高度const printing = () => { const splitDoms = document.getElementsByClassName('paging') console.log(splitDoms) let startY = 0 // 占用A4纸的高度,从每页第一个poetry div的top值开始累加 for (let i = 0; i < splitDoms.length; i++) { const splitDom = splitDoms[i] const splitValue = splitDom.getBoundingClientRect() console.log(splitDom.getBoundingClientRect()) if (startY === 0) { startY = splitValue.top } const pageHeight = splitValue.bottom - startY // 当加上当前div的高度大于A4纸高度时,给前一个div加上分页标识 if (pageHeight > PAGE_HEIGHT) { console.log(i) startY = 0 if (i > 0) { splitDoms[i - 1].style.pageBreakBefore = 'always'; // 给前一个元素添加分页符 } } }}</script><style lang="scss" scoped>.main { width: 900px; margin: 0 auto;}.title { font-size: 19px; margin-bottom: 10px; line-height: 33px; text-align: center;}.techniques { width: 100%; border-color: #000; font-family: "宋体", "SimSun", sans-serif;}.techniques,.techniques th,.techniques td { border-collapse: collapse; line-height: 22px; font-size: 13px}.fd { font-weight: bold;}.tec { text-align: center;}.tag { width: 14px; height: 14px; border-radius: 2px; text-align: center; color: #fff; border: 1px solid #333; font-weight: 700; // margin-left: 10px; // margin-right: -10px; margin: 0 5px; position: relative; .tagIcon { position: absolute; font-size: 12px; top: 1px; left: -20px; z-index: 111; color: #000; }}@page { size: auto A4 landscape; margin-top: 20mm;}@media print { .section { page-break-before: always; /* 在每个部分之前始终开始新页面 */ // margin: 20px 0; /* 为了使打印页面更清晰,可以添加一些上下间距 */ margin-top: 0; }}</style>