1.需求介绍
- 本项目为vue项目,审批表页面需要添加打印功能,打印区域由3部分组成(标题、表格和签名)
2.为什么不使用PrintJS
-
一开始我们使用的是printJS()
-
printDetail() { printJS({ printable:'printJS-form', //打印区域 type:'html', //打印类型html,还可以是json,image等,详细写法见官网 targetStyles: ['*'], //css样式,写成*代表打印样式完全继承你页面的样式 maxWidth:'800px' //打印界面最大宽度,适当调整可以解决打印页面过宽,显示不完整的问题 }) },
-
其中printable:‘printJS-form’ 是打印id为printJS-form的元素的内容
-
<div id="printJS-form"> .... </div>
-
-
打印类型有html、image和json
-
-
当选择html类型时,打印效果会与页面呈现出的效果一致,比如页面中有滚动条,就会把滚动条也打印出来,如果调整页面显示比例,打印内容也会随之变化,适用于要打印的内容中无滚动条的情况。
-
当选择json类型时,可以做到去除滚动条的效果,写法如下(图片来源于网络)
- 这里只需要添加数据源,然后数据源的属性与filed绑定上,就可以显示表格数据了
- header属性可以帮我们在表格上方添加html元素,也就是标题部分能够添加上
- 但是不知道为什么,这个模式只有header,没有footer,所以表格底部的签名加不进去,如果用定位的话,一旦数据太多,超过一页,就会造成元素覆盖(签名只能固定在第一页,所以无奈放弃)
3.使用js原生的打印功能
-
printJS用不了,退一步使用原生的js来实现打印功能
-
这里照着写就行了,不需要改动
-
let html = window.document.body.innerHTML; let iframe = document.createElement('IFRAME'); let doc = null; iframe.setAttribute("id", "print-iframe"); iframe.setAttribute('style','position:absolute;width:0px;height:0px;left:-500px;top:-500px;'); document.body.appendChild(iframe); doc = iframe.contentWindow.document;
-
-
doc对象创建好后,doc.write() 开始画页面,style标签与html文件中的style标签一样,可以在里面添加css样式
-
doc.write("<title> </title>"); doc.write(`<style media="print"> @page { size: auto; margin-top: 30px; margin-left: 20px; margin-right: 20px; margin-bottom: 20px; } /* 这里只展示了设置了表格样式,表格边框的处理需要使用 border-spacing: 0和border-collapse:collapse; 其他样式比较简单,所以省略掉了 */ table { border-left: 1px solid #DFE5EE; border-bottom: 1px solid #DFE5EE; margin-top: 200px; border-spacing: 0;/*去掉单元格间隙*/ } table:first-of-type { margin-top: 0px ; } table thead th{ border-right: 1px solid #DFE5EE; border-top: 1px solid #DFE5EE; text-align:center;font-size:12px; border-collapse:collapse; } table tbody td{ border-right: 1px solid #DFE5EE; border-top: 1px solid #DFE5EE; text-align:center; font-size:12px; border-collapse:collapse; height: 47px; } </style>`);
-
-
添加完样式之后可以添加一些html元素,可以利用这些元素中的class、id等在上面style中添加样式,我们也可以在这些字符串中拼接变量
-
doc.write(`<h1 class="titleText">`+this.titleText +`</h1>`)
-
-
绘制表格
-
先画个表头
-
let table = `` let tableHead = `<table> <thead> <tr> <th width="3%">序号</th> <th width="5%">姓名</th> <th width="6%">岗位</th> ...省略... <th>备注</th> <th width="5%">渠道</th> </tr> </thead> <tbody>`
-
表体中的行要像表头一样画出来,但是需要根据数据源循环拼接
- 问题:如果把所有的数据都放在同一个表格中,那么当数据量较多,需要多页打印时,表格下方会底部的边框就不显示了
-
所以需要拆分成多个表格,一页一个表格,这里单独做了一个方法
-
/* 数据源是一个对象数组,每个数组元素都会映射成一行数据 该方法的作用是将数组中指定下标范围的元素拼接成一个个tr标签,最后拼接出这个表格的所有行标签 包含头不包含尾 */ concatTr(begin,end) { let tableData = '' const self = this let dataSource = 数据源 for (let i = begin; i < end; i++) { tableData += `<tr> <td>`+(i+1)+`</td> <td>` + dataSource[i].name+ `</td> <td>` + dataSource[i].post+ `</td> ...省略... <td>` + dataSource[i].remark+ `</td> <td>` + dataSource[i].channel+ `</td> </tr>` } return tableData },
-
但是每一页的表格条数都是不一样的
- 第一页有表头,跟其他页的条数不一样
- 签名不要单独一页,所以当签名会被单独挤到最后一页时,要从前一页拆出一行,给最后一页
-
拆分并绘制表格(逻辑比较麻烦,应该可以简化)
-
拆分时一定算好大小,因为每个表格行可能因内容物的大小不同导致高度不一样,如果拆分的不对会导致后面的表头部分会出现重影,在具体项目中可以修改firstCount、fullPageCount、splitNextPageCount来调整
-
let tableData = ``; //表格标体部分,由多个tr标签组成 let dataLength = 数据源.length //共有多少条数据 let beginIndex = 0 //从表格数据源取数据,beginIndex表示起始下标 let firstCount = 11 //第一页最多放多少条 let fullPageCount = 14 //没有标题和摘要的页面最多可以放几条数据 let splitNextPageCount = 1 //当签名会被挤到单独一页时,从前一页拆出几行 let endIndex = firstCount //默认第一页会满 //如果第一页没满 if (dataLength <= firstCount - splitNextPageCount) { endIndex = dataLength //不需要给签名腾位置,本来第一页就放得下的情况 } else if (dataLength <= firstCount) { //第一页刚好放得下数据,但是放不下签名,需要拆分表格 endIndex = firstCount - splitNextPageCount } tableData = self.concatTr(0,endIndex) //拆出第一页的表格 beginIndex = endIndex //更新起始下标 //拼接字符串,形成完整的表格 table = tableHead + tableData + `</tbody></table>` doc.write(table) //如果第一页没有放入全部数据 if (dataLength - beginIndex >= 0 ) { //剩余的数据需要几页打印 let page = Math.floor((dataLength - beginIndex)/fullPageCount) let rem = (dataLength - beginIndex) % fullPageCount //余数 //余数为0说明最后一页刚好放得下表格数据但放不下签名,需要另作处理 //dataLength != beginIndex第一页的情况,因为已经处理过了 if (rem == 0 && dataLength != beginIndex ) { page --;//最后一页与其他页不同,所以先减出来 } //满页 for (let i = 0; i < page; i++) { tableData = self.concatTr(beginIndex,(beginIndex + fullPageCount) > dataLength ? dataLength : (beginIndex + fullPageCount)) table = tableHead + tableData + `</tbody></table>` beginIndex += fullPageCount doc.write(table) } //如果最后一页刚好放得下表格数据但放不下签名 if (rem == 0 && dataLength != beginIndex) { //如果第一页因为签名被拆成2页,那肯定一共就2页,不再拼接新表格 if (page >= 0) { tableData = self.concatTr(beginIndex, beginIndex + fullPageCount - splitNextPageCount) table = tableHead + tableData + `</tbody></table>` doc.write(table) beginIndex += fullPageCount - splitNextPageCount } tableData = self.concatTr(beginIndex, dataLength) table = tableHead + tableData + `</tbody></table>` doc.write(table) } else if (rem != 0){ //正常情况不需要拆表格,直接拼接即可 tableData = self.concatTr(beginIndex,dataLength) table = tableHead + tableData + `</tbody></table>` doc.write(table) } }
-
-
-
绘制签名
-
doc.write(`<div class="sign"> <span>申请人: <input type="text" value=""></span> <span>岗级审核: <input type="text" value=""></span> <span>部门经理: <input type="text" value=""></span> </div>`)
-
-
结束(这里也不知道为什么这么写,照着写就行了,不需要改动)
-
doc.close(); iframe.contentWindow.focus(); iframe.contentWindow.print(); if (navigator.userAgent.indexOf("MSIE") > 0){ document.body.removeChild(iframe); }
-
-
效果图