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;实现分页;最后通过绝对定位使容器撑满整个纸张,以达到切换纸张大小时也能撑满整个容器的效果。

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

相关推荐
nvd114 分钟前
企业级 LLM 实战:在受限环境中基于 Copilot API 构建 ReAct MCP Agent
前端·copilot
+VX:Fegn089510 分钟前
计算机毕业设计|基于springboot + vue校园实验室管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
Dragon Wu13 分钟前
TailWindCss cva+cn管理样式
前端·css
烤麻辣烫18 分钟前
Web开发概述
前端·javascript·css·vue.js·html
Front思28 分钟前
Vue3仿美团实现骑手路线规划
开发语言·前端·javascript
徐同保31 分钟前
Nano Banana AI 绘画创作前端代码(使用claude code编写)
前端
Ulyanov31 分钟前
PyVista与Tkinter桌面级3D可视化应用实战
开发语言·前端·python·3d·信息可视化·tkinter·gui开发
计算机程序设计小李同学31 分钟前
基于Web和Android的漫画阅读平台
java·前端·vue.js·spring boot·后端·uniapp
干前端33 分钟前
Message组件和Vue3 进阶:手动挂载组件与 Diff 算法深度解析
javascript·vue.js·算法
lkbhua莱克瓦2434 分钟前
HTML与CSS核心概念详解
前端·笔记·html·javaweb