背景:需求是先用HTML+CSS对打印界面进行排版,然后填入后端数据,实现最终打印界面。实现是通过浏览器的window.print()。不能改变当前界面样式、不能跳转界面,需要直接在当前界面打开打印界面预览弹窗(通过iframe来进行操作)。

总体过程:

    1、创建打印文件并调整好样式(print.vue),为该文件最外层元素绑定唯一ID(print-iframe)

    2、在父组件中导入并使用print.vue文件,并使用display: none;将该组件隐藏不显示

    3、编写打印方法:

      (3-1)将后端数据导入至print.vue中

      (3-2)创建iframe标签(iframe),隐藏iframe标签(防止在打印预览时显示在界面上)

      (3-3)创建link标签,设置link标签属性,设置link标签href

      (3-4)通过document.getElementById(打印组件id).innerHTML获取print.vue组件的元素

      (3-5)将获取的HTML数据写入至iframe的document中

      (3-6)获取iframe的head,将设置后的link标签添加到head里

      (3-7)调用iframe的print()方法: iframe.contentWindow.print()

      (3-8)移除iframe

    // 打印
    handleClickPrint() {
      // 3-1 数据导入
      const item = {
        goodsNm: "冰红茶",
        num: 1,
        notes: "11222",
      }
      const data = []
      for (let i = 1; i < 24; i++) {
        data.push(item)
      }
      this.$refs.printRef.print(data)

      this.$nextTick(() => {
        // 3-2
        const iframe = document.createElement('iframe');
        iframe.style.display = 'none'
        document.body.appendChild(iframe)
        const doc = iframe.contentWindow.document;
        
        // 3-3
        // 新建link标签
        const linkTag = document.createElement('link')
        // 设置lin标签属性
        linkTag.setAttribute('rel', 'stylesheet')
        // 设置style标签内容
        linkTag.href = this.getPrintPageCss()

        // 3-4
        // 获取需要打印区域数据
        let wrap = document.getElementById("print-iframe").innerHTML;

        // 3-5
        // 数据写入
        doc.write("<div>" + wrap + "</div>");
        doc.close();

        //3-6
        // 获取iframe html文件head(必须在写入数据之后进行,写入数据时,会将head中的数据置空)
        const head = doc.head
        // link标签添加进iframe html文件的head里
        head.appendChild(linkTag)
        
        // 延时200ms,为了等css资源加载完
        setTimeout(() => {
            // 3-7
            iframe.contentWindow.focus();
            iframe.contentWindow.print();

             // 3-8
            document.body.removeChild(iframe);
        }, 200
       })
    }

遇到主要问题:

    1、print.vue中的样式主要是class进行编写使用的,通过(3-4)获取到的元素,在打印预览时class设置的样式会不生效,行内style设置的样式是正常的,这是因为创建的iframe中无对应style文件样式,对应class也就无效果。所以需要在(3-3)时通过document.styleSheets方式获取到print.vue文件下style中的标签内容,再进行插入。

    // 获取界面css样式
    getPrintPageCss() {
      const cssBlock = document.styleSheets
      // 当前界面样式会排在后面,所以先进行一次倒序操作
      const styleData = [...cssBlock].reverse().find((item, index) => {
        return [...item.cssRules].find((rule) => {
          if(!rule.selectorText) return false
          return rule.selectorText.includes('.topinfo')
        })
      })
      return styleData.ownerNode.href
    }

    2、(3-5)(3-6)这两步不要更换顺序,(3-5)进行操作时,会将iframe下head标签中的内容置空

    3、大部分浏览器目前对@page的语法支持不太好,主要能用的是改margin(外边距)、size(纸张、布局)少数几个。

     4、样式部分,本地调试可以使用style标签,上线后,需要改为link标签。

总的代码如下:

父组件代码:

<template>
  <div class="container">
    <el-button @click="handleClickPrint">打印</el-button>
    <Print ref="printRef" style="display: none;" />
  </div>
</template>

<script>
import Print from './components/Print.vue'
export default {
  components: {
    Print,
  },
  data() {
    return {}
  },
  methods: {
    // 获取界面css样式
    getPrintPageCss() {
      const cssBlock = document.styleSheets
      // 当前界面样式会排在后面,所以先进行一次倒序操作
      const styleData = [...cssBlock].reverse().find((item, index) => {
        // 如果样式是通过link,则排除(更正,不能排除link,发布到线上后文件都是link,排除会找不到文件)
        // if(item.href) return false
        return [...item.cssRules].find((rule) => {
          if(!rule.selectorText) return false
          return rule.selectorText.includes('.topinfo')
        })
      })
      return styleData.ownerNode.href
    },
    // 打印
    handleClickPrint() {
      // 数据导入
      const item = {
        goodsNm: "冰红茶",
        num: 1,
        notes: "11222",
      }
      const data = []
      for (let i = 1; i < 24; i++) {
        data.push(item)
      }
      this.$refs.printRef.print(data)

      this.$nextTick(() => {
        const iframe = document.createElement('iframe');
        iframe.style.display = 'none'
        document.body.appendChild(iframe)
        const doc = iframe.contentWindow.document;
        // 新建style标签(style标签用于本地调试,线上的话需要使用link标签)
        // const styleTag = document.createElement('style')
        const styleTag = document.createElement('link')
        // 设置style标签属性
        // styleTag.setAttribute('type', 'text/css')
        styleTag.setAttribute('rel', 'stylesheet')
        // 设置style标签内容
        // styleTag.innerHTML = this.getPrintPageCss()
        styleTag.href = this.getPrintPageCss()
        // 获取需要打印区域数据
        let wrap = document.getElementById("print-iframe").innerHTML;
        // 数据写入
        doc.write("<div>" + wrap + "</div>");
        doc.close();
        // 获取iframe html文件head(必须在写入数据之后进行,写入数据时,会将head中的数据置空)
        const head = doc.head
        // style标签添加进iframe html文件的head里
        head.appendChild(styleTag)
        iframe.contentWindow.focus();
        iframe.contentWindow.print();
        document.body.removeChild(iframe);
       })
    }
  }
}
</script>

<style lang="scss" scoped>
</style>

打印组件代码:

<template>
  <div id="print-iframe" class="print-iframe">
    <div class="topinfo">
      <div class="title-form textc center">{{ printData.subjNm }}接收单</div>
      <div class="bar">
        <div class="left" style="width: 100%;">
          客户名称: {{ printData.custNm }}
        </div>
      </div>
    </div>
    <table style="margin: 10px 0">
      <tbody class="part">
        <tr class="header">
          <th style="width: 60px">序号</th>
          <th>物品名称</th>
          <th style="width: 60px">数量</th>
          <th>物品备注</th>
        </tr>
        <tr v-for="(item, index) in printData.tableData" :key="index">
          <td class="textc">{{ index + 1 }}</td>
          <td class="abstr">{{ item.goodsNm }}</td>
          <td class="textc">{{ item.num }}</td>
          <td class="abstr">{{ item.notes }}</td>
        </tr>
      </tbody>
    </table>
    <div class="part2">
      <div class="bar">
        <div class="left" style="width: 100%;">
          接收说明:{{ printData.receiverNotes }}
        </div>
      </div>
      <div class="bar" style="margin-top: 30px;">
        <div class="left" style="width: 40%;">接收人:{{ printData.receiver }}</div>
        <div class="left">预计归还日期:{{ printData.returnTm }}</div>
        <div class="left" style="width: 30%;">客户签字:</div>
      </div>
      <div class="bar" style="margin-top: 25px;">
        <div class="left" style="width: 40%;"></div>
        <div class="left"></div>
        <div class="left" style="width: 30%;">日&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;期:</div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Print",
  data() {
    return {
      printData: {
        // 单据数据
        subjNm: "某某",
        custNm: "某某某",
        tableData: [],
        receiverNotes: "receiver",
        receiver: "某某某(11111111111)",
        returnTm: "2023-01-29",
      },
    };
  },
  methods: {
    // 打印数据传入
    print(data) {
      this.printData.tableData = data;
      console.log(data, '>>>');
    },
  },
};
</script>

<style lang="scss" scoped>
body {
  font-family: "Microsoft YaHei";
  // width:192mm; /*取7/8*/
  // height:116mm; /*取7/8/100mm*/
  text-align: left;
  margin: 2mm;
}
.amount {
  text-align: right;
}
.textl {
  text-align: left;
}
.textc {
  text-align: center;
}
.textr {
  text-align: right;
}
.outer {
  display: inline-block;
  width: 186mm;
  box-sizing: border-box;
}

.topinfo {
  width: 100%;
  line-height: 20px;
  margin-bottom: 10px;
}
.title-corp {
  line-height: 24px;
  font-size: 14px;
}
.title-form {
  line-height: 32px;
  font-size: 26px;
  margin-bottom: 10px;
}
.bar {
  display: table;
  table-layout: fixed;
  width: 100%;
}
.bar .center {
  display: table-cell;
  text-align: center;
}
.bar .right {
  display: table-cell;
  text-align: right;
  width: 35%;
}
.bar .left {
  display: table-cell;
  text-align: left;
  width: 35%;
}

tr.summary {
  height: 30px;
}
tr.header {
  height: 30px;
}
table tr {
  height: 11mm;
}
table {
  border-collapse: collapse;
  table-layout: fixed;
  width: 100%;
}
th {
  border: 1px solid #000;
  font-weight: normal;
  text-align: center;
}
td {
  border: 1px solid #000;
  text-align: left;
}
.abstr {
  white-space: nowrap; /*规定段落中的文本不进行换行 */
  overflow: hidden; /*超出部分隐藏*/
  text-overflow: ellipsis; /* 超出部分显示省略号 */
}
.footer {
  width: 99%;
  margin-top: 40px;
  height: 20px;
}
.footer div {
  float: left;
  width: 33%;
}
.part tr {
  // page-break-inside: always;
  page-break-inside: avoid; // 在元素后插入分页符
}
.part2 {
  page-break-inside: avoid; // 避免在元素后插入分页符
}
@media print {
  @page {
    // size: A5;
    // 上下外边距设为0,会导致打印内容挤压在纸张上下边缘,不推荐这样清除,直接在浏览器打印预览界面关闭页眉页脚即可
    // margin-top: 0mm; //去掉页眉
    // margin-bottom: 0mm; //去掉页脚
  }
}
</style>

最终效果:

Logo

前往低代码交流专区

更多推荐