vue打印指定组件内容,多页打印自动适配纸张大小打印方案

前言

大家好,最近在做vue项目时使用到了print-js打印指定组件内容 ,并且还得切换纸张大小 时做到自动适配布局 的需求,其中遇到了一些问题 及我的解题思路想给大家分享一下,希望日后能帮助到大家。

背景

大致需求是在页面中提供一个多选框组,可以从中选择打印哪些内容,每个勾选项打印格式都是一样的,不同的是每个勾选项独占一页,且响应式适配纸张大小,大致效果如下图所示:

了解需求后,我们开始做开发准备工作!

引入print-js,并封装打印函数

cmd 复制代码
npm install print-js@1.3.1

usePrint.js

js 复制代码
import printJS from 'print-js'

export const usePrint = () => {
  printJS({
    // 需要打印区域设置的Id
    printable: 'print-area',
    // 打印类型
    type: 'html',
    // 默认值为800,我们把把设置为100%
    maxWidth: '100%', 
    // *代表应用所有样式,默认值为null,如果不设置,打印窗口则会忽略所有样式
    targetStyles: ['*'],
  });

上面主要注意点是注意点是修改maxWidth的默认值及设置targetStyles应用组件所写的样式。想要了解更多可参考:print-js文档

封装打印选择器及打印内容组件

引入插件后,我们先把打印的组件结构先写好,下面先把打印内容选择器结构写好

PrintSelector.vue

js 复制代码
<template>
  <div>
    <!-- 这里是我们封装的打印组件 -->
    <PrintArea :data="printData" />
    <el-checkbox :value="checkAll" @change="onCheckAllChange">全选</el-checkbox>
    <div style="margin: 15px 0;"></div>
    <el-checkbox-group v-model="checkedItems" @change="onCheckedChange">
      <el-checkbox v-for="item in options" :label="item.id" :key="item.id">{{item.label}}</el-checkbox>
    </el-checkbox-group>
    <div style="margin: 15px 0;"></div>
    <el-button @click="onPrint">打印</el-button>
  </div>
</template>

<script>
import { usePrint } from './hooks/usePrint'
import PrintArea from './PrintArea.vue'
export default {
  components: {
    PrintArea
  },
  data() {
    return {
      checkedItems: [],
      options: [
        { id: 1, label: '项目1' },
        { id: 2, label: '项目2' },
        { id: 3, label: '项目3' },
      ]
    }
  },
  computed: {
    checkAll() {
      return this.checkedItems.length === this.options.length;
    },
    printData() {
      return this.options.filter(item => this.checkedItems.includes(item.id))
    }
  },
  methods: {
    onCheckAllChange() {
      if(this.checkAll) {
        this.checkedItems = []
      } else {
        this.checkedItems = this.options.map(item => item.id)
      }
    },
    onCheckedChange(val) {
      this.checkedItems = val
    },
    onPrint() {
      usePrint()
    }
  },
}
</script>

注意 ,上面我们把需要打印的组件PrintArea引入到了PrintSelector组件中,因为在调用printJS方法时需要通过id获取页面dom元素,所以需要组件的dom元素渲染到页面中来,但是我们又不希望页面中显示打印的内容,这里可以通过设置display: none把元素隐藏起来。但,隐藏后有一个需要注意的点,我们先把PrintArea组件写出来,后面再进行讲解。

PrintArea.vue

js 复制代码
<template>
  <div id="print-area" v-show="false">
    <div class="item" v-for="item in data" :key="item.id">
      <div class="head">头部</div>
      <div class="main">
        <span class="text">{{ item.label }}</span>
      </div>
      <div class="footer">底部</div>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    data: Array
  }
}
</script>

这个组件就是我们需要打印的内容,外部勾选需要打印的内容后传入当前组件即可,每一个item代表着一页。我们回到前文提到的设置display:none时的问题,我们给idprint-area打印内容跟标签隐藏了起来,如果这时选择项目点击打印后,会发现打印预览窗口为空白!!!

其实问题就是出现给打印区域设置了display:none,所以打印预览窗口也把内容隐藏了,解决办法也简单,就是给打印区域再套一层div,把v-show="false"设置在外层div上即可。

js 复制代码
<template>
  <div v-show="false">
    <div id="print-area">
      ...省略其中代码
    </div>
  </div>
</template>

这样子打印窗口就能正常显示内容了。

我们下面通过一个小例子模拟打印预览讲解一下这个问题出现的原因:

demo.html

js 复制代码
<body>
  <div class="wrapper">
    <!-- 打印区域 -->
    <div id="print-area" style="display: none;">
      <div class="item">111</div>
      <div class="item">222</div>
    </div>
  </div>
  <!-- 模拟打印预览窗口 -->
  <div id="print-window"></div>
  <script>
    window.onload = function() {
      const printArea = document.getElementById('print-area')
      const printWindow = document.getElementById('print-window')
      // 把打印内容放入打印预览窗口
      printWindow.appendChild(printArea)
    }
  </script>
</body>

上面我们实现了一个简单的模拟打印流程,在打印区域的div上设置了display: none,所以调用appendChild放入模拟的打印窗口div下时,页面并没有内容显示,因为print-area样式还是设置为隐藏。所以我们通过在外面增加一层用来设置隐藏样式的div。这样,我们获取idprint-areadom元素后,当前元素上没有设置隐藏样式,所以appendChild放到打印预览窗口,就不会隐藏打印的内容了!

页面布局设计

我们在PrintArea.vue文件中先把样式补上,看下效果

js 复制代码
<style lang="scss" scoped>
#print-area {
  background-color: deeppink;
  .item {
    display: flex;
    flex-direction: column;
    background-color: bisque;
    .head {
      text-align: center;
      line-height: 100px;
      font-weight: bold;
      font-size: 36px;
      background-color: azure;
    }
    .main {
      flex: 1;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    .footer {
      text-align: center;
      line-height: 100px;
      font-weight: bold;
      font-size: 20px;
      background-color: #eee;
    }
  }
}
</style>

我们的需求是根据每个item进行分页打印,分页实现可以直接在.item下设置page-break-after: always;样式即可,但是还有一个需求是怎样能够让item响应式适应纸张大小,占满整个区域呢?第一想到的就是height: 100%,但,这样就得保证每一层父组件的高度为页面文档的高度100%。好像不太理想,那设置每一页item的高度为100vh呢?下图可看出也并没有达到想要的效果:

解决办法是让打印区域脱离文档流,设置一个绝对定位

css 复制代码
#print-area {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  .item {
    height: 100%;
  }
}

上面我们巧用 绝对定位的上下左右 距离都为0,使容器能撑满整个纸张大小,最后给每一页item继承100%高度,这样子布局就能够适应各种纸张大小了,即使切换纸张也能够达到撑满整个容器的效果。

总结

以上我们主要是通过在打印内容区域外设置v-show="false"来隐藏元素,使打印内容不受隐藏影响,并通过一个小demo演示了问题出现的原因;接着我们通过在每个item下设置page-break-after: always;实现分页;最后通过绝对定位使容器撑满整个纸张,以达到切换纸张大小时也能撑满整个容器的效果。

如果大家有更好的方案,欢迎评论区留下你宝贵的意见,谢谢大家!

相关推荐
编程零零七3 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
北岛寒沫4 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
everyStudy4 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
(⊙o⊙)~哦5 小时前
JavaScript substring() 方法
前端
无心使然云中漫步5 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者5 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_6 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
罗政6 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
麒麟而非淇淋6 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120536 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab