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

    • 同源 URL 会进行 下载 操作
    • 非同源 URL 会进行 导航 操作
    • 非同源的资源 仍需要进行下载,那么可以将其转换为 blob: URLdata: 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