vue前端页面实现打印与将页面转换为PDF下载

73 篇文章 3 订阅
29 篇文章 1 订阅

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>
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
实现页面转换PDF 并导出的最好方案,主要有两种方法:前端实现和后端实现前端实现: 在前端实现页面转换PDF 并导出,可以使用 jsPDF 和 html2canvas 库。jsPDF 是一个用于生成 PDF 文件的 JavaScript 库,而 html2canvas 是一个用于将 HTML 元素转换为 Canvas 的 JavaScript 库。 以下是一个简单的例子,展示如何在 Vue 中使用 jsPDF 和 html2canvas 实现页面转换PDF 并导出: 1. 安装 jsPDF 和 html2canvas 库: ```bash npm install jspdf html2canvas --save ``` 2. 在 Vue 组件中引入 jsPDF 和 html2canvas 库: ```javascript import jsPDF from 'jspdf' import html2canvas from 'html2canvas' ``` 3. 在 Vue 组件中定义导出 PDF 的方法: ```javascript export default { methods: { exportPDF() { // 获取需要导出的元素 let element = document.getElementById('pdf-content') // 将元素转换为 Canvas html2canvas(element).then(canvas => { // 获取 Canvas 图片数据 let imgData = canvas.toDataURL('image/png') // 计算 PDF 页面大小 let pageWidth = 210 let pageHeight = canvas.height * pageWidth / canvas.width // 创建 PDF 对象 let pdf = new jsPDF('p', 'mm', 'a4') // 添加 PDF 页面 pdf.addImage(imgData, 'PNG', 0, 0, pageWidth, pageHeight) // 导出 PDF 文件 pdf.save('export.pdf') }) } } } ``` 在以上代码中,`exportPDF` 方法用于导出 PDF 文件。首先,通过 `document.getElementById` 获取需要导出的元素,这里假设元素的 ID 为 `pdf-content`。然后,使用 `html2canvas` 将元素转换为 Canvas,再通过 Canvas 的 `toDataURL` 方法获取图片数据。接着,计算 PDF 页面大小,创建 jsPDF 对象,并将图片添加到 PDF 中。最后,使用 `pdf.save` 方法导出 PDF 文件。 后端实现: 在后端实现页面转换PDF 并导出,可以使用一些开源的 PDF 库,比如 wkhtmltopdf、WeasyPrintPDFKit 等。 其中,wkhtmltopdf 是一个开源的 HTML 到 PDF 转换器,可以在 Linux、Windows 和 macOS 等操作系统上使用。WeasyPrint 是一个开源的 HTML 和 CSS 到 PDF 转换器,支持 Linux、macOS 和 Windows 等操作系统。PDFKit 是一个 Node.js 库,可以用于生成 PDF 文件。 以下是一个简单的例子,展示如何在 Vue 中使用 wkhtmltopdf 实现页面转换PDF 并导出: 1. 安装 wkhtmltopdf: ```bash sudo apt-get install wkhtmltopdf ``` 2. 在 Vue 组件中定义导出 PDF 的方法: ```javascript export default { methods: { exportPDF() { // 计算 PDF 文件名 let filename = 'export.pdf' // 获取当前页面 URL let url = window.location.href // 发送 GET 请求,生成 PDF 文件 axios.get(`/api/pdf?url=${encodeURIComponent(url)}&filename=${encodeURIComponent(filename)}`, { responseType: 'blob' }).then(response => { // 创建 blob 对象 let blob = new Blob([response.data], { type: 'application/pdf' }) // 创建下载链接 let link = document.createElement('a') link.href = URL.createObjectURL(blob) link.download = filename // 触发下载 link.click() }) } } } ``` 在以上代码中,`exportPDF` 方法用于导出 PDF 文件。首先,计算 PDF 文件名,这里假设文件名为 `export.pdf`。然后,获取当前页面 URL,将 URL 和文件名作为参数,发送 GET 请求到后端 API,生成 PDF 文件。最后,将返回的二进制数据转换为 blob 对象,创建下载链接,并触发下载。 你可以根据需要选择前端实现或后端实现,以满足你的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值