以前寫了一篇《前端實現圖片下載》,大部分場景下,文件下載均可以按照這個思路來實現。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