总结:下载文件的方式

  1. a 标签配置 download 属性 (适用于下载一些静态资源)
  2. window.location.href (适用于下载一些静态资源)
  3. a 标签 + Blob 方式
  4. 使用 form 表单提交

1.a 标签配置 download 属性

<a href="test.zip" download="test.zip">download</a>

 download 用来命名下载文件,以及防止 txt , jpg , pdf 等浏览器支持直接打开的文件不能下载(需要注意的是,href地址不能写完整的域名,否则只能预览也不能下载。另外,下载的地址域名和访问网站的域名必须是同源,否则 download 设置无效)。

2.window.location.href/window.open()

window.location.href = '你的 url';
// 或 
window.open('你的 url');

这种方式比较常见,比如我们在一些网站上下载电子书,安装包之类的,一般都是这种方式或 a 标签方式下载的。

3.<a> 标签 + Blob方式

1.下载 api

export function downloadApi() {
  return request({
    url: '/testApi',
    method: 'get',
    responseType: 'blob'
  })
}

注:这里的 request 是用 axios 封装的一个方法,详情可见:vue开发中 axios 的封装 

2.项目下载操作代码 

// vue 代码
    downClick () {
      downloadApi().then(resBlob => {
        this._download(resBlob)
      })
    },
    _downloads (resBlob) {
      const projectName = '项目.zip'
      // IE|Edge
      if (window.navigator && window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveBlob(resBlob, projectName)
      } else {
        // 其他浏览器
        const blobUrl = window.URL.createObjectURL(resBlob)
        const a = document.createElement('a')
        a.style.display = 'none'
        a.download = projectName
        a.href = blobUrl
        document.body.appendChild(a) // 兼容火狐
        a.click()
        document.body.removeChild(a)
        window.URL.revokeObjectURL(blobUrl)
      }
    },

这里我们使用了 a 标签加 blob 的方式,这种方式适用于下载响应比较耗时的情况。它会提前将文件下载完成后,再由前端进行下载。比如,后端在下载前需要进行一系列耗时操作来生成下载文件,这会让用户误以为没有正确点击下载而重复点击下载按钮。通过这种方式我们可以做一些loading 之类的处理。

当然,如果不需要 loading 之类的处理,可以直接通过 a 标签点击下载,而无需 blob 方式。

注意:

上面这个 blob 方式在我们下载失败时仍然会下载文件,当我们要处理后端抛出的错误信息时,就行不通了。解决方法两种:

3.1. 使用 Response(IE不支持)

downClick () {
      downloadApi().then(async resBlob => {
        let resObj = {}
        if (resBlob.size == 0) {
          // 没有数据可导出
        } else if (resBlob.type == 'application/json') {
          // 返回(错误)信息
          resObj = await (new Response(resBlob)).text()
          // 得到错误信息
          resObj = JSON.parse(resObj)
        }
        // ... 相关判断处理
        this._download(resBlob)
      })
    },

3.2. 使用 FileReader

downClick () {
      downloadApi().then(resBlob => {
        let resObj = {}
        if (resBlob.size == 0) {
          // 没有数据可导出
        } else if (resBlob.type == 'application/json') {
          // 返回(错误)信息
          const reader = new FileReader()
          reader.addEventListener('loadend', () => {
            // reader.result 包含被转化为一个字符串内容的 blob
            resObj = JSON.parse(reader.result)
            if (/* 是错误信息 */) {
              // 相关判断处理
            }
          })
          reader.readAsText(resBlob)
        } else {
          this._download(resBlob)
        }
      })
    },

4.使用 form 表单提交

/**
   * 使用 form-data -post 方式导出文件
   * @param url 导出地址
   * @param params 参数
   */
  const postFormDataFile = (url, params) => {
    const form = document.createElement('form')
    form.style.display = 'none'
    form.action = BASE_URL + url
    form.method = 'post'
    document.body.appendChild(form)
    // 动态创建 input 并给 value 赋值
    for (const key in params) {
      const input = document.createElement('input')
      input.type = 'hidden'
      input.name = key
      input.value = params[key]
      form.appendChild(input)
    }
    form.submit()
    form.remove()
  }

注意:传给后端的参数不是 json 对象的形式,而是 currentPage=2&pageSize=20 形式。

5.实现下载进度条

原理:设置 onDownloadProgress 事件

export function downloadProject(projectId) {
  return request({
    url: downloadProjectUrl + projectId,
    method: 'get',
    responseType: 'blob',
    onDownloadProgress: function (event) {
      // 注意: 后端需要设置响应头 Content-Length, 否则 event.lengthComputable 就会为 false, event.total 为 0
      // event.loaded 得到当前已加载的数据大小, event.total 拿到总的数据大小, 通过两者的比值就知道其进度。
      console.log('event', event)
    }
  })
}

event 参数内容如下:

前端下载文件 response为文本_前端下载文件 response为文本