vue批量导出pdf(含分页及解决最后一页空白问题)

1、问题概述:由于vue模板的限制,导出pdf只能打印出来一页。因此我单独写了一个h5页面,将该页面放在服务器上,接收需要导出内容的id即可完成分页导出。附加解决导出的最后一页是空白的问题。

2、效果预览:

3、完整代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>导出手卡</title>
    <style>
      #card-inner {
        background-color: #fff;
        width: 1400px;
        height: 100%;
      }
      #imgWrap {
        width: 1400px;
        height: 1990px;
      }
      .card-list {
        page-break-before: always;
        page-break-after: always;
        page-break-inside: avoid;
      }
      .cardBox {
        display: flex;
      }
      .cardItem {
        min-width: 150px;
        width: 12%;
        display: flex;
        flex-direction: column;
        border: 1px solid #b8b8b8;
        text-align: center;
        border-right: 0;
      }
      .cardItem1 {
        min-width: 100px;
        width: 12%;
        display: flex;
        flex-direction: column;
        border: 1px solid #b8b8b8;
        text-align: center;
        border-right: 0;
      }
      .cardItem2 {
        min-width: 30px;
        width: 4%;
        display: flex;
        flex-direction: column;
        border: 1px solid #b8b8b8;
        text-align: center;
        border-right: 1px solid #b8b8b8;
      }
      .cardItem:last-child {
        border-right: 1px solid #b8b8b8;
      }

      .item_con {
        display: flex;
        justify-content: center;
        padding: 10px;
        font-size: 14px;
        font-family: PingFangSC-Regular, PingFang SC;
        font-weight: 400;
        color: #333333;
        margin-bottom: 20px;
        text-align: left;
        line-height: 30px;
      }
      .item_text {
        padding: 10px;
        font-size: 14px;
        font-family: PingFangSC-Regular, PingFang SC;
        font-weight: 400;
        color: #333333;
        margin-bottom: 20px;
        text-align: left;
        line-height: 30px;
      }

      .item_con:nth-child(1) {
        border-bottom: 1px solid #b8b8b8;
      }

      .cardBox_second {
        display: flex;
      }
      .secondItem {
        min-width: 150px;
        width: 12%;
        display: flex;
        justify-content: center;
        flex-direction: column;
        border: 1px solid #b8b8b8;
        text-align: left;
        border-right: 0;
        border-top: 0;
        line-height: 30px;
        padding: 10px;
        font-size: 14px;
        font-family: PingFangSC-Regular, PingFang SC;
        font-weight: 400;
        color: #333333;
      }
      .secondItem:last-child {
        width: 16%;
        border-right: 1px solid #b8b8b8;
      }

      .cardBox_last {
        display: flex;
        border: 1px solid #b8b8b8;
        border-top: 0;
        border-bottom: 0;
      }
      .last_left {
        width: 37.5%;
        border-right: 1px solid #b8b8b8;
      }

      .leftItem,
      .leftItem_list {
        display: flex;
        border-bottom: 1px solid #b8b8b8;
      }
      .leftItem_list .itemCon {
        min-height: 180px;
      }

      .itemCon {
        width: 50%;
        display: flex;
        justify-content: center;
        text-align: left;
        line-height: 30px;
        padding: 10px;
        font-size: 14px;
        font-family: PingFangSC-Regular, PingFang SC;
        font-weight: 400;
        color: #333333;
      }
      .itemCon:nth-child(1) {
        border-right: 1px solid #b8b8b8;
      }

      .last_right {
        width: 62.5%;
        border-bottom: 1px solid #b8b8b8;
        text-align: left;
        line-height: 30px;
        padding: 10px;
        font-size: 14px;
        font-family: PingFangSC-Regular, PingFang SC;
        font-weight: 400;
        color: #333333;
      }
      .spinner {
        width: 60px;
        height: 60px;
        background-image: linear-gradient(0deg, #8a76f6, #70cafe);

        margin: 100px auto;
        -webkit-animation: rotateplane 1.2s infinite ease-in-out;
        animation: rotateplane 1.2s infinite ease-in-out;
      }

      @-webkit-keyframes rotateplane {
        0% {
          -webkit-transform: perspective(120px);
        }
        50% {
          -webkit-transform: perspective(120px) rotateY(180deg);
        }
        100% {
          -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg);
        }
      }

      @keyframes rotateplane {
        0% {
          transform: perspective(120px) rotateX(0deg) rotateY(0deg);
          -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg);
        }
        50% {
          transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
          -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
        }
        100% {
          transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
          -webkit-transform: perspective(120px) rotateX(-180deg)
            rotateY(-179.9deg);
        }
      }
    </style>
  </head>
  <body style="text-align: center; width: 100%; height: 100%">
    <!-- 导出的所有手卡 -->
    <div style="padding-bottom: 30px" id="card-inner">
      <div
        id="imgWrap"
        ref="imgCon"
        v-for="(cardData,index) in cardDataList"
        :key="index"
        class="card-list"
      >
        <div class="cardBox">
          <div class="cardItem">
            <div class="item_con" style="min-width: 100px; font-weight: 700">
              生产日期/保质期
            </div>
            <div class="item_text">
              <p>生产日期: <span v-html="cardData.productionDate"></span></p>
              <br />
              <p>保质期: <span v-html="cardData.shelfLife"></span></p>
            </div>
          </div>

          <div class="cardItem">
            <div class="item_con" style="font-weight: 700">利益点</div>
            <div class="item_con"><span v-html="cardData.benefit"></span></div>
          </div>

          <div class="cardItem">
            <div class="item_con" style="font-weight: 700">道具准备</div>
            <div class="item_con"><span v-html="cardData.prop"></span></div>
          </div>

          <div class="cardItem">
            <div class="item_con" style="font-weight: 700">直播间福利</div>
            <div class="item_con">
              <span v-html="cardData.broadcastBenefits"></span>
            </div>
          </div>

          <div class="cardItem">
            <div class="item_con" style="font-weight: 700">随单赠品</div>
            <div class="item_con">
              <span v-html="cardData.orderGift"></span>
            </div>
          </div>

          <div class="cardItem">
            <div class="item_con" style="font-weight: 700">直播价</div>
            <div class="item_con">
              <span v-html="cardData.priceLive"></span>
            </div>
          </div>

          <div class="cardItem">
            <div class="item_con" style="font-weight: 700">商品规格(SKU)</div>
            <div class="item_con">
              <span v-html="cardData.sku"></span>
            </div>
          </div>

          <div class="cardItem1">
            <div class="item_con" style="font-weight: 700">产品图</div>
            <div class="item_con">
              <img :src="cardData.image" style="width: 100%" alt="" />
            </div>
          </div>
          <div class="cardItem2">
            <div class="item_con" style="font-weight: 700">序号</div>
            <div class="item_con">{{index+1}}</div>
          </div>
        </div>
        <div class="cardBox_second">
          <div class="secondItem" style="font-weight: 700">发货方式</div>
          <div class="secondItem">
            <span v-html="cardData.sampleExpress"></span>
          </div>

          <div class="secondItem" style="font-weight: 700">月销</div>

          <div class="secondItem">
            <span v-html="cardData.monthlySales"></span>
          </div>

          <div class="secondItem" style="font-weight: 700">市价</div>

          <div class="secondItem">
            <span v-html="cardData.marketPrice"></span>
          </div>

          <div class="secondItem" style="font-weight: 700">对接招商</div>

          <div class="secondItem">
            <span v-html="cardData.dockingUserName"></span>
          </div>
        </div>

        <div class="cardBox_last">
          <div class="last_left" style="">
            <div class="leftItem">
              <div class="itemCon">
                <span v-html="cardData.companyName"></span>
              </div>
              <div class="itemCon">
                <span v-html="cardData.brandName"></span>
              </div>
            </div>

            <div class="leftItem">
              <div class="itemCon" style="font-weight: 700">不包邮地区</div>
              <div class="itemCon">
                <span v-html="cardData.nonShippingArea"></span>
              </div>
            </div>

            <div class="leftItem">
              <div class="itemCon" style="font-weight: 700">是否配合控库存</div>
              <div class="itemCon">
                <span v-html="cardData.isStockNum"></span>
              </div>
            </div>

            <div class="leftItem">
              <div class="itemCon" style="font-weight: 700">现货库存</div>
              <div class="itemCon" style="font-weight: 700">商品二维码</div>
            </div>

            <div class="leftItem_list">
              <div class="itemCon">
                <span v-html="cardData.spotStock"></span>
              </div>
              <div class="itemCon">
                <img
                  style="width: 90px; height: 80px"
                  :src="cardData.linkUrlQrcode"
                  alt=""
                  v-if="cardData.linkUrlQrcode"
                />
                <img
                  style="width: 90px; height: 80px"
                  :src="cardData.giftUrlQrcode"
                  alt=""
                  v-if="cardData.giftUrlQrcode"
                />
                <!-- <card-upload v-if="cardOpen"  v-model="cardData.urlQrcodeSupplement" /> -->
                <div
                  v-if="cardData.urlQrcodeSupplement"
                  style="margin-top: 10px"
                >
                  <div
                    v-for="(img, index) in cardData.urlQrcodeSupplement"
                    :key="index"
                  >
                    <img :src="img" alt="" style="width: 60px; height: 60px" />
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div class="last_right" v-html="cardData.sallingDes"></div>
        </div>
      </div>
    </div>
  </body>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
  <!-- <script type="text/javascript" src ="js/canvas2image.js"></script> -->
  <script src="https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.js"></script>
  <script src="https://cdn.bootcss.com/jspdf/1.3.4/jspdf.debug.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.10.1/qs.js"></script>

  <script>
    //获取浏览器页面可见高度和宽度
    var _PageHeight = document.documentElement.clientHeight,
      _PageWidth = document.documentElement.clientWidth;
    //计算loading框距离顶部和左部的距离(loading框的宽度为215px,高度为61px)
    var _LoadingTop = _PageHeight > 90 ? (_PageHeight - 90) / 2 : 0,
      _LoadingLeft = _PageWidth > 90 ? (_PageWidth - 90) / 2 : 0;
    //在页面未加载完毕之前显示的loading Html自定义内容
    var _LoadingHtml =
      '<div id="loadingDiv" style="position:absolute;left:0;width:100%;height:' +
      _PageHeight +
      'px;top:600;background:#FFFFFF;opacity:1.0;filter:alpha(opacity=80);z-index:10000;"><img style="margin-top:200px;" src="https://jm-talk.oss-cn-hangzhou.aliyuncs.com/static/2023-1/167324838001991.gif"/><div style="margin-top:10px;">下载中 ...</div></div></div>';
    //呈现loading效果
    document.write(_LoadingHtml);

    //监听加载状态改变
    document.onreadystatechange = completeLoading;

    //加载状态为complete时移除loading效果
    function completeLoading() {
      if (document.readyState == "complete") {
        $("#loadingDiv").fadeOut(15000);
      }
    }
    new Vue({
      el: "#card-inner",
      data: {
        cardDataList: [],
      },
      methods: {
        testGet() {
          var url = window.location.href; //获取当前链接
          let p = url.split("?")[1];
          let params = new URLSearchParams(p);
          let ids = params.get("ids");
          let URL =
            "https:www.daidu.com?ids=" + ids;
          // var idCode = url.split("="); //截取id的值
          // var ids = idCode[1].split(',') //截取id的值。即第一项
          axios({
            method: "GET",
            headers: {
              Accept: "*/*",
              "content-type": "application/x-www-form-urlencoded",
              "Access-Control-Allow-Origin": "*",
            },
            url: URL,
          }).then((res) => {
            if (res.status == 200) {
              this.cardDataList = res.data.card;
              setTimeout(()=>{
                this.jsPrintPdf();
              },1000)
              
            }
          });
        },
        //页面转canvas再转PDF
        jsPrintPdf() {
          var filename = "导出手卡.pdf";
          var element = $("#card-inner"); // 这个dom元素是要导出pdf的div容器
          var w = element.width(); // 获得该容器的宽
          var h = element.height(); // 获得该容器的高
          var offsetTop = element.offset().top; // 获得该容器到文档顶部的距离
          var offsetLeft = element.offset().left; // 获得该容器到文档最左的距离
          var canvas = document.createElement("canvas");
          var abs = 0;
          var win_i = $(window).width(); // 获得当前可视窗口的宽度(不包含滚动条)
          var win_o = window.innerWidth; // 获得当前窗口的宽度(包含滚动条)
          if (win_o > win_i) {
            abs = (win_o - win_i) / 2; // 获得滚动条长度的一半
          }
          canvas.width = w * 2; // 将画布宽&&高放大两倍
          canvas.height = h * 2;
          var context = canvas.getContext("2d");
          context.scale(2, 2);
          context.translate(-offsetLeft - abs, -offsetTop);
          // 这里默认横向没有滚动条的情况,因为offset.left(),有无滚动条的时候存在差值,因此
          // translate的时候,要把这个差值去掉
          html2canvas($("#card-inner"), {
            useCORS: true,
            dpi: window.devicePixelRatio * 2,
            scale: 2,
            width: w,
            height: h,
            pagesplit: true,
            background: "#ffffff",
            onrendered: function (canvas) {
              var contentWidth = canvas.width;
              var contentHeight = canvas.height;
              //一页pdf显示html页面生成的canvas高度;
              var pageHeight = (contentWidth / 592.28) * 841.89;
              //未生成pdf的html页面高度
              var leftHeight = contentHeight;
              //页面偏移
              var position = 0;
              //a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
              var imgWidth = 595.28;
              var imgHeight = (592.28 / contentWidth) * contentHeight;

              var pageData = canvas.toDataURL("image/jpeg", 1.0);

              var pdf = new jsPDF("", "pt", "a4");

              //有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
              //当内容未超过pdf一页显示的范围,无需分页
              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(filename);
              var pageCount = pdf.internal.getNumberOfPages()//获取当前pdf有多少页
              pdf.deletePage(pageCount)//删除最后一页
            },
          });
        },
      },
      created() {
        this.testGet();
      },
    });
  </script>
</html>

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值