前言
之前写过一篇关于打印的文章:前端使用print.js实现打印,但是呢其实一直有一个关于表格的打印问题没有解决,最近才有了解决思路,因此打算整理一下。
问题
问题是这样的,很多时候都会有这样的需求:打印时页面上面是固定的表头之类的内容,下面是一个表格。由于表格的数据可能会很多,这就涉及一个分页问题,如何保证表格的最后一条数据不会分割到两张纸上。
这个问题已经困扰很久了,现在采用的一种方式是:
- 测试一下每页显示多少条数据比较合适,以10条数据为例,超过10条数据就进行分页
- 对于可能会影响行高的字段,比如备注。通过判断字符串的长度,当长度大于多少值后,就通过设置样式来调整字的大小
但是上面这种方法是治标不治本。
解决
关于什么时候分页的实际问题是:页面剩余的高度知道,但是表格每一行的高度不知道,无法确定多少条数据的时候分页;要想知道每一行的高度,就必须要等dom渲染出来,这就产生了一种矛盾。最近同事的一次分享,才发现自己钻牛角尖了。
我完全可以将表格的数据完全渲染出来,但是我不显示不就可以了吗。由于文档流是按照从上往下的顺序进行渲染的,我可以等表格全部高度都拿到后再进行渲染真正显示的区域。数据也不可能特别多,应该在100条以内,在获取高度的这段时间可以显示一个loading加载,这样也不会显得很奇怪。
实现代码
<template>
<div>
<!-- 这个表格是用来获取高度的,不显示。不能设置display: none;,否则会获取不到高度 -->
<div class="no-show">
<table >
<tr>
<th>序号</th>
<th>内容</th>
<th>来源</th>
</tr>
<tr v-for="(item, index) in data" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.content }}</td>
<td>{{ item.source }}</td>
</tr>
</table>
</div>
<!-- 真正的打印区域 -->
<div id="printDom">
<div class="print-content" v-for="(item,index) in list" :key="index">
<div>
{{`当前页${index+1},总页数${list.length}`}}
</div>
<table>
<tr>
<th>序号</th>
<th>内容</th>
<th>来源</th>
</tr>
<tr v-for="(tr, i) in item" :key="tr.id">
<td>{{ i + 1 }}</td>
<td>{{ tr.content }}</td>
<td>{{ tr.source }}</td>
</tr>
</table>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
const loading = ref(false);
// 用于记录行高
let lineHeight = [];
// 表格数据
const data = [
{
id: '1',
content: '也许我的选择最终会被证明是错误。但至少现在,我想这样选,那么我便这样选。',
source: '将夜'
},
{
id: '2',
content: '鱼跃此时海,花开彼岸天。',
source: '将夜'
},
{
id: '3',
content: '走得慢并不要紧,只要你坚持不停地走,那么总有一天你能走到你想要到达的地方,能超过道旁那些不敢走的人。',
source: '将夜'
},
{
id: '4',
content: '君子不行陌路,管它咫尺还是天涯。',
source: '将夜'
}
];
// 记录分页的数据
const list = ref([]);
const init = () => {
loading.value = true;
lineHeight = [];
// 获取高度
let trList = document.querySelectorAll('.no-show table tr');
console.log(trList);
trList.forEach((tr, index) => {
if (index > 0) {
lineHeight.push(parseInt(tr?.offsetHeight || 0));
}
});
setTimeout(() => {
console.log('高度', lineHeight);
// 处理数据,假设每张纸可以打印的表格区域高度为220
// 这里不考虑特别离谱的情况,一行的高度直接大于一张纸的高度
// 记录起始位置
let start = 0;
// 记录高度和
let sum = 0;
lineHeight.forEach((item, index) => {
sum += item;
if (index + 1 < lineHeight.length && sum + lineHeight[index + 1] > 220) {
list.value.push(data.slice(start, index + 1));
// 更新起始位置和高度和
start = index + 1;
sum = 0;
}
});
if (list.value.length != data.length) {
// 如果最后长度不相等,说明最后剩余的数据高度和不够220
list.value.push(data.slice(start));
}
console.log('最后的数据', list.value);
}, 500);
};
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>
.no-show {
height: 1px; overflow: hidden;opacity: 0;
}
.print-content{
border: 1px solid #000;
}
table {
border-collapse: collapse;
margin: 0 auto;
width: 300px;
}
table tr {
background: #EFF3F5;
}
table td,
table th {
/**文字垂直、水平居中 */
vertical-align: middle;
text-align: center;
/** 基础样式 */
border: 1px solid black;
color: #666;
height: 40px;
font-size: 18px;
min-width: 150px;
line-height: 30px;
}
table thead th {
background-color: #CCE8EB;
width: 100px;
}
</style>
效果