在后台管理系统中,我们经常会遇到文件导出这个需求,下面,我将几种常见的导出方式做一个简单的介绍,让大家在以后遇到此类需求时,能够切合实际情况,采取相对合理的方式。
导出目标
文件地址
已经存在服务器上的静态文件,比如用户上传的图片、材料等等。http://192.168.1.103:3000/imgs/bg.jpg
导出接口
根据用户需求,由后端动态生成的文件,常见的比如导出业务流水表格,数据汇总表格等等。http://192.168.1.103:3000/api/export
导出方式
a.download
html5
新增的属性
优点:简单。
缺点:ie
不支持,并且在跨域时,即使后台设置了允许跨域的响应头,也无法下载,也就是说必须与当前域一致。
// 这里的http://192.168.1.103:3000必须与当前地址栏一致才行
<a href="http://192.168.1.103:3000/imgs/xx.jpg" download />
download
属性可以指定文件名(如果content-disposition
指定了文件名,以content-disposition
为准),也可以为空值,两种情况具体如下:
ajax + a.download
缺点:ie
不支持,blob
有内存限制。
优点:避免了单独使用a.download
必须与域名一致的问题。
将后台返回的二进制数据,转换成blob
,然后利用URL.createObjectURL
,创建一个指向内存中blob
的URL
,再使用a
标签的download
属性进行导出
function useLinkDownload(url, fileName) {const link = document.createElement("a");link.style.display = "none";link.href = url;link.download = fileName;document.body.appendChild(link);link.click();document.body.removeChild(link);
}$.get('http://192.168.1.103:3000/api/export', { responseType: 'blob' }).then(function(res) {// 创建一个指向内存中blob的URLconst objectURL = URL.createObjectURL(res.data);useLinkDownload(objectURL, 'xx.xlsx')URL.revokeObjectURL(objectURL)})
ajax + msSaveBlob
优点:支持ie
,这算优点?
缺点:chrome、firefox
不支持,blob
有内存限制
$.get('http://192.168.1.103:3000/api/export', { responseType: 'blob' }).then(function(res) {navigator.msSaveBlob(res.data, "xx.xlsx");})
content-disposition
优点:兼容性极好。
缺点:需要后台支持。
前端发送请求即可(其他的交给后台),后台响应数据时,按照规范设置content-disposition
,需要注意的一点是,不能与ajax
组合(使用ajax
后,会变成二进制数据流,流的处理被ajax
接管)
(此种方式指定的文件名优先级高于a.download
)
var url = 'http://192.168.1.103:3000/api/export'
content-disposition
可以结合以下任一一种方式进行导出:
// a标签function useLink(url) {const link = document.createElement("a");link.style.display = "none";link.href = url;document.body.appendChild(link);link.click();document.body.removeChild(link);
}useLink(url)
// location.hrefwindow.location.href = url;
// window.openwindow.open(url);
// formfunction useFormDownload(url) {const form = document.createElement("form");form.action = url;form.method = "get";form.style.display = "none";document.body.appendChild(form);form.submit();form.remove();
}useFormDownload(url)
// iframe
function useIframeDownload(url) {const iframe = document.createElement("iframe");iframe.src = url;iframe.style.display = "none";document.body.appendChild(iframe);document.body.removeChild(iframe);
}
useIframeDownload(url)
问题
跨域a.download
无效,即使后台设置了Access-Control-Allow-Origin
也无效,download
的值需要与当前域名一致,ngnix
进行转发可以解决。
https://html.spec.whatwg.org/dev/links.html#downloading-resources
大文件ajax
的方式需要用到blob
,而blob
是有限制的,例如chrome
的上限是2GB
,所以ajax
的方式需要被排除,可选的方式是a.download
和Content-Disposition
,这两种方式没有用到blob
,所以也没有明确的限制。
iframe存在的问题
在使用a
标签或者form
时,为了避免在导出时发生页面闪烁现象,我们可以使用targe
非_self
的值来避免,但是当target
为_self
时,如果导出请求失败了,页面会被覆盖掉,用哪种方式可以避免这个问题?
可选的方式为ajax
和iframe
,这两者都可以避免失败时覆盖掉当前页面,并且ajax
还可以告诉用户失败的原因
总结
默认情况下,浏览器面对自身无法打开的文件,都会采取将其保存到本地方式,但是图片、文本文件以及pdf
,浏览器会首先尝试将其打开,这在一般情况下是合理的,但当我们的目的是保存而不是打开时,这就会变成一个问题。
上文罗列了几种导出方式以及各自的局限性,综合来看Content-Disposition
应该是比较通用的方式,即兼顾了兼容性,也避免了浏览器内存限制。
资料
https://github.com/eligrey/FileSaver.js/wiki/Saving-a-remote-file#using-http-header
原文链接:javascript - 文件导出 - 这好像无法拒绝 - SegmentFault 思否