前端实现导出(下载)文件功能

1,668 阅读4分钟

image.png

前端开发工作中,偶尔会需要实现导出文件的功能,这个看似很简单的一个按钮点击事件的功能,如果没有理解透,每次遇上也只能上度娘搜各种解决方案,一旦遇上后端返回数据格式变了,又得各种搜,好像会了又好像不会。

今天决定把导出(下载)各种可能遇到的情况都一一记录一下,深入了解一下文件导出。

前端导出excel, 一般分为三种, 第一种是后端直接返回链接,第二种是后端返回二进制数据流, 第三种是纯前端生成(如导出一个模板)。 而导出的方式可以使用form表单提交, a标签模拟点击下载, window.open打开新窗口. 最常用的应该就是a标签了。

前端在开始实现导出功能时,我们需要先跟后端确定,后端将会以什么形式返回,我们再针对返回的方式选择合适的方案。

一、后端返回链接的导出

  • 后端返回包含域名的完整链接 这种导出前端是最省心的,直接设置 window.location.href 为完整的链接地址就可以

  • 后端返回不包含域名的链接 这时候需要在链接前拼接请求域名

前端通常需要把后端请求的域名配置在静态资源public文件夹的config.js文件中,前端项目打包时会把public静态文件夹原封不动的输出,这样对于生产环境,运维就可以直接在里面配置后端的域名。前端可以在配置文件中写一个配置的示例,便于运维人员配置。

/*
* public/config.js 配置文件
* 配置示例:
*   baseUrl: 'http://xxx/xxx/'
*/

window.apiConfig = {
    baseUrl: '后端域名'
}

配置后需要在index.html中引用这个config.js文件,这样就可以在全局获取apiConfig这个变量的值

image.png

window.apiConfig.baseUrl    // 配置的后端域名

二、后端返回二进制数据(文件流)的导出

异步请求配置

当后端告知我们,导出接口给我们返回二进制文件流时,我们前端需要手动设置一下异步请求的responseType(响应类型)为blob,其他的设置与普通异步请求没有什么区别。

image.png

我们前端调用了导出接口后,在控制台 Preview 所看到的一堆乱码, 就是二进制文件流。

数据处理

res 是我们从后台返回的数据,我们把 res 放到数组里,通过 new Blob 构造函数处理二进制数据,创建一个新的Blob对象(生成一个文件),再使用URL.createObjectURL() 方法创建一个URL,并且模拟a标签点击事件实现导出。

// blob可以解决数据量大导致网络失败。
let url = window.URL.createObjectURL(new Blob([res])); // 这里可以使用'\ufeff' 解决乱码问题,new Blob(['\ufeff' + res.data]), 
let link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.setAttribute("download", "excel.xlsx");
document.body.appendChild(link);
link.click();

何为Blob?

Blob是一个类文件对象,它的数据可以按文本或二进制的格式进行读取,也可以转换成ReadableStream来用于数据操作 Blob构造函数:返回一个新的Blob对象。

var  aBlob = new Blob(array, options)
// array 是由ArrayBuffer、ArrayBufferView、Blob、DOMString等对象构成的数组
var aBlobSize = aBlob.size   // 获取blob对象的大小(字节)

File接口基于Blob,继承了blob的功能,提供有关文件的信息,并允许网页中的Javascript访问其内容。

通常情况下,File对象来自以下三种操作返回:

  1. 是用户通过<input>元素选取文件后返回的FileList对象
  2. 通过拖放操作生成的DataTransfer对象
  3. 来自HTMLCanvasElement上的mozGetAsFile() API

File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。比如说, FileReader, URL.createObjectURL(), createImageBitmap() (en-US), 及 XMLHttpRequest.send() 都能处理 Blob 和 File。

回到正题。此处导出使用了 URL.createObjectURL() 方法,创建一个 DOMString(一个新的 URL 对象)。当不再需要这些URL对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放内存,以便获得最佳性能和内存使用。

三、纯前端生成EXcel

一般用于json格式数据导出模板

      let str = "";
      const jsonData = [
        { 仓库代码: "", 货号: "", 尺码: "", 条码: "", 切货数量: "", 折扣: "" },
      ];
      console.log(jsonData);
      for (var k in jsonData[0]) {
        str += k + ",";
      }
      str = str.slice(0, str.length - 1) + "\n";
      console.log(str);
      // 增加\t为了不让表格显示科学计数法或者其他格式
      for (let i = 0; i < jsonData.length; i++) {
        for (const item in jsonData[i]) {
          str += `${jsonData[i][item] + "\t"},`;
        }
        str += "\n";
      }
      // encodeURIComponent解决中文乱码
      const uri =
        "data:application/vnd.ms-excel;charset=utf-8,\ufeff" +
        encodeURIComponent(str);
      // 通过创建a标签实现
      const link = document.createElement("a");
      link.href = uri;
      // 对下载的文件命名
      link.download = "订单模板.xls";
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);

四、直接使用导出接口下载,无需处理后端返回数据后再下载

前面两种情况都是需要先请求接口,得到接口返回的数据再实现下载。此处是直接在导出接口地址上加参数直接打开下载

window.location.href = '导出接口地址'?user='tom'&id='001'

上面的请求方法相当get方法,但是当请求导出接口时候参数太多了,使用get方法请求会导致参数缺少。这时候就想办法用post方法请求。

由于这种场景是直接打开导出接口地址下载,有点不好用post方法,那么这时候就要借助HTML<form> 标签和 DOM Form 对象来解决。我们需要封装一个组件来实现。(未完待续...)