前言
大家好,最近在做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
时的问题,我们给id
为print-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
。这样,我们获取id
为print-area
的dom
元素后,当前元素上没有设置隐藏样式,所以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;
实现分页;最后通过绝对定位使容器撑满整个纸张,以达到切换纸张大小时也能撑满整个容器的效果。
如果大家有更好的方案,欢迎评论区留下你宝贵的意见,谢谢大家!