PDF 下载
客户端方面所能支持的下载方式,最常见的如下几种:
- a 标签,例如 下载
- location.href,例如 window.location.href = xxx
- window.open,例如 window.open(xxx)
- Content-disposition,例如 Content-disposition:attachment;filename=”xxx”
的 download 属性用于指示浏览器 下载 href 指定的 URL,而不是导航到该资源,通常会提示用户将其保存为本地文件,如果 download 属性有指定内容,这个值就会在下载保存过程中作为 预填充的文件名,主要是因为如下原因:
- 这个值可能会通过 JavaScript 进行动态修改
- 或者 Content-Disposition 中指定的 download 属性优先级高于 a.download
这种应该是大家最熟悉的方式了,但熟悉归熟悉,还有一些值得注意的点:
download 属性只适用于 同源 URL
-
若 HTTP 响应头中的 Content-Disposition 属性中指定了一个不同的文件名,那么会优先使用 Content-Disposition 中的内容
HTTP 若 HTTP 响应头中的 Content-Disposition 被设置为 Content-Disposition=’inline’,那么在 Firefox 中会优先使用 Content-Disposition 的 download 属性
静态方式:
1
| <a href="http://127.0.0.1:4000/pdf/2-1.png" download="2.pdf">下载</a>
|
动态方式:
1 2 3 4 5 6 7 8 9
| function download(url, filename){ const a = document.createElement("a"); // 创建 a 标签 a.href = url; // 下载路径 a.download = filename; // 下载属性,文件名 a.style.display = "none"; // 不可见 document.body.appendChild(a); // 挂载 a.click(); // 触发点击事件 document.body.removeChild(a); // 移除 }
|
Blob 方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| if (reqConf.responseType == 'blob') { // 返回文件名 let contentDisposition = config.headers['content-disposition'];
if (!contentDisposition) { contentDisposition = `;filename=${decodeURI(config.headers.filename)}`; }
const fileName = window.decodeURI(contentDisposition.split(`filename=`)[1]);
// 文件类型 const suffix = fileName.split('.')[1];
// 创建 blob 对象 const blob = new Blob([config.data], { type: FileType[suffix], });
const link = document.createElement('a'); link.style.display = 'none'; link.href = URL.createObjectURL(blob); // 创建 url 对象 link.download = fileName; // 下载后文件名 document.body.appendChild(link); link.click();
document.body.removeChild(link); // 移除隐藏的 a 标签 URL.revokeObjectURL(link.href); // 销毁 url 对象 }
|
Content-disposition 和 location.href/window.open 实现下载
这看似是三种下载方式,但实际上就是一种,而且还是以 Content-disposition 为准。
Content-Disposition 响应头 指示回复的内容该以何种形式展示,是以 内联 的形式(即网页或页面的一部分)展示,还是以 附件 的形式 下载 并保存到本地,如下:
- inline: 是 默认值,表示回复中的消息体会以页面的一部分或者整个页面的形式展示
- attachment: 设置为此值意味着消息体应该被下载到本地,大多数浏览器会呈现一个 “保存为” 的对话框,并将 filename 的值预填为下载后的文件名
因此,基于 location.href=’xxx’ 和 window.open(xxx) 的方式能实现下载就是基于 Content-Disposition: attachment; filename=”filename.jpg” 的形式,又或者说是触发了浏览器本身的下载行为,满足了这个条件,无论是通过 a 标签跳转、location.href 导航、window.open 打开新页面、直接在地址栏上输入 URL 等都可以实现下载。
1 2
| Content-Disposition: inline Content-Disposition: attachment; filename="filename.jpg"
|
pdf下载例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import {Loading, Message} from 'element-ui'
// 验证是否为blob格式 export async function blobValidate(data) { try { const text = await data.text(); JSON.parse(text); return false; } catch (error) { return true; } }
// 下载pdf export function downloadPdf(url, config, pdfFilename) { let downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) return service.get(url, { headers: {'Content-Type': 'application/pdf'}, responseType: 'blob', ...config }).then(async (allResponse) => { const {data, headers} = allResponse
const isLogin = await blobValidate(data); const contentDisposition = headers?.['content-disposition'] ?? '' const filenameMatch = contentDisposition.match(/filename=(.+)/) const filename = filenameMatch && filenameMatch[1];
if (isLogin) { const blob = new Blob([data], {type: 'application/pdf'}) saveAs(blob, pdfFilename ? pdfFilename : (filename ? filename : 'downloadFile')) } else { const resText = await data.text(); const rspObj = JSON.parse(resText); const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] Message.error(errMsg); } downloadLoadingInstance.close(); }).catch((r) => { console.error(r) Message.error('下载文件出现错误,请联系管理员!') downloadLoadingInstance.close(); }) }
|
参考:https://juejin.cn/post/7207078219215732794?searchId=202312051350294CF62EF4D86C841F0D6B#heading-14