使用URL.createObjectURL(blob)如何为动态生成的PDF设置有意义的文件名?
我碰到过完全一样的问题!你当前的方案在IE和Chrome内嵌PDF阅读器下表现都没问题,但当Chrome调用外部Acrobat这类阅读器时,外部程序根本拿不到我们设置的自定义文件名——毕竟blob的name属性和iframe的name都是浏览器上下文里的东西,外部程序只能看到那个临时的blob GUID地址,还会多出个空白标签页,确实头疼。
先给你分析下核心原因:当Chrome把blob URL交给外部程序处理时,外部进程只能解析这个临时URL,无法访问浏览器内存里的blob对象及其属性,所以我们设置的blob.name完全没用;而那个空白标签页是因为你用window.open()创建了新窗口,但外部程序接管PDF打开后,这个窗口就空下来了。
下面给你几个可行的解决办法,按推荐程度排序:
方案1:用a标签触发下载/打开(无后端依赖,最稳妥)
放弃iframe的方式,改用隐藏的a标签来触发操作,这样浏览器会自动处理文件名,不管用户选下载还是直接用外部阅读器打开,文件名都是对的,还不会出现空白标签页:
var title = "Claim-" + this.claimNumber + "-" + new Date() + ".pdf"; var blob = new Blob([wsRequest.response], { type: 'application/pdf' }); if (browser() === 'IE') { window.navigator.msSaveOrOpenBlob(blob, title); } else { // 创建隐藏的a标签 const downloadLink = document.createElement('a'); downloadLink.href = URL.createObjectURL(blob); // 关键:设置download属性指定文件名 downloadLink.download = title; // 加入DOM触发点击 document.body.appendChild(downloadLink); downloadLink.click(); // 清理元素和blob URL document.body.removeChild(downloadLink); URL.revokeObjectURL(downloadLink.href); }
这个方案的好处是不需要改任何后端代码,所有浏览器(包括Chrome用外部阅读器的情况)都能正确识别文件名,体验也更顺畅。
方案2:后端配合返回Content-Disposition头(最优体验)
如果你的Web服务可以调整,让它返回PDF的时候带上Content-Disposition响应头,直接指定文件名:
Content-Disposition: inline; filename="Claim-1234-2024-05-20.pdf"
然后你就不用在前端处理blob了,直接打开服务的URL就行:
var title = "Claim-" + this.claimNumber + "-" + new Date() + ".pdf"; var win = window.open(`/your-api-endpoint?claimNumber=${this.claimNumber}`); win.document.title = title;
这种方式下,外部阅读器能直接从HTTP响应头里拿到文件名,打开PDF的过程更自然,没有中间步骤,但需要后端同事配合修改接口。
方案3:FileReader转Data URI(仅小PDF适用)
把blob转换成Data URI,然后用a标签触发操作,不过这种方式对大PDF不太友好,因为Data URI会把文件内容编码成字符串,占用更多内存:
var title = "Claim-" + this.claimNumber + "-" + new Date() + ".pdf"; var blob = new Blob([wsRequest.response], { type: 'application/pdf' }); const reader = new FileReader(); reader.onload = function(e) { const a = document.createElement('a'); a.href = e.target.result; a.download = title; document.body.appendChild(a); a.click(); document.body.removeChild(a); }; reader.readAsDataURL(blob);
总结一下:优先用方案1,零成本解决问题;如果能改后端,方案2体验最好;方案3只适合小文件应急用。
内容的提问来源于stack exchange,提问作者paulsm4

