以前写了一篇《前端实现图片下载》,大部分场景下,文件下载均可以按照这个思路来实现。javascript
可是,最近遇到了一个新的需求——POST 下载。服务端只支持 POST 请求,而上一篇文章中涉及的大部分场景都是 GET 请求。html
以 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 表单来实现java
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 插件 jquery.form.js API 中提供了对成功和失败的回调。纵观源码,主要实现 form 上传,可借鉴用于下载的方案并无发现对请求的状态进行监听。ios
在经历了以上插曲后,找到一种新的方案。git
在新方案中,使用了一些 HTML5 的 API,例如 Blob
。因此,兼容性须要 IE 10+ 。github
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) }
<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) }) })
segmentfault 网站编辑器编辑 markdown 对 js 代码块的解析有 bug,带了箭头函数 //
注释就失效了。axios