这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情
前言
在一些项目中我们常常使用静态文件服务器,作为一些资源的存储,这样的静态服务器配置简单,使用成本低,访问便捷,常用于图片、视频、PDF 等文件的在线预览。
当我们需要下载这些文件的时候,创建 a 标签,利用 a 标签的点击事件也能够很方便的进行下载。但是,如果这个文件是 PDF,我们在页面上点击一个 PDF 链接的时候,会直接打开新窗口预览文件。
浏览器可以解析的文件,比如.txt,.png,.pdf....浏览器就会采取预览模式,.exe,.xlsx..那么浏览器会自动下载
那么如何做到点击链接使其触发下载事件而不是预览呢?
方案1
通过设置浏览器,使其下载 pdf 而不是在 浏览器中自动打开它们。
步骤:
浏览器设置 -> 高级 -> 内容设置 -> pdf
虽然简单便捷,但是作为一个合格前端开发工程师儿,我们要时刻想着以用户为本,用户就是上帝,我们怎么能麻烦上帝去自己更改设置呢?
方案2
将 PDF 文件的拓展名更改为大写。
如果扩展名是.pdf,则默认会预览。如果扩展名为.PDF,则会直接下载。
下面是同一个文件分别以 .PDF 和 .pdf 命名,点击查看效果。
触发下载:
test-jpfile1.oss-cn-shenzhen.aliyuncs.com//Bom/bom/20…
触发预览:
test-jpfile1.oss-cn-shenzhen.aliyuncs.com//Bom/bom/20…
或者:
<body>
<button onclick="downloadEvt('https://test-jpfile1.oss-cn-shenzhen.aliyuncs.com//Bom/bom/2022/1/19/2022011911370824626513.pdf')">pdf</button>
<button onclick="downloadEvt('https://test-jpfile1.oss-cn-shenzhen.aliyuncs.com//Bom/bom/2022/1/19/2022011911355693652034.PDF')">PDF</button>
<script>
function downloadEvt(url, fileName = '未知文件') {
const el = document.createElement('a');
el.style.display = 'none';
el.setAttribute('target', '_blank');
fileName && el.setAttribute('download', fileName);
el.href = url;
console.log(el);
document.body.appendChild(el);
el.click();
document.body.removeChild(el);
}
</script>
</body>
这种方案存在弊端,如果用户是自己的话,在上传 pdf 文件的时候可以刻意去更改文件的拓展名,但是需要用户上传的话,总不能要求所有用户在上传前更改一遍文件的拓展名吧。
方案3
ajax下载(Blob - 利用Blob对象生成Blob URL)
首先使用 new XMLHttpRequest() 拿到这个文件流,前端获取到服务器端生成的字节流,此时数据是存在于js的内存中的,是不可以直接保存在本地的,利用Blob对象和window.URL.createObjectURL对象生成一个虚拟的URL地址,然后在利用浏览器的特性进行下载。
ajax.responseType = 'blob' 用于指定返回数据的类型,如果不加的话,下载下来的文件可能导致打不开。
function downLoadAjaxEvt(){
const ajax = new XMLHttpRequest();
ajax.responseType = 'blob';
ajax.onload = function () {
if (this.status === 200 || this.status === 304) {
// 通过FileReader去判断接口返回是json还是文件流
const fileReader = new FileReader();
fileReader.readAsDataURL(this.response);
fileReader.onload = () => {
if (this.getResponseHeader('content-type').includes('application/json')) {
alert('服务器出现问题,请联系管理员');
} else {
// decodeURIComponent/decodeURI
//(主要获取后缀名,否则某些浏览器会一律识别为txt,导致下载下来的都是txt)
const _fileName = decodeURIComponent((this.getResponseHeader('content-disposition') || '; filename="未知文件"').split(';')[1].trim().slice(9));
// 也可以用FileSaver(需提前引入https://github.com/eligrey/FileSaver.js)
// saveAs(fileReader.result, _fileName);
downloadEvt(fileReader.result, _fileName);
}
}
} else {
alert('服务器出现问题,请联系管理员');
}
};
// 发送请求
ajax.send(queryParams);
}
function downloadEvt(url, fileName = '未知文件') {
const link = document.createElement('a');
link.style.display = 'none';
link.setAttribute('target', '_blank');
fileName && el.setAttribute('download', fileName);
link.href = url;
document.body.appendChild(el);
link.click();
document.body.removeChild(el);
};
方案 3 就很满足需求了
如果需要甚至可以在请求头中加上token鉴权:
ajax.setRequestHeader('Authorization', _config.token);
ajax.setRequestHeader('Content-Type', _config.contentType);
也可以监控文件下载的进度:
// 获取文件下载进度
ajax.addEventListener('progress', (progress) => {
const percentage = ((progress.loaded / progress.total) * 100).toFixed(2);
const msg = `下载进度 ${percentage}%...`;
console.log(msg);
});
参考文章: