4

之前写了一篇《前端实现图片下载》大部分场景下,文件下载都可以按照这个思路来实现。

但是,最近遇到了一个新的需求——POST 下载。服务端只支持 POST 请求,而上一篇文章中涉及的大部分场景都是 GET 请求。

服务端实现

以 Node + Koa2 实现为例,服务端返回 excel 文件流

const fs = require('fs')
const path = require('path')

module.exports = ctx => {
  ctx.set('Content-Type', 'application/vnd.ms-excel')
  ctx.set('Content-Disposition', 'attachment; filename=download.xlsx')
  ctx.body = fs.createReadStream(path.resolve(__dirname, './download.xlsx'))
}

兼容性好的老方案

经典的、兼容性好的方案可以通过构建 Form 表单来实现

let uuidIndex = 0

export default (url, params, method = 'post') => {
  const uuid = `TMP_FRAME_NAME__${uuidIndex++}`
  const iframe = document.createElement('iframe')
  iframe.name = uuid
  iframe.style = 'display:none'
  // 无论响应成功失败,都会调用 onload
  // iframe.onload = success
  // iframe.onerror = fail
  document.body.appendChild(iframe)

  const form = document.createElement('form')
  form.action = url
  form.method = method
  form.target = uuid
  form.style = 'display:none'
  form.enctype = 'application/x-www-form-urlencoded'
  document.body.appendChild(form)

  if (params) {
    Object.keys(params).forEach(key => {
      const v = params[key]
      if (v !== undefined) {
        const input = document.createElement('input')
        input.type = 'hidden'
        input.name = key
        input.value = v
        form.appendChild(input)
      }
    })
  }

  form.submit()
  document.body.removeChild(form)
  document.body.removeChild(iframe)
}

寻找新方案

产品提了一个需求,下载成功要提示,下载失败也要提示。那么问题来了,上面的老方案,不太好监听这次操作是正常还是异常。(其实可以和后台约定返回内容,前端通过监听 iframe 的内容实现监听。)

插曲

有个 jQuery 插件 jquery.form.js API 中提供了对成功和失败的回调。纵观源码,主要实现 form 上传,可借鉴用于下载的方案并没有发现对请求的状态进行监听。

正题

在经历了以上插曲后,找到一种新的方案。

在新方案中,使用了一些 HTML5 的 API,例如 Blob。所以,兼容性需要 IE 10+ 。

function download(url, name) {
  const aLink = document.createElement('a')
  aLink.download = name
  aLink.href = url
  aLink.dispatchEvent(new MouseEvent('click', {}))
}

export default (data, name, type) => {
  const blob = new Blob([data], { type })
  const url = URL.createObjectURL(blob)
  download(url, name)
}

demo 一枚

<button id="button">下载</button>
import axios from 'axios'
// 上面的新方案
import download from './download' 

document.getElementById('button').addEventListener('click', () => {
  axios.post('/download', null, {
    // 记得设置为成以 buffer 格式读取
    responseType: 'arraybuffer'
  })
  .then((res) => {
    // 从响应头里面读取名字,当然,可以自定义
    const name = res.headers['content-disposition'].replace(/.*filename=/, '')
    download(res.data, name, 'application/vnd.ms-excel')
  })
  .catch((error) => {
    console.log(error)
  })
})

限制

做了一个前端导出 excel 的功能,但受限于 url 长度。

吐槽

segmentfault 网站编辑器编辑 markdown 对 js 代码块的解析有 bug,带了箭头函数 // 注释就失效了。


siwuxie
528 声望67 粉丝

404