前端-文件上传、下载几种场景总结

211 阅读5分钟

一:直接给文件地址,无需调用接口即可下载文件

1. window.open(filePath):打开新窗口下载文件;

2. 利用a链接:

const filePath = /isoc/api/v1/abnormal/down_file?file_path=${this.fileName};

const link = document.createElement("a");

link.style.display = "none";

link.href = filePath;

link.setAttribute("download",this.fileName);

document.body.appendChild(link);

link.click();

 

a标签直接指向一个文件地址链接时,浏览器会自动下载该文件,对于单文件下载没有什么问题,但是如果需要下载多个文件的话,点击过快就会重置掉前面的请求;

3. window.location.href:同a链接,如果直接指向一个文件地址链接的话,浏览器就会直接下载该文件;href链接静态资源并且适合用于浏览器无法识别文件,如果是html、jpg、pdf等会直接解析展示,而不会下载

 

 

二:通过创建form表单方式

利用form表单提交也能够发起浏览器请求,并且也可以作为多文件下载来使用:

let params = {

  id: "xx",

  name: "xx",

};

let form = document.createElement("form");

form.id = "form";

form.name = "form";

document.body.appendChild(form);

for (let obj in params) {

  if (params.hasOwnProperty(obj)) {

    let input = document.createElement("input");

    input.type = "hidden";

    input, (name = obj);

    input.value = params[obj];

    form.appendChild(input);

  }

}

form.method = "GET";

form.action = runEnv.api_url + "请求地址";

form.submit();

document.body.removeChild(form);

 

三:会对后端发post请求,使用blob格式,后端返回文件二进制流

一般是调用导出也即文件下载接口,后端直接返回一个二进制的文件流,然后前端拿到res返回值就可直接下载文件并保存,可以基于blob格式(调用接口拿到接口返回值之后的操作就是第一种文件下载,直接利用a链接href属性到对应文件地址下载文件即可

Blob对象:不可变、原始数据的类文件对象,Blob表示的不一定是JavaScript原生格式的数据,axios的responseType设置为arraybuffer,用于接受二进制流;Angular的httpClient就需要设置responseType为blob,具体实现如下:

const params = {

   ip: ips as string[],

};

// Angular的httpClient调用接口传递的参数

this.httpClient

  .get("接口url", {

    responseType: "blob",

    params: params,

})

  .subscribe((res) => {

    console.log(res); // 此处接口返回值res就是二进制文件流

    const blob = new Blob([res]);

    let link = document.createElement("a");

link.href = window.URL. createObjectURL(blob);

link.style.display = ‘none’;

link.download = "download.zip"; // 设置文件下载名称

document.body.appendChild(link);

link.click();

document.body.removeChild(link);

    URL.revokeObjectURL(link.href);

});

 

// 基于axios调接口

axios({

  method: 'post',

  url: '/export',

  responseType: 'arraybuffer',})

.then(res => {

  // 假设 data 是返回来的二进制数据

  const data = res.data

  const url = window.URL.createObjectURL(new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}))

  const link = document.createElement('a')

  link.style.display = 'none'

  link.href = url

  const fileNames = res.headers['content-disposition'] // 获取到Content-Disposition;filename

  const regFileNames = decodeURI(fileNames.match(/=(.*)$/)[1])

  link.setAttribute('download', regFileNames) // 设置下载的文件名

  document.body.appendChild(link)

  link.click() // 自动点击a链接,即会跳转至href指向的地址下载文件

  document.body.removeChild(link)})

 

四:获取文件下载的实时进度,然后基于progress进度条展示

文件下载功能不友好,特别是文件较大时,下载消耗时间很长,需要提高用户体验感,此时就需要添加一个文件下载进度条展示;后端需要返回计算好的文件大小,否则前端计算进度时取的total永远都是0(坑),也即请求返回头里需要添加content-length

上传文件显示进度示例

postRecoveryFile() {

      if (!this.uploadFileName) return this.$message.error("请上传配置文件");

      this.disabledpostRecoveryFile = true;

      this.progressStatus = undefined; // 进度条状态

      this.uploadProgress = 0; // 进度条百分比值

      const formData = new FormData();

      formData.append("fileType", this.fileType);

      formData.append("uploadFile", this.uploadFile);

      let config = {

        headers: {

          "Content-Type": "multipart/form-data", // 上传文件为formData类型

        },

// axios调用接口直接再config中设置进度值

        onUploadProgress: (progressEvent) => {

          let persent =

           ((progressEvent.loaded / progressEvent.total) * 100) | 0; //上传进度百分比

          this.uploadProgress = persent;

        },

      };

      this.progressShow = true;

      postRecoveryFile(formData, config).then(

        (res) => {

          let { code, data, msg } = res.data;

          if (code === 200) {

            this.disabledpostRecoveryFile = false;

            this.$message.success("操作成功");

          } else {

       this.disabledpostRecoveryFile = false;

       this.progressStatus = "exception"; // 上传失败则修改进度条状态为exception

       this.$message.error(msg);

   }

          this.uploadFile = null;

          this.uploadFileName = null;

        },

        (error) => {

          this.disabledpostRecoveryFile = false;

          this.progressStatus = "exception";

        }

      );

    },

 

下载文件显示进度示例:

export const dowloadFile = (data, callback) => {

  return request({

    url: 'file/downloadFile',

    method: 'post',

    responseType: 'blob', // 基于Blob下载文件,设置返回值类型为blob

    data: data,

onDownloadProgress (progress) {

// 实时获得上传进度百分比

      callback(progress)

    }

  })}

async batchDownloadFile (id) {

      this.percentage = 0

      this.$refs.FileProgress.progressShow = true

      const res = await batchDownloadFile(id, this.processCallback)

      if (res.status !== 200) {

        this.$message.warning('操作失败')

        this.$refs.FileProgress.progressShow = false // 关闭进度条显示层

        return false

      }

      const fileNames = res.headers['content-disposition'] // 获取到Content-Disposition;filename

      const regFileNames = decodeURI(fileNames.match(/=(.*)$/)[1])

      const url = window.URL.createObjectURL(new Blob([res.data]))

      const link = document.createElement('a')

      link.style.display = 'none'

      link.href = url

      link.setAttribute('download', regFileNames)

      document.body.appendChild(link)

      link.click()

      this.$refs.FileProgress.progressShow = false // 关闭进度条显示层

 },

 processCallback (progressEvent) {

const progressBar = Math.round(progressEvent.loaded / progressEvent.total * 100)

this.percentage = progressBar

 },

 

五:利用封装的download.js的形式下载文件,会兼容各种文件上传下载形式

(function (root, factory) {

          if (typeof define === "function" && define.amd) {

          // 针对amd规范,注册一个匿名模块

          define([], factory);

          } else if (typeof exports === "object") {

          // 针对node环境,不支持严格模式

          module.exports = factory();

          } else {

          // 针对浏览器全局变量支持

          root.download = factory();

          }

      })(this, function () {

          // 第一个参数:数据,第二个参数文件名,第三个参数mime类型

          // 下载服务器上的文件直接第一个参数传入url即可,后面两个参数不用传入

          return function download(data, strFileName, strMimeType) {

          // 此处脚本仅支撑客户端

          let self = window,

              // 默认mime类型,也即后端接口返回值类型

              // 可直接是二进制流类型,然后基于blob直接下载即可

              defaultMime = "application/octet-stream",

              mimeType = strMimeType || defaultMime,

              payload = data,

              //   如果仅传入第一个参数,就将其解析为下载url

              url = !strFileName && !strMimeType && payload,

              //   创建a标签,直接利用a标签的href属性和点击事件下载文件

              anchor = document.createElement("a"),

              toString = function (a) {

              return String(a);

              },

              // 根据浏览器兼容性,设置blob即文件下载形式

              myBlob = self.Blob || self.MozBlob || self.WebKitBlob || toString,

              fileName = strFileName || "download",

              blob,

              reader;

          myBlob = myBlob.call ? myBlob.bind(self) : Blob;

 

          //   调换参数的顺序,允许download.bind(true, 'text/xml','export.xml')这种写法

          if (String(this) === "true") {

              payload = [payload, mimeType];

              mimeType = payload[0];

              payload = payload[1];

          }

 

          //   根据传入的url参数下载文件(必须同源,因为根据XMLHttpRequest)

          if (url && url.length < 2048) {

              // 如果没有文件名或者返回文件类型,就假定url是唯一的参数,直接从url中获取文件名

              fileName = url.split("/").pop().split("?")[0];

              // 设置a标签的href,也即文件下载链接地址

              anchor.href = url;

              // 避免文件下载链接不可用

              if (anchor.href.indexOf(url) !== -1) {

              // 构造XMLHtteRequest请求

              let ajax = new XMLHttpRequest();

              // get方法

              ajax.open("GET", url, true);

              // 设置相应类型,也即responseType为blob,避免浏览器直接解析出来展示

            //   即为了解决图片自动打开的问题,将响应类型改为blob,让浏览器无法识别从而避免自动打开

              ajax.responseType = "blob";

              // 调用下载接口的回调函数

              ajax.onload = function (e) {

                  // 再次调用自身,相当于递归,把xhr返回的blob数据生成对应的文件

                  // 也即利用接口返回的文件二进制流数据生成对应的blob文件并下载

                  download(e.target.response, fileName, defaultMime);

              };

              //   发送请求

              setTimeout(() => {

                  ajax.send();

              }, 0);

              return ajax;

              }

          }

 

          //   如果是dataUrl,则直接生成文件

          //   兼容低版本的ie浏览器

          if (/^data:[\w+-]+/[\w+-]+[,;]/.test(payload)) {

              // 如果满足条件(大于2M,并且myBlob!==toString,直接通过dataUrlBlob生成文件)

              if (payload.length > 1024 * 1024 * 1.999 && myBlob !== toString) {

              payload = dataUrlToBlob(payload);

              mimeType = payload.type || defaultMime;

              } else {

              //  如果是ie,走navigator.msSaveBlob

              return navigator.msSaveBlob // IE10 can't do a[download], only Blobs:

                  ? navigator.msSaveBlob(dataUrlToBlob(payload), fileName)

                  : //  否则走saver方法

                  saver(payload); // everyone else can save dataURLs un-processed

              }

          }

          blob =

              payload instanceof myBlob

              ? payload

              : new myBlob([payload], { type: mimeType });

 

          //  根据传入的dataurl,通过myBlob生成文件

          function dataUrlToBlob(strUrl) {

              var parts= strUrl.split(/[:;,]/),

              type= parts[1],

              decoder= parts[2] == "base64" ? atob : decodeURIComponent,

              binData= decoder( parts.pop() ),

              mx= binData.length,

              i= 0,

              uiArr= new Uint8Array(mx);;

              for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);​

              return new myBlob([uiArr], {type: type});

          }

 

           //  winMode 是否是在window上调用

        function saver(url, winMode){

            //  如果支持download标签,通过a标签的download来下载

            if ('download' in anchor) { //html5 A[download]

                anchor.href = url;

                anchor.setAttribute("download", fileName);

                anchor.className = "download-js-link";

                anchor.innerHTML = "downloading...";

                anchor.style.display = "none";

                document.body.appendChild(anchor);

                setTimeout(function() {

                    //  模拟点击下载

                    anchor.click();

                    document.body.removeChild(anchor);

                    //  如果在window下,还需要解除url跟文件的链接

                    if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}

                }, 66);

                return true;

            }​

            // handle non-a[download] safari as best we can:

            //  针对不支持download的safari浏览器,走window.open的降级操作,优化体验

            if(/(Version)/(\d+).(\d+)(?:.(\d+))?.*Safari//.test(navigator.userAgent)) {

                url=url.replace(/^data:([\w/-+]+)/, defaultMime);

                if(!window.open(url)){ // popup blocked, offer direct download:

                    if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }

                }

                return true;

            }​

            //do iframe dataURL download (old ch+FF):

            //  针对老的chrome或者firefox浏览器,创建iframe,通过设置iframe的url来达成下载的目的

            var f = document.createElement("iframe");

            document.body.appendChild(f);​

            if(!winMode){ // force a mime that will download:

                url="data:"+url.replace(/^data:([\w/-+]+)/, defaultMime);

            }

            f.src=url;

            //  移除工具节点

            setTimeout(function(){ document.body.removeChild(f); }, 333);

          };}

 

          //  针对ie10+ 走浏览器自带的msSaveBlob

        if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)

            return navigator.msSaveBlob(blob, fileName);

        }​

        //  如果全局对象下支持URL方法

        if(self.URL){ // simple fast and modern way using Blob and URL:

        //  根据blob创建指向文件的ObjectURL

            saver(self.URL.createObjectURL(blob), true);

        }else{

            // handle non-Blob()+non-URL browsers:

            //  针对不支持Blob和URL的浏览器,通过给saver传入dataUrl来保存文件

            if(typeof blob === "string" || blob.constructor===toString ){

                try{

                    return saver( "data:" +  mimeType   + ";base64,"  +  self.btoa(blob)  );

                }catch(y){

                    return saver( "data:" +  mimeType   + "," + encodeURIComponent(blob)  );

                }

            }​

            // Blob but not URL support:

            //  支持Blob但是不支持URL方法的浏览器,通过构造文件阅读器来保存文件

            reader=new FileReader();

            reader.onload=function(e){

                saver(this.result);

            };

            reader.readAsDataURL(blob);

        }

        return true;

      })();

 

 /*

      a标签下载体验效果最佳,针对图片资源等文件,同源时,使用download可获得较好的效果,window.open会打开新的tab页,页面存在跳转

      使用构造表单提交,存在浏览器会直接解析文件的问题

      */

 

 

总结:

如果给定地址链接过去可直接下载,不需要传递任何参数即可;

若需要调接口传递参数,利用接口返回值下载文件时,前端传递的参数一般都是formData类型,并且请求头参数(主要是reponseType)需要根据后端返回参数类型进行设置,