vue2 打印、导出
父组件
<template> <el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body> <statement-preview :data-list="dataList" ref="statementPreview" @loadingChange="loadingChange" /> <div slot="footer" class="dialog-footer print"> <div> <el-progress v-show="printLoading" :percentage="exportPercentage"></el-progress> </div> <div> <el-button type="primary" :loading="printLoading" :disabled="printLoading" @click="onPrinter">打印</el-button> <el-button type="primary" :loading="printLoading" :disabled="printLoading" @click="onExportPDF">导出</el-button> <el-button @click="cancel">关 闭</el-button> </div> </div> </el-dialog> </template> <script> import StatementPreview from "./StatementPreview.vue"; import { sortData } from "../utils"; import store from "@/store"; export default { name: "StatementSelectView", components: { StatementPreview }, data() { return { title: "导出/打印 预览效果", open: false, dataList: [], printLoading: false, exportPercentage: 0, // 初始进度值 status: "active" // 进度条状态 }; }, created() {}, mounted() {}, methods: { init(dataList) { this.dataList = sortData(dataList); this.open = true; }, // 打印 onPrinter() { this.$refs.statementPreview.printer(); }, // 导出 onExportPDF() { this.$refs.statementPreview.exportPDF(); }, loadingChange(value) { const { exportPercentage, printLoading } = value; this.printLoading = printLoading; this.exportPercentage = Number(exportPercentage) > 100 ? 100 : Number(exportPercentage); }, // 取消按钮 cancel() { this.open = false; } } }; </script> <style lang="scss" scoped> .print { display: flex; align-items: center; justify-content: space-between; ::v-deep { .el-progress-bar { width: 300px !important; } .el-progress-bar__outer { height: 15px !important; } .el-progress-bar__inner { background-color: #409eff; background-image: -webkit-linear-gradient( 45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent ); background-image: -o-linear-gradient( 45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent ); background-image: linear-gradient( 45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent ); -webkit-background-size: 1rem 1rem; background-size: 1rem 1rem; animation: 2s linear infinite pbs; animation-iteration-count: 10; } @keyframes pbs { 0% { background-position-x: 1rem; } } } } </style>
子组件
<template> <div :class="['print-box', { 'pdf': isPdf }]"> <div v-for="(item, index) in dataList" :key="index"> <!-- 需要打印的内容--> </div> </div> </template> <script> import html2canvas from "html2canvas"; import jsPDF from "jspdf"; export default { name: "StatementPreview", props: { dataList: { type: Array, default: [], required: true } }, components: { }, data() { return { isPdf: false, exportPercentage: 0, printLoading: false, exportLoading: false }; }, created() {}, mounted() {}, methods: { printer() { this.printLoading = true; this.isPdf = true; this.loadingChange(); let dom = document.querySelector(".print-box"); let time = setTimeout(() => { this.generatePrint(dom).then(() => { this.isPdf = false; this.printLoading = false; this.loadingChange(); }); clearTimeout(time); time = null; }, 100); }, generatePrint(dom) { return new Promise((resolve, reject) => { let reportItem = dom.querySelectorAll(".report-dom"); const pageNumber = Math.floor((100 / reportItem.length) * 10) / 10; let nextData = null; if (pageNumber > 0) nextData = reportItem.entries(); let div = document.createElement("div"); const printPage = (value) => { // 生成打印的图片 if (value.done) { // 结束 console.log(div); this.$print(div); // document.body.appendChild(div); this.exportPercentage = 0; resolve(); } else { html2canvas(value.value[1], { scale: 2, backgroundColor: "#ffffff", useCORS: true, scrollY: 0, scrollX: 0 // width: 794, // height: 1123 }).then((canvas) => { const imgData = canvas.toDataURL("image/jpeg", 1.0); let img = document.createElement("img"); img.classList.add("printImg"); img.src = imgData; div.appendChild(img); // // 进度条 if (this.exportPercentage >= 100) this.exportPercentage = 100; else { let value = this.exportPercentage + pageNumber; value = Math.round(value, 1); this.exportPercentage = value; } this.loadingChange(); printPage(nextData.next()); }); } }; if (nextData) printPage(nextData.next()); else this.$message.warning("打印异常,请稍后重试"); }); }, exportPDF() { this.exportLoading = true; this.isPdf = true; this.loadingChange(); let dom = document.querySelector(".print-box"); let time = setTimeout(() => { this.generatePDF(dom).then(() => { this.isPdf = false; this.exportLoading = false; this.loadingChange(); }); clearTimeout(time); time = null; }, 300); }, generatePDF(dom) { return new Promise((resolve, reject) => { let reportItem = dom.querySelectorAll(".report-dom"); const pageNumber = Math.floor((100 / reportItem.length) * 10) / 10; const pdf = new jsPDF("", "pt", "a4"); let nextData = null; if (pageNumber > 0) nextData = reportItem.entries(); const pdfPage = (value) => { if (value.done) { // 结束 pdf.save(`导出.pdf`); this.exportPercentage = 0; resolve(); } else { html2canvas(value.value[1], { scale: 2, backgroundColor: "#ffffff", scrollY: 0, scrollX: 0, width: 794, height: 1123, useCORS: true // 是否尝试使用CORS从服务器加载图像 (allowTaint 允许跨域 不允许同时true) }).then((canvas) => { const contentWidth = canvas.width; const contentHeight = canvas.height; const imgWidth = 595.28; const imgHeight = (595.28 / contentWidth) * contentHeight; const pageData = canvas.toDataURL("image/jpeg", 1.0); pdf.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight); if (value.value[0] !== reportItem.length - 1) pdf.addPage(); // 导出进度条 if (this.exportPercentage >= 100) this.exportPercentage = 100; else { let value = this.exportPercentage + pageNumber; value = Math.round(value, 1); this.exportPercentage = value; } this.loadingChange(); // end pdfPage(nextData.next()); }); } }; if (nextData) pdfPage(nextData.next()); else this.$message.warning("导出异常,请稍后重试"); }); }, // 改变父组件的loading状态 loadingChange() { this.$emit("loadingChange", { exportPercentage: this.exportPercentage, printLoading: this.printLoading || this.exportLoading }); } } }; </script> <style lang="scss" scoped> .pdf { .report-dom { } } </style>
print.js
// 打印类属性、方法定义 /* eslint-disable */ const Print = function (dom, options) { if (!(this instanceof Print)) return new Print(dom, options); this.options = this.extend({ 'noPrint': '.no-print' }, options); if ((typeof dom) === "string") { this.dom = document.querySelector(dom); } else { this.isDOM(dom); this.dom = this.isDOM(dom) ? dom : dom.$el; } this.init(); }; Print.prototype = { init: function () { var content = this.getStyle() + this.getHtml(); this.writeIframe(content); }, extend: function (obj, obj2) { for (var k in obj2) { obj[k] = obj2[k]; } return obj; }, getStyle: function () { var str = "", styles = document.querySelectorAll('style,link'); for (var i = 0; i < styles.length; i++) { str += styles[i].outerHTML; } str += "<style>" + (this.options.noPrint ? this.options.noPrint : '.no-print') + "{display:none;}; </style>"; return str; }, getHtml: function () { var inputs = document.querySelectorAll('input'); var textareas = document.querySelectorAll('textarea'); var selects = document.querySelectorAll('select'); for (var k = 0; k < inputs.length; k++) { if (inputs[k].type == "checkbox" || inputs[k].type == "radio") { if (inputs[k].checked == true) { inputs[k].setAttribute('checked', "checked") } else { inputs[k].removeAttribute('checked') } } else if (inputs[k].type == "text") { inputs[k].setAttribute('value', inputs[k].value) } else { inputs[k].setAttribute('value', inputs[k].value) } } for (var k2 = 0; k2 < textareas.length; k2++) { if (textareas[k2].type == 'textarea') { textareas[k2].innerHTML = textareas[k2].value } } for (var k3 = 0; k3 < selects.length; k3++) { if (selects[k3].type == 'select-one') { var child = selects[k3].children; for (var i in child) { if (child[i].tagName == 'OPTION') { if (child[i].selected == true) { child[i].setAttribute('selected', "selected") } else { child[i].removeAttribute('selected') } } } } } // 包裹要打印的元素 // fix: https://github.com/xyl66/vuePlugs_printjs/issues/36 let outerHTML = this.dom.innerHTML; return outerHTML; }, // 向父级元素循环,包裹当前需要打印的元素 // 防止根级别开头的 css 选择器不生效 wrapperRefDom: function (refDom) { let prevDom = null let currDom = refDom // 判断当前元素是否在 body 中,不在文档中则直接返回该节点 if (!this.isInBody(currDom)) return currDom while (currDom) { if (prevDom) { let element = currDom.cloneNode(false) element.appendChild(prevDom) prevDom = element } else { prevDom = currDom.cloneNode(true) } currDom = currDom.parentElement } return prevDom }, writeIframe: function (content) { var w, doc, iframe = document.createElement('iframe'), f = document.body.appendChild(iframe); iframe.id = "myIframe"; //iframe.style = "position:absolute;width:0;height:0;top:-10px;left:-10px;"; iframe.setAttribute('style', 'position:absolute;width:0;height:0;top:-10px;left:-10px;'); w = f.contentWindow || f.contentDocument; doc = f.contentDocument || f.contentWindow.document; doc.open(); doc.write(content); doc.close(); var _this = this iframe.onload = function(){ _this.toPrint(w); setTimeout(function () { document.body.removeChild(iframe) }, 100) } }, toPrint: function (frameWindow) { try { setTimeout(function () { frameWindow.focus(); try { if (!frameWindow.document.execCommand('print', false, null)) { frameWindow.print(); } } catch (e) { frameWindow.print(); } frameWindow.close(); }, 10); } catch (err) { console.log('err', err); } }, // 检查一个元素是否是 body 元素的后代元素且非 body 元素本身 isInBody: function (node) { return (node === document.body) ? false : document.body.contains(node); }, isDOM: (typeof HTMLElement === 'object') ? function (obj) { return obj instanceof HTMLElement; } : function (obj) { return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string'; } }; const PrintPlugin = {}; PrintPlugin.install = function(Vue, options) { Vue.prototype.$print = Print; }; export default PrintPlugin;