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>&nbsp;</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);
      }
      
  • 效果图

在这里插入图片描述

Logo

前往低代码交流专区

更多推荐