Vue front-end page realizes printing and converting pages to PDF download

The vue front-end page realizes printing and converting the page to PDF download:
first is a contract page, the required function is to print the contract directly on the page, or download the contract in PDF format: bottom of the
insert image description here
page:
insert image description here
first print:
The doPrint function is called:

doPrint() {
    
    
      this.$print(this.$refs.print);
    }

Define print.js under utils:
insert image description here

// 打印类属性、方法定义
/* 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

Add the following code to style:
insert image description here

@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);
}

Introduced and used in main.js: import Print from './utils/print.js' Vue.use(Print)
insert image description here
test print:
insert image description here

2. Download:
Define htmlToPdf.js under utils:

// 导出页面为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");
      });
    };
  }
};

Called in main.js:import htmlToPdf from './utils/htmlToPdf.js' Vue.use(htmlToPdf)
insert image description here

After saving here, the page will report an error: the jspdf module cannot be found

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

Install the module:cnpm install [email protected]
insert image description here

Test download:
insert image description here

The css file introduced by the page has been placed on the cloud disk: Link: https://pan.baidu.com/s/1iNj8Z1Wk4JiaiCXHfNXR4w?pwd=6666 Extraction code: 6666 After copying this content, open the Baidu Netdisk mobile app, which is more convenient to operate oh
insert image description here

The template code for the contract page is as follows:

<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>

Guess you like

Origin blog.csdn.net/weixin_42260782/article/details/127745837