之前写了一篇《前端实现图片下载》,大部分场景下,文件下载都可以按照这个思路来实现。
但是,最近遇到了一个新的需求——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,带了箭头函数 //
注释就失效了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。