vue前端页面实现打印与将页面转换为PDF下载:
首先是一个合同页面,需要的功能是,直接在页面打印该合同,或者将该合同以PDF的格式下载下来:
页面最下方:
首先是打印:
调用了doPrint函数:
doPrint() {
this.$print(this.$refs.print);
}
在utils下面定义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.wrapperRefDom(this.dom).outerHTML
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 MyPlugin = {}
MyPlugin.install = function (Vue, options) {
// 4. 添加实例方法
Vue.prototype.$print = Print
}
export default MyPlugin
在style中加入以下代码:
@media print {
//重要
/deep/ #app .content-container {
width: 100% !important;
// height: 100% !important;
padding: 0 !important;
// margin: 0 !important;
margin-left: 0px !important;
}
//重要
/deep/ #app .main-container {
width: 100% !important;
height: 100% !important;
padding: 0 !important;
margin: 0 !important;
}
}
.pagehead {
width: 100%;
overflow: hidden;
}
.titleimg {
height: 42px;
width: 100%;
margin-bottom: 0px;
}
/deep/ .el-radio__input.is-disabled + span.el-radio__label {
color: #000 !important;
font-weight: bold !important;
font-size: 16px !important;
padding-left: 3px !important;
}
.paper {
padding-bottom: 60px;
}
.btnbox {
width: calc(100vw - 180px);
position: fixed;
bottom: 0;
right: 0;
height: 60px;
background-color: #fff;
box-shadow: 1px 3px 9px rgba($color: #000000, $alpha: 0.2);
background-color: #fff;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
/deep/.el-image {
width: 100px;
height: 100px;
}
.dialog-footer {
padding-top: 20px;
text-align: center;
color: #000000;
}
.el-form-item {
display: inline-block;
width: 40%;
margin-bottom: 5px;
}
.contract-box {
font-family: SimSun;
line-height: 1.6;
color: #000;
font-weight: bold;
text-align: center;
margin: 10px auto;
padding: 2% 23mm;
div {
margin-bottom: 10px;
}
.xiahuaxian {
border-bottom: 1px solid #333;
padding: 0 10px;
}
.space {
padding-left: 30px;
}
.header {
font-weight: 500;
.title {
font-weight: bold;
font-family: SimSun;
color: #000;
font-size: 24px;
}
.sub-title {
font-weight: bold;
font-family: SimSun;
color: #000;
font-size: 22px;
padding-top: 10px;
}
}
.center {
// padding-top: 10px;
font-weight: 500;
font-size: 16px;
text-align: left;
color: #000;
// font-weight: bold;
}
.content {
font-weight: 500;
font-size: 16px;
text-align: left;
color: #000;
font-weight: bold;
text-indent: 2em;
margin-bottom: 10px;
text-indent: 40px;
}
}
/deep/ .el-radio__input.is-disabled + span.el-radio__label {
color: #333;
}
/deep/ .el-radio__input.is-disabled.is-checked .el-radio__inner {
background-color: #ffffff;
border-color: #9c9c9c;
}
/deep/ .el-radio__input.is-disabled .el-radio__inner {
background-color: #ffffff;
border-color: #9c9c9c;
}
/deep/ .el-radio__input.is-disabled.is-checked .el-radio__inner::after {
background-color: #7e8ba5;
}
.mytable {
width: 100%;
text-align: center;
border-collapse: collapse;
.mytd {
// padding: 10px 0;
height: 34px;
.inner-div {
text-align: left;
padding-top: 5px;
padding-left: 5px;
}
}
}
.sign-box {
display: flex;
align-items: flex-start;
.sign-item {
width: 50%;
display: flex;
justify-content: flex-start;
align-items: flex-end;
}
}
.sheet {
-webkit-box-shadow: 0 0.5mm 2mm rgb(255, 255, 255);
box-shadow: 0 0.5mm 2mm rgba(0, 0, 0, 0);
}
在main.js中引入并使用:import Print from './utils/print.js'
Vue.use(Print)
测试打印:
二、下载:
在utils下面定义htmlToPdf.js:
// 导出页面为PDF格式
import html2Canvas from "html2canvas";
import JsPDF from "jspdf";
export default {
install(Vue, options) {
Vue.prototype.getPdf = function() {
var title = this.htmlTitle;
html2Canvas(document.querySelector("#pdfDom"), {
allowTaint: true
}).then(function(canvas) {
let contentWidth = canvas.width;
let contentHeight = canvas.height;
let pageHeight = (contentWidth / 592.28) * 841.89;
let leftHeight = contentHeight;
let position = 0;
let imgWidth = 595.28;
let imgHeight = (592.28 / contentWidth) * contentHeight;
let pageData = canvas.toDataURL("image/jpeg", 1.0);
let PDF = new JsPDF("", "pt", "a4");
if (leftHeight < pageHeight) {
PDF.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
} else {
while (leftHeight > 0) {
PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight);
leftHeight -= pageHeight;
position -= 841.89;
if (leftHeight > 0) {
PDF.addPage();
}
}
}
PDF.save(title + ".pdf");
});
};
}
};
在main.js中调用:import htmlToPdf from './utils/htmlToPdf.js'
Vue.use(htmlToPdf)
这里保存后页面会报错:找不到jspdf这个模块
These dependencies were not found:
* html2canvas in ./src/utils/htmlToPdf.js
* jspdf in ./src/utils/htmlToPdf.js
To install them, you can run: npm install --save html2canvas jspdf
安装该模块:cnpm install jspdf@2.2.0
测试下载:
其中页面引入的css文件已经放在云盘:链接: https://pan.baidu.com/s/1iNj8Z1Wk4JiaiCXHfNXR4w?pwd=6666 提取码: 6666 复制这段内容后打开百度网盘手机App,操作更方便哦
合同页面的模板代码如下:
<template>
<div style=" background-color: #fff;">
<!-- <el-breadcrumb
separator-class="el-icon-arrow-right"
style="margin: 0 5px 5px 5px"
>
<el-breadcrumb-item :to="{ path: 'contract' }"
>合同列表</el-breadcrumb-item
>
<el-breadcrumb-item>合同详情</el-breadcrumb-item>
</el-breadcrumb> -->
<div class="paper A4" style="margin:0 auto;background-color: #fff;">
<div class="contract-box sheet" ref="print" id="pdfDom">
<div class="header">
<!-- <div class="pagehead" style="margin-bottom: 0">
<el-image
class="titleimg"
:src="contractInfo.logo"></el-image>
</div> -->
<div class="title" style="margin-bottom: 0">
{{ contractInfo.title }}
</div>
<!-- <div class="sub-title" style="margin-bottom: 0">工程业务合约</div> -->
</div>
<div class="center">
<div>
<span> 甲方单位名称:</span>
<span class="xiahuaxian"
>{{ contractInfo.partya }}</span>
<span class="space"></span>
<span> 甲方代表:</span>
<span class="xiahuaxian"
>{{ contractInfo.partyaDelegate }}</span>
<span class="space"></span>
<span> 甲方代表联系方式:</span>
<span class="xiahuaxian">{{ contractInfo.partyaMobile }}</span>
<span class="space"></span>
<span> 甲方公司地址:</span>
<span class="xiahuaxian">{{contractInfo.partyaAddress }}</span>
<!-- <span class="space"></span>
<span> 甲方项目经理:</span>
<span class="xiahuaxian">{{ contractInfo.siteManager }}</span>
<span class="space"></span>
<span> 联系方式:</span>
<span class="xiahuaxian">{{ contractInfo.siteManagerMobile }}</span> -->
</div>
<div>
<span> 乙方单位名称:</span>
<span class="xiahuaxian"
>{{ contractInfo.partyb }}</span>
<span class="space"></span>
<span> 乙方代表:</span>
<span class="xiahuaxian"
>{{ contractInfo.partybDelegate }}</span>
<span class="space"></span>
<span> 乙方代表联系方式:</span>
<span class="xiahuaxian">{{ contractInfo.partybMobile }}</span>
<span class="space"></span>
<span> 乙方公司地址:</span>
<span class="xiahuaxian">{{ contractInfo.partybAddress }}</span>
<!-- <span class="space"></span>
<span> 经理联系方式:</span>
<span class="xiahuaxian">{{ contractInfo.partybManagerMobile }}</span> -->
</div>
<div class="content">
{{ contractInfo.content }}
</div>
<div>
<div>
<!-- <span>工程项目地址:</span>
<span class="xiahuaxian"
>{{ contractInfo.province }}{{ contractInfo.city
}}{{ contractInfo.region
}}{{ contractInfo.detailAddress }}</span
> -->
<span class="space"></span>
<span>约定服务工期:</span>
<span class="xiahuaxian"
>{{ contractInfo.startTime }}至{{
contractInfo.finishTime
}}</span
>
</div>
<div>
包工:工程项目总额:<span class="xiahuaxian">
{{ contractInfo.totalMoney }} </span
>元
<span class="space"></span>
定金及预付款:<span class="xiahuaxian">
{{ contractInfo.frontMoney }}</span
>元
<span class="space"></span>
项目尾款:<span class="xiahuaxian">{{
contractInfo.finishMoney
}}</span
>元
</div>
<!-- <div>
点工:<span class="xiahuaxian">{{
contractInfo.spotWorkWages
}}</span
>元/天
<span class="space"></span>
约定服务时间:<span class="xiahuaxian"
>{{ contractInfo.appointmentStartTime }}至{{
contractInfo.appointmentEndTime
}}</span
>
<span class="space"></span>
约定加班费:<span class="xiahuaxian">{{
contractInfo.appointmentWorkOvertime
}}</span>
<span class="space"></span>
</div> -->
<!-- <div>
付款渠道:
<el-radio
:label="1"
v-model="isCheck"
:disabled="true"
style="color: #000; font-weight: bold; font-size: 16px"
>微信</el-radio
>
<el-radio
:label="2"
v-model="isCheck"
:disabled="true"
style="color: #000; font-weight: bold"
>支付宝</el-radio
>
<el-radio
:label="3"
v-model="isCheck"
:disabled="true"
style="color: #000; font-weight: bold"
>银行转账</el-radio
>
<el-radio
:label="4"
v-model="isCheck"
:disabled="true"
style="color: #000; font-weight: bold"
>现金</el-radio
>
<el-radio
:label="5"
v-model="isCheck"
:disabled="true"
style="color: #000; font-weight: bold"
>其他</el-radio
>
约定付款时间:<span class="xiahuaxian"></span>
</div> -->
<!-- <div>工程服务明细及要求:</div>
<div>
<table border="1" class="mytable">
<tr v-if="otherContractProofing.length>0">
<td class="mytd" colspan="1">名称</td>
<td class="mytd" colspan="1">数量</td>
<td class="mytd" colspan="1">单价</td>
<td class="mytd" colspan="1">总价</td>
</tr>
<tr >
<td class="mytd" colspan="1">{{ contractProofing.name }}</td>
<td class="mytd" colspan="1">{{ contractProofing.num }}</td>
<td class="mytd" colspan="1">{{ contractProofing.price }}</td>
<td class="mytd" colspan="1">
{{ contractProofing.totalPrice }}
</td>
</tr>
<tr v-for="(item, index) in otherContractProofing" :key="index" v-if="otherContractProofing.length>0">
<td class="mytd" colspan="1">{{ item.name }}</td>
<td class="mytd" colspan="1">{{ item.num }}</td>
<td class="mytd" colspan="1">{{ item.price }}</td>
<td class="mytd" colspan="1">{{ item.totalPrice }}</td>
</tr>
<tr v-if="otherContractProofing.length>0">
<td class="mytd">合计:</td>
<td class="mytd">{{ total.num }}</td>
<td class="mytd">{{ total.price }}</td>
<td class="mytd">{{ total.totalPrice }}</td>
</tr>
<tr>
<td class="mytd" colspan="5">
<div class="inner-div">
特殊注意事项:{{ contractInfo.specialNotes }}
</div>
</td>
</tr>
<tr>
<td class="mytd" colspan="5">
<div class="inner-div">
约定服务质量及施工要求{{ contractInfo.quality}}
</div>
</td>
</tr>
</table>
</div> -->
<div>
合约备注:<br />
{{ contractInfo.remarks }}
</div>
<!-- <div>
合约备注:
<br />
1.本合约一式三份,甲方持一份,乙方两份,以甲方签字,乙方签字盖章生效。
<br />
2.如有任何合约变动,需经双方协商,书面补充该协议,口头协议视为无效。
</div> -->
<div style="height: 1px"></div>
<div class="sign-box" style="margin-bottom: 0px">
<div class="sign-item" style="margin-bottom: 0">
<span>甲方签名:</span>
<!-- <img style="width:60px;height:60px" src="" alt=""> -->
</div>
<div class="sign-item" style="margin-bottom: 0">
<span>乙方签名:</span>
<!-- <img style="width:60px;height:60px" src="" alt=""> -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>