1、安装依赖环境
npm install --save html2canvas //将页面html转换成图片
npm install jspdf --save //将图片生成pdf
2、定义全局转换函数
创建一个pdf.js文件在指定位置,主要作用是进行html——>图片——>pdf的转换,可以直接引用,也可挂载在Vue实例,所有有两种写法。
实现逻辑思路:
a、获取DOM元素;
b、将DO转换成Canvas;
c、获取转换后的canvas的高度和宽度;
d、将PDF的宽和高设置为canvas的宽高;
e、将canvas转换成图片;
f、实例化jspdf,将内容图片放在pdf中因为内容宽高和pdf宽高一样,就只需要一页,也防止内容截断问题)。
a、挂载到Vue实例
代码如下:
// 引入依赖
import Vue from "vue";
import html2canvas from "html2canvas";
import JsPDF from "jspdf";
const PDF = {};
// eslint-disable-next-line no-unused-vars
PDF.install = function(Vue, options) {
Vue.prototype.$pdf = function(dom, user) {
html2canvas(dom).then(canvas => {
var contentWidth = canvas.width;
var contentHeight = canvas.height;
//一页pdf显示html页面生成的canvas高度;
var pageHeight = (contentWidth / 595.28) * 841.89;
//未生成pdf的html页面高度
var htmlHeight = contentHeight;
//pdf页面偏移
var position = 0;
//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
var imgWidth = 595.28;
var imgHeight = (595.28 / contentWidth) * contentHeight;
var pageData = canvas.toDataURL("image/jpeg", 1.0);
debugger;
var pdf = new JsPDF("", "pt", "a4");
//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
//当内容未超过pdf一页显示的范围,无需分页
if (htmlHeight < pageHeight) {
pdf.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
} else {
while (htmlHeight > 0) {
pdf.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight);
htmlHeight -= pageHeight;
position -= 841.89;
//避免添加空白页
if (htmlHeight > 0) {
pdf.addPage();
}
}
}
pdf.save(user + ".pdf");
// doc.save(user);
});
};
};
Vue.use(PDF);
export default PDF;
在main.js中使用,直接import即可。
import "./base/tools/pdf";
a、直接引入实现方法
代码如下:
// 导出页面为PDF格式,宽度大于高度
/**
* 用于 t-student-attendance 学生端-学生管理-学生考勤-下载记分册(任课老师和班主任)
*/
import html2Canvas from "html2canvas";
import JsPDF from "jspdf";
export default {
printPdfBigWidth
};
/**
* @param ele 要生成 pdf 的DOM元素(容器)
* @param pdfName PDF文件生成后的文件名字
* @param pos 调整间距 {left:25,top:25}
* */
function printPdfBigWidth(ele, pdfName) {
//留白
html2Canvas(ele, {
scale: 1 // 提升画面质量,但是会增加文件大小
}).then(function(canvas) {
/**jspdf将html转为pdf一页显示不截断,整体思路:
* 1. 获取DOM
* 2. 将DOM转换为canvas
* 3. 获取canvas的宽度、高度(稍微大一点)
* 4. 将pdf的宽高设置为canvas的宽高
* 5. 将canvas转为图片
* 6. 实例化jspdf,将内容图片放在pdf中(因为内容宽高和pdf宽高一样,就只需要一页,也防止内容截断问题)841.89
*/
// 得到canvas画布的单位是px 像素单位
var contentWidth = canvas.width;
var contentHeight = canvas.height;
//一页pdf显示html页面生成的canvas高度;
var pageHeight = (contentWidth / 595.28) * 841.89;
//未生成pdf的html页面高度
var htmlHeight = contentHeight;
//pdf页面偏移
var position = 0;
//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
var imgWidth = 595.28;
var imgHeight = (595.28 / contentWidth) * contentHeight;
var pageData = canvas.toDataURL("image/jpeg", 1.0);
debugger;
var pdf = new JsPDF("", "pt", "a4");
//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
//当内容未超过pdf一页显示的范围,无需分页
if (htmlHeight < pageHeight) {
pdf.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
} else {
while (htmlHeight > 0) {
pdf.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight);
htmlHeight -= pageHeight;
position -= 841.89;
//避免添加空白页
if (htmlHeight > 0) {
pdf.addPage();
}
}
}
// 将内容图片添加到pdf中
pdf.save(pdfName + ".pdf");
});
}
然后在使用的组件中直接import即可。
import printPdfBigWidth from "@/projects/utils/reportExport/printPdfBigWidth";
完整代码示例如下:
pdf容器文件,pdfPage.vue:
<template>
<div class="search">
<!-- 页面展示区 -->
<!-- <div></div> -->
<pdfContent></pdfContent>
<!-- pdf打印区 -->
<div style="position: fixed; top: 0; left: -2000px">
<!-- <div id="pdfContainer" style="margin-left: auto; margin-right: auto"></div> -->
<pdfContent id="pdfContainer"></pdfContent>
</div>
</div>
</template>
<script>
import printPdfBigWidth from "@/projects/utils/reportExport/printPdfBigWidth";
import pdfContent from "./pdfContent";
export default {
name: "pdf-print",
components: { pdfContent },
data() {
return {
searchForm: {}
};
},
methods: {
// 导出pdf
downloadPDF() {
printPdfBigWidth.printPdfBigWidth(document.querySelector("#pdfContainer"), "运维报表");
//挂载VUE实例时写法
//this.$pdf(document.querySelector("#pdfContainer"), "运维报表");
}
},
computed: {
width() {
return Number(document.documentElement.clientWidth);
},
showScroll() {
if (this.width <= 1280) {
return {
"overflow-x": "scroll",
"overflow-y": "hidden"
};
} else {
return {};
}
}
}
};
</script>
<style lang="scss" scoped>
.search {
width: 100%;
/* padding: 30px; */
color: #000 !important;
font-family: "STHeiti";
}
</style>
pdf内容文件,pdfCotent.vue:
注意文件中定义的PDF尺寸大小,为A4纸的大小。
<!-- -->
<template>
<div class="pdfContent" style="margin-left: auto; margin-right: auto">
<div class="report-p1" style="page-break-before: always">
<span class="R-advert">电力运维 我们是您的管家</span>
<h1>{{ reportData.reportDate }}运维报告</h1>
<table class="message">
<tbody>
<tr>
<td>客户名称:</td>
<td>{{ reportData.customName }}</td>
</tr>
<tr>
<td>报告周期:</td>
<td>{{ reportData.reportCycle }}</td>
</tr>
<tr>
<td>生成日期:</td>
<td>{{ reportData.createDate }}</td>
</tr>
</tbody>
</table>
<img src="./assets/report-bg-01.png" class="page1-bg" />
</div>
<div class="report-p2" style="page-break-before: always">
<div class="content">
<span class="title">事件统计</span>
<h1>1、事件概况</h1>
<p>
  根据统计,本月用电系统报警事件总计 {{ reportData.text_eventTotal }} 条;按照事件等级,事故报警共
{{ reportData.text_accidentEventTotal }} 条,占比为 {{ reportData.text_accidentEventRate }}% ,请注意查看。
</p>
<h1>2、报警事件总数统计</h1>
<div class="reportChart">
<CetChart v-bind="CetChart_eventTotal"></CetChart>
</div>
<h1>3、报警事件类型分析</h1>
<h4>监测终端数量:26台</h4>
<div class="reportChart">
<CetChart v-bind="CetChart_eventType"></CetChart>
</div>
<!-- <h1>4、报警事件等级占比</h1> -->
</div>
</div>
</div>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import echarts from "echarts";
export default {
//import引入的组件需要注入到对象中才能使用
components: {},
data() {
//这里存放数据
return {
reportData: {
reportDate: "2021年2月", //报告日期
customName: "--",
reportCycle: "XXXX-XX-XX 至 XXXX-XX-XX",
createDate: "XXXX年XX月XX日",
text_eventTotal: "--", //报警事件总计
text_accidentEventTotal: "--", //事故事件数量
text_accidentEventRate: "--" //事故事件占比
},
//本月报警事件统计
CetChart_eventTotal: {
//组件输入项
inputData_in: null,
options: {
title: {
left: "center",
text: "本月报警事件总数统计",
textStyle: {
color: "#606368"
}
},
grid: {
height: 120,
left: "0%",
right: "10%",
containLabel: true
},
legend: {
show: true,
data: ["报警数"]
},
xAxis: {
type: "category",
name: "日期",
axisLabel: {
textStyle: {
color: "#606368" //更改坐标轴文字颜色
}
},
axisLine: {
lineStyle: {
type: "solid",
color: "#606368"
}
},
data: ["2021/01/01", "2021/01/02", "2021/01/03", "2021/01/04", "2021/01/05", "2021/01/06", "2021/01/07"]
},
yAxis: {
type: "value",
name: "事件数量",
axisLabel: {
textStyle: {
color: "#606368" //更改坐标轴文字颜色
}
},
axisLine: {
lineStyle: {
type: "solid",
color: "#606368"
}
}
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: "line"
}
]
}
},
//事件类型统计
CetChart_eventType: {
//组件输入项
inputData_in: null,
options: {
title: {
left: "center",
text: "事件类型统计",
textStyle: {
color: "#606368"
}
},
grid: {
height: 120,
left: 0,
right: "10%",
containLabel: true
},
legend: {
show: true,
data: ["事件数量"]
},
xAxis: {
name: "事件数",
axisLabel: {
textStyle: {
color: "#606368" //更改坐标轴文字颜色
}
},
axisLine: {
lineStyle: {
type: "solid",
color: "#606368"
}
}
},
yAxis: {
type: "category",
name: "事件类型",
axisLabel: {
textStyle: {
color: "#606368" //更改坐标轴文字颜色
}
},
axisLine: {
lineStyle: {
type: "solid",
color: "#606368"
}
},
data: ["主机越限返回事件", "主机越限事件"]
},
series: [
{
name: "value",
type: "bar",
data: [3.66, 2.86]
}
]
}
},
};
},
//监听属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {},
//方法集合
methods: {},
//生命周期 - 创建完成(可以访问当前this实例)
created() {},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style lang='scss' scoped>
.report-p1,
.report-p2,
.report-p3 {
width: 595.28px;
height: 841.89px;
clear: both;
margin: 0 auto;
padding: 30px;
position: relative;
border: 1px solid #000;
background-color: #fff;
.reportChart {
height: 180px;
}
}
.report-p1 {
h1 {
text-align: center;
margin-top: 140px;
font-size: 36px;
font-weight: bold;
margin-bottom: 80px;
}
label {
padding-top: 8px;
font-weight: normal;
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
font-weight: 700;
}
.message {
margin: 0 auto;
width: 400px;
padding: 10px;
border: 1px solid #ccc;
background-color: #eee;
clear: both;
}
table {
background-color: transparent;
display: table;
border-spacing: 0px;
border-collapse: collapse;
box-sizing: border-box;
text-indent: initial;
border-color: grey;
tbody {
display: table-row-group;
vertical-align: middle;
border-color: inherit;
tr {
display: table-row;
vertical-align: inherit;
border-color: inherit;
td:first-child {
width: 100px;
}
td {
padding: 10px;
border: 1px solid #ccc;
}
}
}
}
.page1-bg {
position: absolute;
bottom: 0;
left: -5px;
vertical-align: middle;
}
}
.report-p2 {
.content {
width: 535px;
height: 780px;
border-top: 1px solid #dfe1e6;
span.title {
display: block;
width: 135px;
position: relative;
top: -12px;
left: 200px;
text-align: center;
background: #fff;
font-size: 16px;
font-weight: bold;
}
h1 {
font-size: 20px;
// font-weight: bold;
}
}
}
.report-p3 {
.content {
width: 535px;
height: 780px;
border-top: 1px solid #dfe1e6;
span.title {
display: block;
width: 135px;
position: relative;
top: -12px;
left: 200px;
text-align: center;
background: #fff;
font-size: 16px;
font-weight: bold;
}
h1 {
font-size: 20px;
// font-weight: bold;
}
}
.page1-bg {
width: 535px;
}
}
</style>