Bootstrap

【Vue2】vue指定页面div保存为pdf、vue指定页面内容转为文件流上传到后端、js指定div页面转为pdf文件


前言

参考文章:实测可行、但是需要修改
前端vue的JsPDF html2canvas 生成pdf并以文件流形式上传到后端

项目背景:vue2
框架:若依
需求:把页面上某一块内容(或者整个页面),转为pdf文件上传到后端、或者下载到本地


提示:以下是本篇文章正文内容,下面案例可供参考

一、安装插件

npm i html2canvas --save
npm i jspdf --save

二、使用步骤

1. utils/htmlToPdf.js

代码如下(示例):

需要修改id为你的页面上内容的id
我这里是#printer

import html2Canvas from 'html2canvas';
import JsPDF from 'jspdf';
export default {
  install(Vue, options) {
    /**
     *
     * @param {*} reportName 下载时候的标题
     * @param {*} isDownload  是否下载默认为下载,传false不下载
     */
    Vue.prototype.getPdf = function (reportName, isDownload = true) {
      //     var target = document.getElementsByClassName("right-aside")[0];
      // target.style.background = "#FFFFFF";
      return new Promise((resolve, reject) => {
        var title = reportName;
        html2Canvas(document.querySelector('#printer'), {
          allowTaint: true,
        }).then((canvas) => {
          let contentWidth = canvas.width;
          let contentHeight = canvas.height;
          //一页pdf显示html页面生成的canvas高度;
          let pageHeight = (contentWidth / 592.28) * 841.89;
          //未生成pdf的html页面高度
          let leftHeight = contentHeight;
          //页面偏移
          let position = 0;
          //a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
          let imgWidth = 595.28;
          let imgHeight = (592.28 / contentWidth) * contentHeight;
          let pageData = canvas.toDataURL('image/jpeg', 1.0);
          let 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();
              }
            }
          }
          if (isDownload) {
            PDF.save(title + '.pdf');
          }
          // 删除本地存储的base64字段
          var pdfData = PDF.output('datauristring'); //获取base64Pdf
          resolve(pdfData);
        });
      });
    };
  },
};

2. main.js

代码如下(示例):

import htmlToPdf from '@/utils/htmlToPdf'; // 下载+上传
Vue.use(htmlToPdf);

3. utils/base64.js

这个是base64转blob,不是base64加密

// 转换为 Blob 对象的方法 (可复用)
export function base64ToBlob(base64Data) {
    const parts = base64Data.split(";base64,");
    const contentType = parts[0].split(":")[1];
    const raw = window.atob(parts[1]);
    const rawLength = raw.length;
    const uInt8Array = new Uint8Array(rawLength);
    for (let i = 0; i < rawLength; ++i) {
        uInt8Array[i] = raw.charCodeAt(i);
    }
    return new Blob([uInt8Array], { type: contentType });
}

// 从 base64 数据中获取文件名和 mime 类型的方法 (可复用)
export function getFilenameAndMimetypeFromBase64(base64Data) {
    const fileInfo = base64Data.split(';base64,')[0].substring(5).split(':');
    const mimeType = fileInfo[0];
    const filename = fileInfo[1];
    return [filename, mimeType];
}

4. 上传方法

只是api方法示例,基本上vue框架,都会封装request.js,如果看不懂,可以翻原文

import request from '@/utils/request';

// 上传签名图片 || 上传手写意见图片
export function uploadSign(data) {
  return request({
    url: '/flow/common/upload',
    method: 'post',
    headers: {
      'Content-Type': 'multipart/form-data',
    },
    data: data,
  });
}

在这里插入图片描述

5. vue页面内容

我这里需要可复用性,所以关键性都在utils中,

5.1 引入方法

import { uploadSign } from '@/api/flow/common.js';
import { base64ToBlob, getFilenameAndMimetypeFromBase64 } from '@/utils/base64.js';

// getFilenameAndMimetypeFromBase64 可以不用
// 因为我指定了上传文件是pdf,所以不需要去查找是什么类型

5.2 页面内容div

需要idutils/htmlToPdf.js中的对应

<el-button type="primary" icon="el-icon-printer" @click="toGetPdf(0)">只下载</el-button>
<el-button type="warning" icon="el-icon-printer" @click="toGetPdf(1, 0)">只上传</el-button>
<div id="printer" class="table">
...
</div>

5.3 js方法

// 保存方法
toGetPdf(val = false, download = true) {
  /**
 * val 决定走不走上传接口,默认为不上传给后端
 * download 默认是下载
 * /
  /* */
  this.$nextTick(() => {
    setTimeout(() => {
      window.scrollTo(0, 0); //这行代码很重要,它让页面的滚动条跳到了最上方如果点击打印按钮的时候,
      // 滚动条没有在最上方,打印内容会是不完整的,体验也会差
      let title = '个人报告';
      this.getPdf(title, download) //download:false为不下载,这里调用了刚刚引用的全局函数,
      // .then得到的值是base64位的pdf文件
        .then((res) => {
          if (val) {
            console.log('准备上传');
            this.UploadPdf(res);
          } else {
            console.log('不上传');
          }
        });
    }, 1000);
  });
},
//上传pdf接口
UploadPdf(res) {
  //res拿到base64的pdf
  let pdfBase64Str = res;
  let filename = '测试.pdf';
  // 将 base64 编码的文件数据转为 Blob 对象
  const blob = base64ToBlob(pdfBase64Str);
  // 获取文件名和 mime 类型
  // const [filename, mimeType] = getFilenameAndMimetypeFromBase64(res);
  // 将 Blob 对象转换为 File 对象
  const file = new File([blob], filename, { type: '.pdf' }); // type类型对应的是mimeType
  // 上传文件
  const formData = new FormData();
  formData.append('file', file);
  formData.append('name', filename);
  //该uploadMy为接口,直接以formdata格式传给后台
  uploadSign(formData)  // 改为你自己的上传接口
    .then((res) => {
      console.log('上传pdf接口', res);
    })
    .catch((err) => {
      console.log('上传pdf接口', err);
    });
},

注释事项

  1. htmlToPdf.js中的打印对应id,改成你的页面内容的。你也可以改写为变量传入
  2. 上传方法是request.js,一定要加headers类型
    ‘Content-Type’: ‘multipart/form-data’,
  3. 点击方法传参,两个都是boolean类型,用1和0是因为隐式转换
  4. formData.append(‘name’, filename)是把名称传给后端

总结

如有不懂,请看原文。
我这里是基于原文实现,并且修改部分内容。

js指定页面内容另存为pdf文件
前端vue把页面转为file文件流上传到后端

;