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>
          &emsp;&emsp;根据统计,本月用电系统报警事件总计 {{ 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>
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐