赞
踩
web文件下载是业务常见的需求场景
function download(path){
window.open(path)
}
将新开一个窗口页面进行下载,下载完毕,页面会自动关闭。会有一瞬间的白屏显示及消失。
function download(path){
location.href = path
}
将在当前页面进行下载。触发浏览器的下载弹框,交互友好。常规下载推荐这种方式。
这两种方式有如下优缺点:
优点
缺点
decodeURIComponent
处理。header
,也就不能进行鉴权。我们知道,a
标签可以访问下载文件的地址,浏览器帮助进行下载。但是对于浏览器支持直接浏览的txt、png、jpg、gif等文件,是不提供直接下载(可右击从菜单里另存为)。为了解决这个问题,可以利用download
属性。
要知道浏览器是否支持download属性,简单的一句代码即可区分
const isSupport = 'download' in document.createElement('a');
此属性指示浏览器下载 URL 而不是导航到它,因此将提示用户将其保存为本地文件。如果属性有一个值,那么此值将在下载保存过程中作为预填充的文件名(如果用户需要,仍然可以更改文件名)。此属性对允许的值没有限制,但是 / 和 \ 会被转换为下划线。大多数文件系统限制了文件名中的标点符号,故此,浏览器将相应地调整建议的文件名。
blob: URL
或 data: URL
,以方便用户下载使用 JavaScript 生成的内容(例如使用在线绘图 Web 应用程序创建的照片)。Content-Disposition
属性赋予了一个不同于此属性的文件名,HTTP 头属性优先于此属性。Content-Disposition
被设置为 inline
(即 Content-Disposition='inline'
),那么 Firefox 优先考虑 HTTP 头 Content-Disposition
download 属性基于上面描述,如果你尝试下载跨域链接,那么其实download的效果就会没了,跟不设置download表现一致。即浏览器能预览的还是会预览,而不是下载。
简单用法:
<a href="example.jpg" download>点击下载</a>
可以带上属性值,指定下载的文件名称,即重命名下载文件。不设置的话默认是文件原本名。
<a href="example.jpg" download="test">点击下载</a>
还可以带上文件名及后缀名,对文件名称及文件MIME类型进行覆盖。不过一般不会对文件类型进行处理,除非下载无法自动处理该文件类型。
<a href="example.jpg" download="test.png">点击下载</a>
如上,会下载了一个名叫test.png的图片
对于在跨域下不能下载浏览器可预览的文件,其实可以跟后端协商好,在后端层做多一层转发,最终返回给前端的文件链接跟下载页同域就好了。
优点
缺点
function download(path, fileName){
const a = document.createElement('a')
a.href = path
a.download = fileName
a.click()
}
该方法较上面的直接使用a标签download这种方法的优势在于,它除了能利用已知文件地址路径进行下载外,还能通过发送ajax请求api获取文件流进行下载。毕竟有些时候,后端不会直接提供一个下载地址给你直接访问,而是要调取api。
利用Blob对象可以将文件流转化成Blob二进制对象。该对象兼容性良好,需要注意的是
Safari has a serious issue with blobs that are of the type application/octet-stream
进行下载的思路很简单:发请求获取二进制数据,转化为Blob对象,利用URL.createObjectUrl
生成url地址,赋值在a标签的href属性上,结合download进行下载。
/** * 下载文件 * @param {String} path - 请求地址 * @param {String} name - 文件名字 */ function downloadFile (path, name) { const xhr = new XMLHttpRequest(); xhr.open('get', path); xhr.responseType = 'blob'; xhr.send(); xhr.onload = function () { if (this.status === 200) { let resBlob = this.response // 当未设置responseType为'blob'时,则将此注释放开 // resBlob = new Blob([resBlob], { type: xhr.getResponseHeader('Content-Type') }); const url = URL.createObjectURL(resBlob); const a = document.createElement('a'); a.href = url; a.download = name; a.click(); URL.revokeObjectURL(url); } }; }
因为发请求时已设置返回数据类型为Blob类型(xhr.responseType = ‘blob’),所以target.response就是一个Blob对象,打印出来会看到两个属性size和type。虽然type属性已指定了文件的类型,但是为了稳妥起见,还是在download属性值里指定后缀名,如Firefox不指定下载下来的文件就会不识别类型。
如果发送请求时不设置xhr.responseType = ‘blob’,默认ajax请求会返回DOMString类型的数据,即字符串。这时就需要注释的代码了,对返回的文本转化为Blob对象,然后创建blob url。
优点
这里的用法跟上面用Blob大同小异,基本上思路是一样的,唯一不同的是,上面是利用Blob对象生成Blob URL,而这里则是生成Data URL,所谓Data URL,就是base64编码后的url形式。
/** * 下载文件 * @param {String} path - 请求地址 * @param {String} name - 文件名字 */ function downloadFile (path, name) { const xhr = new XMLHttpRequest(); xhr.open('get', path); xhr.responseType = 'blob'; xhr.send(); xhr.onload = function () { if (this.status === 200) { const fileReader = new FileReader(); fileReader.readAsDataURL(this.response); fileReader.onload = function () { const a = document.createElement('a'); a.href = this.result; a.download = name; a.click(); }; } }; }
有时候我们在发送下载请求之前,并不知道文件名,或者文件名是后端提供的,我们就要想办法获取。
当返回文件流的时候,我们在浏览器上观察接口返回的信息,会看到有这么一个header:Content-Disposition
Content-disposition: attachment; filename=20200323151823_190342.xlsx; filename*=UTF-8''CMCoWork_%E6%B5%8B%E8%AF%95
上面的值是例子。
其中包含了文件名,我们可以想办法获取其中的文件名。我们看到,有filename=和filename*=,后者不一定有,在旧版浏览器中或个别浏览器中,会不支持这种形式,filename*采用了RFC 5987中规定的编码方式。
所以你要获取文件名,就变成,截取这段字符串中的这两个字段值了。
看上面的例子大家可能发现,怎么值怪怪的。是的,如果名字是英文,那好办, 如果是有中文或者其他特殊符号,是需要处理好编码的
decodeURIComponent
就能解码了,能还原成原本的样子。当然,解码前你要把值中的UTF-8’'这种部分给去掉。所以,在我们实现之前,我们就要明白,取Content-Disposition的内容,并不是百分百能符合你预期的,除非你的文件名全是英文数字。
我们提取文件名值:
// xhr是XMLHttpRequest对象
const content = xhr.getResponseHeader('Content-disposition');
if (content) {
let name1 = content.match(/filename=(.*);/)[1]; // 获取filename的值
let name2 = content.match(/filename\*=(.*)/)[1]; // 获取filename*的值
name1 = decodeURIComponent(name1);
name2 = decodeURIComponent(name2.substring(7)); // 这个下标7就是UTF-8''
}
上面我们获得了两个文件名name1,name2,如果两个都存在,那么我们优先取name2的,因为这个更靠谱,name1如果包含中文或特殊符号,就有风险还原不了真正的文件名。
缺陷
本质上跟上述的Content-Disposition差不多,只是我们这里不使用默认的header,我们自己自定义一个response header,跟后端决定好编码方式返回,前端直接获取这个自定义header,然后使用对应的解码即可,如使用decodeURIComponent
。
但是我们都要知道,在跨域的情况下,前端获取到的header只有默认的6个基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。
所以你想要获取到别的header,需要后端配合,设置
Access-Control-Expose-Headers: Content-disposition, custom-header
这样,前端就能获取到对应暴露的header字段,需要注意的是,Content-disposition
也是需要暴露的。
这里提供个方法来获取文件后缀名
function getFileType (name) {
const index = name.lastIndexOf('.');
return name.substring(index + 1);
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。