当被要求「纯前端实现报告下载」...(纯前端用VUE实现PDF/WORD下载)

1,282 阅读3分钟

⭐️ 前因

事情是这样的。后端人手紧缺。领导想要在前端实现报告下载。报告里面有表格就算了,还有Echart图表。所以我本能反应就是「怎么可能😱😱😱」。

后面翻了下,还真的可以。

✅ 你将收获

通过本篇文章,你讲学会

  1. 使用html2Canvas、JsPDF实现界面截图导出PDF
  2. 使用html-docx-js-typescript实现指定Dom转Word
  3. 设置模板变量,制作word模板,纯前端导出Word

废话不多说,上代码。让你们舒舒服服的躺平✌🏻

备注:以下示例均是在vue项目下的。

方法一:巧用html2Canvas、JsPDF实现界面截图导出PDF

效果图

先放上一拨效果图。这是我写的网页👉🏻

image.png 这是下载后的效果👉🏻

WechatIMG274.png

核心代码

  html2Canvas(ele, {
    allowTaint: true,
  }).then((canvas) => {
    const PDF = new JsPDF("p", "mm", "a4"); // A4纸,纵向
    const ctx = canvas.getContext("2d");
    const a4w = 190;
    const a4h = 277; // A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
    // eslint-disable-next-line no-mixed-operators
    const imgHeight = Math.floor((a4h * canvas.width) / a4w); // 按A4显示比例换算一页图像的像素高度
    let renderedHeight = 0;

    while (renderedHeight < canvas.height) {
      const page = document.createElement("canvas");
      page.width = canvas.width;
      page.height = Math.min(imgHeight, canvas.height - renderedHeight); // 可能内容不足一页
      // 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
      page
        .getContext("2d")
        .putImageData(
          ctx.getImageData(
            0,
            renderedHeight,
            canvas.width,
            Math.min(imgHeight, canvas.height - renderedHeight)
          ),
          0,
          0
        );
      // 添加图像到页面,保留10mm边距
      // eslint-disable-next-line no-mixed-operators
      PDF.addImage(
        page.toDataURL("image/jpeg", 1.0),
        "JPEG",
        10,
        10,
        a4w,
        Math.min(a4h, (a4w * page.height) / page.width)
      );

      renderedHeight += imgHeight;
      if (renderedHeight < canvas.height) {
        PDF.addPage();
      } // 如果后面还有内容,添加一个空页
    }
    this.downloadStatus = false;
    PDF.save(`${title}.pdf`);
  });

总结

利用类似截图的原理,所以页面上长啥样,下载下来后就是啥样。

备注:Echart图表不行。可以试着调用echart的getDataURL api。 将它转成图片。然后插入到页面中,再截图,就可以了。

下载后的PDF文档其实是长图切割后塞到PDF里的。所以没办法编辑。

SO... 重点来了,方法二让你轻轻松松导出word, 让你想怎么编辑就怎么编辑

方法二:使用html-docx-js-typescript实现指定Dom转Word

转折点来了

本来按照方法一都开发好了,感觉好不容易可以休息一会儿的时候

但但但是,产品来了句话,「客户想要下载WORD,因为他想编辑」

我:「...白干了?」 (此处有黑人问号脸)

所以就有了这个法二!!!

上效果图(此处实操了echart转图片)

注意: 页面上多了Echat图表 image.png

下载后的Word

image.png

你们想要的echat图表转图片来了!! image.png

核心代码

let insertChartImg = ((chart, imageDom) => { //把echat转成图片
    let baseImage = this.$refs[chart].getDataURL({
        type: 'png',
        pixelRatio: 1,
        backgroundColor: '#0e3166'
    });
    let imgEl = document.createElement('img')
    imgEl.src = baseImage
    imgEl.style.width = '500px'
    imgEl.style.height = '300px'
    imgEl.style.backgroundColor = '#0e3166'
    imgEl.id = imageDom
    document.querySelector(`#${chart}`).appendChild(imgEl)
 })
 insertChartImg('chart1', 'baseImage1')
 var bodyString=ele.innerHTML; //需要专门为下载下来的word写样式,因为它只能识别指定dom的style,class没效果
  const htmlString =`<!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Document</title>
            <style>
            body{
                color: #333;
                margin-bottom: 40px;
                padding-bottom: 40px; //样式定义在这,太长了。这边就省略了
            }
            </style>
        </head>
        <body>
        ${bodyString}
        </body>
        </html>
      ` 
      asBlob(htmlString,{
        // orientation:  'landscape'
      }).then(data => { 
          saveAs(data, '玛丽有只小羊羔的简介.docx') // save as docx file
          document.querySelector('#baseImage1') && document.querySelector('#baseImage1').remove()
          this.downloadStatus = false;
      })

方法三:设置模板变量,制作word模板,纯前端导出Word

效果图

首先你需要制作一个Word模板 image.png

然后将值传给传给word模板,下载下来就可以实现这个效果了

image.png

方法三是参考这篇文章的。TA写的挺详情,亲测可用。我就一并放在代码里了。

原文👉🏻:www.jianshu.com/p/b3622d6f8…

核心代码

asBlob(htmlString, opt).then(data => { 
    imageData = data

    // 设置模板变量的值
    doc.setData({
      ..._this.form,
      table: _this.tableData,
      checkReason: imageData
    });

    try {
      // 用模板变量的值替换所有模板变量
      doc.render();
    } catch (error) {
      // 抛出异常
      let e = {
        message: error.message,
        name: error.name,
        stack: error.stack,
        properties: error.properties
      };
      console.log(JSON.stringify({ error: e }));
      throw error;
    }

    // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
    let out = doc.getZip().generate({
      type: "blob",
      mimeType:
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
    });
    // 将目标文件对象保存为目标类型的文件,并命名
    saveAs(out, "报价单.docx");
  })

⭐️ 写在最后

因为一开始的模糊沟通,导致这个调研了好多方法,参考了市面很多文章。

好在,效果还是不错的

除了Echat是转成图片放到文档里,导致不能编辑外。其它还是很不错的。

但纯前端应该是实现不了下载echart的。后端能实现并且还是收费的第三方服务。

所以这应该是最全的纯前端转WORD/PDF的文章了。而且还给你们源码地址了, 还不赶紧马住!!!

附上代码链接: vueexport

三个方法都在这个项目里了,有需要的下载自行查看!!

PS: (如果你们这样简单纯粹的风格,请点👍🏻。让我有动力持续的输出优质文章。你们就负责躺平就好了)

好啦,有问题的话评论区提问哦

⭐️ 往期精彩