后台导出的报表是个 HTML 表格,想让用户点一下就存成文件,结果加了 download 属性,Chrome 里点开还是新标签页展示,Edge 也一样。不是属性写错,是跨域把它废了。
download 只对同源地址生效。文件放在另一个域名或者 CDN 上,浏览器会忽略这个属性,老老实实打开。这是安全限制,没有绕过的开关。
让服务端说清楚这是附件
最直接的办法是响应头带 Content-Disposition: attachment,再给 filename。浏览器看到 attachment 就不再内联渲染,直接弹保存框。前端怎么点都一样。
Content-Disposition: attachment; filename=report.xlsx文件名带中文要按 RFC 5987 写 filename* 加 UTF-8 编码,只写 filename 在部分浏览器上会变成乱码。nginx 里可以用 add_header 配,但更稳的是让后端在下载接口里输出。
前端能做的三件事
- 同源文件,href 直接指向地址,加 download 和文件名,够用。
- 跨域但允许 CORS,用 fetch 拿 blob,再 createObjectURL,用完记得 revoke。
- 纯前端生成的文本、JSON,直接 new Blob 加临时 a 元素触发,不用碰服务器。
注意 blob 方式在移动端微信内置浏览器里经常没反应,重要文件还是走后端附件头,兼容性最好。
响应头里的几个细节
文件是后端动态拼的,Content-Type 也要一起给。application/octet-stream 会让浏览器一律当附件,比单靠 disposition 更稳,代价是系统不知道文件类型,下载完双击可能问用哪个程序打开。
文件名里的中文、空格、括号都要编码,不然响应头里出现非 ASCII 字符,部分客户端会直接报错或者截断。稳妥的写法是同时给 filename 和 filename* 两个参数,老浏览器读前者,新的读后者。
大文件别硬来
几 GB 的文件走 blob 会整个读进内存,页面直接卡死。这种就别玩前端了,给个带 attachment 的直链,让下载器接手。文件名里的斜杠和冒号也要过滤,Windows 上会存不下来。