前端打印(三联纸票据打印)

需要满足批量打印,每个单据由三部分组成基础信息+表格数据+落款。当表格数据过多出现

分页时,需要实现每页都包含基础信息和落款,只需要拆分表格数据。

一、核心思路

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>

三、效果图

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

相关推荐
董世昌412 小时前
深入浅出 JavaScript 常用事件:从原理到实战的全维度解析
前端
满栀5852 小时前
分页插件制作
开发语言·前端·javascript·jquery
qq_406176142 小时前
深入剖析JavaScript原型与原型链:从底层机制到实战应用
开发语言·前端·javascript·原型模式
开开心心_Every3 小时前
免费窗口置顶小工具:支持多窗口置顶操作
服务器·前端·学习·macos·edge·powerpoint·phpstorm
闲蛋小超人笑嘻嘻4 小时前
Vue 插槽:从基础到进阶
前端·javascript·vue.js
梦6504 小时前
Vue2 与 Vue3 对比 + 核心差异
前端·vue.js
tiandyoin4 小时前
给 MHTML 添加滚动条.mhtml
前端·chrome·html·mhtml
遗憾随她而去.4 小时前
前端大文件上传(切片并发/断点续传/秒传/WebWorker 计算Hash) 含完整代码
前端
风叶悠然4 小时前
vue3中pinia的数据持久化
vue.js