详解window.print(),实现长列表打印分页

相信大家平时做项目时,打印需求很常见,但想把打印做好,还是要花点时间的。特别是长列表要分页的情况。

我们知道浏览原生 API ```window.print()``` 可以用于印当前窗口(window.document)视图内容。调用此方法会产生一个打印预览弹框,用户可以根据具体设置来得到打印结果。

一、window的打印事件

默认情况下,调用 window.print() 会对整个 document.body 进行打印,当需要打印指定容器内容时,可以这样做:

1、先获取指定容器中的内容,将body的内容替换掉,调用了打印方法后,再把原来的body恢复。

html 复制代码
<body>
     <div id="container1">
        <p>这是第一个段落</p>
     </div>
     <div id="container2">
        <p>这是第二个段落</p>
     </div>
  
  <input type="button" value="打印此页面" onclick="printPage()" />

  <script>
    const printPage= () => {
      let newstr = document.getElementById("container1").innerHTML;
      let oldstr = document.body.innerHTML;
      document.body.innerHTML = newstr;
      window.print();
      document.body.innerHTML = oldstr;
  </script>
</body>

2、监听打印前、后事件实现区域打印

window.onbeforeprint()、window.onafterprint()

html 复制代码
<body>
  <div id="container">
    <p>这是一个段落</p>
    <h1 id="title">这是一个标题</h1>
  </div>
  
  <input type="button" value="打印此页面" onclick="printPage()" />

  <script>
    const printPage= () => {
      window.print();
    }
    
    // 打印前事件
    window.onbeforeprint = function() {
      // 隐藏不需要打印的元素
      document.getElementById('title').style.display = 'none';
    }

    // 打印完成后
    window.onafterprint = function() {
      // 放开隐藏的元素
      document.getElementById('title').style.display = 'block';
    }
  </script>
</body>

二、打印样式

我们页面的样式和打印页面时的样式是两个不同的样式,打印时会默认携带页面的样式,同时呢我们也可以修改页面打印时的样式。修改打印样式的方法:

1、使用内联media属性

html 复制代码
<style media="print">
  .container{
    width:800px;
  }
</style>

2、使用媒体查询

html 复制代码
<style>
    @media print {
      h1{
        color: #333;
        background: #ccc;
      }
    }
</style>

3、引入打印样式表

例如:print.css

css 复制代码
@media print {
    @page {
        size: auto;
        margin: 20px 30px;
    }
    #mainBody{
        margin-top:0 !important;
        margin-bottom:0 !important;
    }
}

用link引入

html 复制代码
<link rel="stylesheet" type="text/css" href="./css/print.css" media="print" />

注意:

1、如果是前后不分离的项目,在样式中用到`@`时可能会报错:"上下文中未定义@media或@page",这时候我们可以用`<link>`的方式引入。

2、修改打印样式时必须确保打印机样式实际上确实覆盖了主样式表。可以使用!important。

3、@page属性可以控制打印页面的边距大小和页眉页脚

css 复制代码
@media print {
    @page {
        size: auto; // {size:A4}、{size: 800px 1200px}、{size:portrait}竖向打印、{size:landscape}横向打印

        margin: 20px 30px; // 边距,可去除页眉页脚
    }

    // 覆盖页面原有样式
    #container{
        margin-top:0 !important;
        margin-bottom:0 !important;
    }
}

4、-webkit-print-color-adjust:是一个在浏览器中强制打印背景颜色和字体颜色的css属性,当打印出来的某些元素的背景颜色没有被显示时,可以使用-webkit-print-color-adjust:exact

5、当需要自定义打印分页时机时,可通过如下方式将指定 DOM 设为分割点。

css 复制代码
@media print {
  h1 {
    page-break-before: always; //在指定元素前面添加分页符
  }

  #title {
    page-break-after: always;//在指定元素后面添加分页符
  }
}

了解更多:page-break-after - CSS:层叠样式表 | MDN

三、长列表打印

打印长列表时会要求自动分页,但添加了分页符效果可能并不理想。最常见的就是表格行被从中间截断,那要怎么解决呢?

其实我们只要控制打印的行数就可以了。我们需要知道打印元素的高度和表格行的高度,算出一页纸可以打印多少行,超出的部分放到下一页打印。(一页放多少行没必要计算,根据打印元素的高度估算下就可以了。)

示例:

html 复制代码
<style>
        body {
            font-family: "微软雅黑",Verdana,SimHei,"Microsoft JhengHei",Tahoma;
            line-height: 1.5;
            background-color: #ffffff;
            margin: 0;
        }

        
        // 打印容器
        #mainBody {
            margin: 20px 20px 0 20px;
        }

        table {
            border: 1px solid #000;
            border-collapse: collapse;
            width: 100%;
        }

            table td {
                border: 1px solid #000;
                height: 30px;
                text-align: center;
            }

            table th {
                border: 1px solid #000;
                height: 30px;
                text-align: center;
            }

        

        .printContainer {
            margin: 0 auto;
            // 打印内容宽高
            width: 1052px;
            height: 1480px;
            position: relative;
        }

        .printTitle {
            font-size: 22px;
            font-weight: bold;
            text-align: center;
        }

        .printnav {
            display: flex;
            justify-content: space-between;
        }

        .signature_footer {
            position: absolute;
            width: 100%;
            bottom: -20px;
            display: flex;
            justify-content: space-around;
            font-size: 20px;
        }
</style>
<body>
    <div id="mainBody"></div>
</body>

	<script>
        $(document).ready(function () {
            getList()
        })

        //获取地址栏参数
        function getUrlParam(name) {
            var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
            var r = window.location.search.substr(1).match(reg);  //匹配目标参数
            if (r != null) return unescape(r[2]); return null; //返回参数值
        }

       // 将列表数据分页,array要分页的数据,subNum是每页多少条(根据纸张大小估算一页能展示多少条)
        function groupArray(array, subNum) {
            let index = 0
            let newArray = []
            while (index < array.length) {
                newArray.push(array.slice(index, (index += subNum)))
            }
            return newArray
        }

        // 获取数据源,页面添加元素
        function getList() {
            var params = {
                PatientID: getUrlParam('PatientID'),
                BedNo: getUrlParam('BedNo'),
                xdStartDate: getUrlParam('StartDate'),
                xdEndDate: getUrlParam('EndDate')
            }

            $.get('/IndexPrintList', params, function (res) {

                // 先清空上一次的数据
                $('#mainBody').empty()

                // 分页的数据,返回的数组长度即页数
                var arr = groupArray(res.alist, 14)

                if (arr.length) {
                    for (let i = 0; i < arr.length; i++) {
                        // 创建打印元素
                        $('#mainBody').append(`<div class="printContainer">

                            <div class="printTitle">${res.hospitalName}</div>
                            <div style="text-align:center;font-weight:bold;font-size:20px;margin:20px 0;">定点血糖记录表</div>
                            <div class="printnav" style="margin: 10px 0; font-size:14px;">
                                <div>
                                    患者ID: ${res.patientModel.no}
                                </div>
                                <div>
                                    姓名:${res.patientModel.name}
                                </div>
                                <div>
                                    性别: ${res.patientModel.sex == 0 ? "男" : "女"}
                                </div>
                                <div>
                                    病区:${res.patientModel.Bed.WardModel.wardName}
                                </div>
                                <div>
                                    床号:${res.patientModel.Bed.innerOrder}
                                </div>
                                <div>
                                    测量次数:${res.allNum}
                                </div>
                             </div>
                             <div class="printContent">
                                 <table>
                                     <thead>
                                          <tr>
                                             <th colspan="2">日期\监测点</th>
                                             ${res.SortList.map(column => {
                                                 return `
                                                     <th>${column.name}</th>
                                                 `
                                             }).join("")}
                                          </tr>
                                      </thead>

                                      <tbody>
                                          ${arr[i].map(row => {
                                             return `
                                                 <tr>
                                                     <td rowspan="3" style="width:90px">${row.measureTime}</td>
                                                     <td>血糖值(mmol/L)</td>
                                                     ${res.MeasurePointList.map(point => {
                                                         if (row.dict[point.id]) {
                                                             return `
                                                                 <td style="color:${row.dict[point.id].color}"> 		    																		 ${row.dict[point.id].Value}
                                                             	 </td>`
                                                         } else {
                                                             return `<td></td>`
                                                         }
                                                     }).join("")}
                                                 </tr>
                                                 <tr>
                                                     <td>操作者</td>
                                                     ${res.MeasurePointList.map(point => {
                                                         if (row.dict[point.id]) {
                                                             return `<td>${row.dict[point.id].AccountName}</td>`
                                                         } else {
                                                             return `<td></td>`
                                                         }

                                                     }).join("")}
                                                 </tr>
                                                 <tr>
                                                     <td>测量时间</td>
                                                     ${res.MeasurePointList.map(point => {
                                                         if (row.dict[point.id]) {
                                                             return `<td>08:00</td>`
                                                         } else {
                                                             return `<td></td>`
                                                         }

                                                     }).join("")}
                                                 </tr>
                                                 `
                                             }).join("")
                                          }
                                     </tbody>
                                 </table>
                             </div>
                             <div class="signature_footer">
                                 <div>医生手签:</div>
                                 <div style="margin-right:30px;">质控护士手签:</div>
                             </div>

                        </div>`)
                    }
                }

                window.print();
            })
        }
    </script>

代码解释:

1、首先给打印元素设置宽和高,高度根据需要或实际情况设置。(可根据A4纸大小来定)

2、通过接口拿到长列表数据,然后用`groupArray`方法计算需要打印几页。(groupArray函数返回一个二维数据,二维数组的长度就是页数,二维数组的每一项就是每页的数据)

3、根据这个二维数组循环创建要打印的元素。(类`.printContainer`是要打印的整体内容,即一页纸的内容,arr[i]就是每页的表格数据)

4、示例中每页都加了标题和签名,表格有合并单元格。

5、简单表格如下:

html 复制代码
<table>
    <thead>
        <tr>
            <th>检测日期</th>
            <th>检测值</th>
            <th>操作护士</th>
         </tr>
    </thead>
                                            
    <tbody>
        ${arr[i].map(item=>{
            return `<tr>
                        <td>${item.MeasureDateStr}</td>
                        <td>${item.value}</td>
                        <td>${item.AccountName}</td>
                    </tr>`
            }).join("")
        }
     </tbody>
</table>
相关推荐
excel9 分钟前
webpack 核心编译器 十四 节
前端
excel16 分钟前
webpack 核心编译器 十三 节
前端
腾讯TNTWeb前端团队7 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰11 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪11 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪11 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy12 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom12 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom12 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom12 小时前
React与Next.js:基础知识及应用场景
前端·面试·github