需要满足批量打印,每个单据由三部分组成基础信息+表格数据+落款。当表格数据过多出现
分页时,需要实现每页都包含基础信息和落款,只需要拆分表格数据。
一、核心思路
1、结构拆分:将单条单据拆分为基础信息、表格容器、页脚(落款)三个独立模块;
2、打印样式:
1)页眉/页脚用position: fixed固定每一个的顶部/底部;
2)表格容器预留也没/页脚空间,避免内容重叠;
3)表格行用page-break-inside: avoid 防止行被截断,表格超出时自动分页;
3、适配三联纸:保留纸张尺寸,优化边距和字体适配针式打印
二、代码实现
javascript
<template>
<div class="fa-wrap">
<div class="or-box" v-for="(pageItem, pageIdx) in finalOrderPages" :key="pageItem.uniqueKey">
<!-- 基础数据(页眉)- 每页都显示 -->
<div class="print-header">
<div class="m-title">{{ pageItem.companyName }}</div>
<div class="pur-title">项目发货单</div>
<div class="in-row">
<div class="in-item in-item-first">
<span class="in-lable">项目名称:</span>
<span class="in-val">{{ pageItem.projectName }}</span>
</div>
<div class="in-item">
<span class="in-lable">发货日期:</span>
<span class="in-val">{{ pageItem.documentDate }}</span>
</div>
<div class="in-item">
<span class="in-lable">页码:</span>
<span class="in-val">{{ `${pageItem.pageNum}/${pageItem.totalPages}` }}</span>
</div>
</div>
<div class="in-row">
<div class="in-item in-item-first">
<span class="in-lable">项目编号:</span>
<span class="in-val">{{ pageItem.projectCode }}</span>
</div>
<div class="in-item">
<span class="in-lable">销售单号:</span>
<span class="in-val">{{ pageItem.sourceCode }}</span>
</div>
<div class="in-item">
<span class="in-lable">编号:</span>
<span class="in-val">{{ pageItem.documentCode }}</span>
</div>
</div>
<div class="in-row">
<div class="in-item">备注:{{ pageItem.remark }}</div>
</div>
</div>
<!-- 表格部分 - 每页显示对应拆分的数据 -->
<div class="print-table-container">
<table class="table-block">
<thead>
<tr>
<th width="5%">序号</th>
<th width="12%">产品代码</th>
<th width="20%">产品名称</th>
<th width="15%">规格型号</th>
<th width="12%">发货仓库</th>
<th width="8%">单位</th>
<th width="8%">应发数量</th>
<th width="8%">实发数量</th>
<th width="12%">备注</th>
</tr>
</thead>
<tbody>
<!-- 当前页的表格数据 -->
<tr v-for="(sub, idx) in pageItem.pagePartList" :key="sub.id + '_' + pageIdx">
<td>{{ pageItem.startSerial + idx }}</td> <!-- 累计序号 -->
<td>{{ sub.partCode }}</td>
<td class="text-l">{{ sub.partName }}</td>
<td>{{ sub.partSpec || " " }}</td>
<td>{{ sub.warehouse || " " }}</td>
<td>{{ sub.unit || " " }}</td>
<td>{{ sub.pendingNum }}</td>
<td>{{ " " }}</td>
<td>{{ sub.remark || " " }}</td>
</tr>
<!-- 合计行:仅最后一页显示 -->
<tr v-if="pageItem.isLastPage">
<td colspan="6" style="text-align: left">合计:</td>
<td>{{ fieldSummary(pageItem.originalPartList, "pendingNum") }}</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
<!-- 落款(页脚)- 每页都显示 -->
<div class="print-footer">
<div class="in-row">
<div class="in-item in-item-first">
<span class="in-lable">送货地址:</span>
<div class="in-val">{{ pageItem.address }}</div>
</div>
<div class="in-item">
<span class="in-lable">联系人:</span>
<div class="in-val">{{ pageItem.contactPerson }}</div>
</div>
<div class="in-item">
<span class="in-lable">联系电话:</span>
<div class="in-val">{{ pageItem.contactPhone }}</div>
</div>
</div>
<div class="in-row in-row-spe">
<div class="in-item">
<span class="in-lable">制单人:</span>
<div class="in-val">{{ pageItem.createBy }}</div>
</div>
<div class="in-item">
<span class="in-lable">审核人:</span>
<div class="in-val">{{ pageItem.auditName }}</div>
</div>
<div class="in-item">
<span class="in-lable">发料人:</span>
<div class="in-val"></div>
</div>
<div class="in-item">
<span class="in-lable">签收人:</span>
<div class="in-val"></div>
</div>
</div>
<div class="last-row">
<span>白联:存根</span>
<span>红联:财务</span>
<span>黄联:接收单位</span>
</div>
</div>
</div>
</div>
</template>
<script>
import { formatNumber } from "@/lib/tools.js";
import { fieldSummary } from "./templatePrint.js";
export default {
name: "BillPrint",
props: {
printData: {
type: Object,
default: () => ({}),
},
},
data() {
return {
finalOrderPages: [], // 最终拆分后的所有单据页(每页都是完整单据)
pageSize: 6, // 每页表格最大行数(根据三联纸高度调整)
};
},
created() {
this.initAndSplitData();
},
methods: {
formatNumber,
fieldSummary,
initAndSplitData() {
const originalList = this.printData.data || [];
this.finalOrderPages = [];
// 遍历每一个原始单据
originalList.forEach((originalItem) => {
const partList = originalItem.partList || [];
const totalRows = partList.length;
const totalPages = Math.ceil(totalRows / this.pageSize); // 计算总页数
// 为每一页生成独立的完整单据
for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
// 计算当前页的表格数据范围
const startIdx = (pageNum - 1) * this.pageSize;
const endIdx = Math.min(pageNum * this.pageSize, totalRows);
const pagePartList = partList.slice(startIdx, endIdx); // 当前页表格数据
// 构造当前页的完整单据(复制基础数据+当前页表格+标记分页信息)
const pageItem = {
...originalItem, // 复制所有基础数据(公司名、项目名、地址等)
uniqueKey: `${originalItem.id || Date.now()}_page_${pageNum}`, // 唯一Key
pageNum: pageNum, // 当前页码
totalPages: totalPages, // 总页数
pagePartList: pagePartList, // 当前页表格数据
originalPartList: partList, // 原始完整表格数据(用于合计)
isLastPage: pageNum === totalPages, // 是否最后一页(控制合计行显示)
startSerial: startIdx + 1, // 当前页起始序号(保证累计)
};
// 加入最终分页列表
this.finalOrderPages.push(pageItem);
}
});
},
},
};
</script>
<style lang="scss" scoped>
.fa-wrap {
width: 100%;
font-family: "宋体", SimSun, sans-serif;
box-sizing: border-box;
padding: 5px 0px;
.or-box {
margin-bottom: 30px;
}
.print-header {
margin-bottom: 15px;
}
.print-table-container {
margin: 15px 0;
}
.print-footer {
margin-top: 15px;
}
.m-title {
width: 100% !important;
font-size: 30px;
font-weight: bold;
line-height: 40px;
text-align: center !important;
}
.pur-title {
width: 100% !important;
font-size: 26px;
margin: 15px auto;
text-align: center;
font-weight: bold;
}
.in-row {
display: flex;
flex-wrap: nowrap;
margin-bottom: 8px;
.in-item {
display: flex;
align-items: flex-start;
margin-right: 40px;
margin-bottom: 10px;
min-width: 250px;
font-size: 22px;
font-weight: bold;
.in-lable {
white-space: nowrap;
margin-right: 5px;
}
.in-val {
word-break: break-all;
flex: 1;
white-space: nowrap;
}
}
.in-item-first {
min-width: 400px;
.in-val {
white-space: pre-wrap;
}
}
}
.in-row-spe {
width: 100% !important;
border-bottom: 1px solid #000;
padding-bottom: 5px;
margin-bottom: 10px;
.in-item {
margin-bottom: 5px;
}
}
.last-row {
width: 100% !important;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
font-weight: bold;
span {
margin: 0 10px;
}
}
}
// 表格样式
.table-block {
width: 100%;
border-collapse: collapse;
border: 1px solid #000 !important;
td,
th {
border: 1px solid #000 !important;
padding: 8px 5px !important;
vertical-align: middle;
box-sizing: border-box;
text-align: center;
font-size: 20px !important;
font-weight: bold !important;
}
th {
padding: 12px 5px !important;
background-color: #f5f5f5;
}
.text-l {
text-align: left !important;
padding-left: 8px !important;
}
td:not([colspan]):empty {
border: none !important;
}
}
// 打印专属样式(核心:强制每页独立打印)
@media print {
// 三联纸尺寸配置
@page {
size: 241mm 140mm; // 三联纸标准尺寸
margin: 5mm 3mm; // 窄边距适配针式打印
// 隐藏浏览器默认页眉页脚
@top-center { content: ""; }
@bottom-center { content: ""; }
}
// 每个单据页强制分页,且内部不拆分
.or-box {
page-break-after: always; // 每页单据后强制分页
page-break-inside: avoid; // 避免单据内容被拆分到两页
margin: 0;
padding: 0;
border: none;
}
// 最后一页取消强制分页(避免空白页)
.or-box:last-child {
page-break-after: avoid;
}
// 打印时的页眉/表格/页脚样式
.print-header, .print-footer {
background: #fff !important;
width: 100%;
}
// 表格表头每页重复显示
.table-block thead {
display: table-header-group !important;
}
// 隐藏非打印元素
button, .no-print {
display: none !important;
}
}
</style>
三、效果图


这世界很喧嚣,做你自己就好
