文件下载
从浏览器下载文件是一件很正常的操作。我们通常会处理两种情况的文件。一种是服务器发送未生成文件的文件流,另一种是下载已经生成的文件。
我们可以通过传入不同的参数来决定哪一种情况的下载。
/** * 下载文件 * @param urlOrFilename * @param content * @param mime */export default function downloadFile( urlOrFilename: string, content?: BlobPart, mime?: string, bom?: string) { // 如果传递第二个参数,走下载流,否则直接请求 url if (content) { downloadContent(content, urlOrFilename, mime, bom) } else { downloadUrl(urlOrFilename) }}
通过文件流下载文件
通过文件流下载的代码使用了 file-download 库。该库对各种浏览器进行了兼容。
/** * https://github.com/kennethjiang/js-file-download/blob/master/file-download.js * @param filename * @param content * @param mime */function downloadContent(data: string | ArrayBuffer | ArrayBufferView | Blob, filename: string, mime?: string, bom?: string) { // 如果当前 bom 为 null, IE 11 报错 const blobData = (typeof bom !== 'undefined') ? [bom, data] : [data] const blob = new Blob(blobData, { // 当前没有指定类型,直接使用任意格式 type: mime || 'application/octet-stream' });
// IE9 以上的 IE 浏览器都会报一个 request URI too large 的错误, 直接使用 msSaveBlob if (typeof window.navigator.msSaveBlob !== 'undefined') { window.navigator.msSaveBlob(blob, filename); } else { // 兼容 webkitURL const blobURL = (window.URL && window.URL.createObjectURL) ? window.URL.createObjectURL(blob) : window.webkitURL.createObjectURL(blob);
const tempLink = document.createElement('a'); tempLink.style.display = 'none'; tempLink.href = blobURL; tempLink.setAttribute('download', filename);
// 当前浏览器不支持 h5 download,打开 if (typeof tempLink.download === 'undefined') { tempLink.setAttribute('target', '_blank'); }
document.body.appendChild(tempLink); tempLink.click();
// 修复移动端 safari 浏览器下载 bug "webkit blob resource error 1" setTimeout(function () { document.body.removeChild(tempLink); // 释放 url 对象 window.URL.revokeObjectURL(blobURL); }, 200) }}
下载已生成文件
早期的文件下载通过 window.open 携带参数打开新的浏览器标签页,通过浏览器来决定如何处理当前文件(展示或者下载),但是弹出标签页非常影响用户体验。
然后开发者通过创建具有 download 属性的 a 标签进行下载。该方案针对图片等会发生直接跳转且无法实现跨域。
const url = 'fileApi/AccountTemplate.xlsx'const link = document.createElement('a')link.style.display = 'none'link.href = urllink.setAttribute( 'download', '导入学生账号模板')document.body.appendChild(link)link.click()
最佳的方案是 iframe ,因为可以直接无感知下载文件。
function downloadUrl(url: string) { let el: any = document.getElementById('iframeForDownload') as HTMLElement if (!el) { el = document.createElement('iframe') el.id = 'iframeForDownload' el.style.width = 0 el.style.height = 0 el.style.position = 'absolute' document.body.appendChild(el) } el.src = url}
但很可惜,出于沙盒安全性考虑,83 版本的 chrome 浏览器默认禁止了 iframe 嵌套页面。所以我们不但需要对当前代码进行修改,还需要对响应头进行修改。