1. 为什么Content-Disposition无法获取?
要拿到 Content-Disposition 里的 filename,可以用正则或者简单的字符串解析。
浏览器默认不让前端访问非标准响应头,Content-Disposition
需要后端显式暴露。
在浏览器开发者工具 → Network → Response Headers 能看到 Content-Disposition,
但那只是 调试面板直接读取网络层数据,它绕过了 JavaScript 的访问限制。
JavaScript 代码(axios、fetch)拿响应头时,浏览器会套用 CORS 安全策略:
只有 简单响应头(Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma)是默认可访问的。其它非标准式响应头(比如 Content-Disposition)必须由服务端显式暴露。
这是浏览器的安全设计,为了防止恶意网站跨域访问你的接口时,读取敏感信息(比如文件名、token、隐私数据等)。
2. 遇到的问题
请求文件导出接口后,服务端返回响应头
Content-Disposition: attachment; filename=20250720_Magnetic_beads_add_Worklist.zip
但浏览器没暴露这个头,因为服务端 CORS 响应缺少:
Access-Control-Expose-Headers: Content-Disposition
所以在代码里拿到的 res.headers['content-disposition']
是 undefined → 解析出来就是 null。
3. 解决方法
后端必须显式暴露响应头:
Access-Control-Expose-Headers: Content-Disposition
前端拿值并解析:
const contentDisposition = res.headers['content-disposition'];
console.log(contentDisposition); // attachment; filename=20250720_Magnetic_beads_add_Worklist.zipconst match = /filename\*?=(?:UTF-8''|")?([^"]+)/i.exec(contentDisposition);
const filename = match ? decodeURIComponent(match[1]) : 'default_filename';
console.log(filename);
完整的导出方法(支持zip、csv、xlsx)
async function downloadFile(data) {try {// 调用导出抽样检测APIconst res = await request({url: data.url,method: data.method,data: data.params,responseType: 'blob', // 设置为blob类型headers: {Accept:'application/zip, application/x-zip-compressed, application/octet-stream, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, text/csv',},});// 检查响应头,判断是否为文件下载const contentType = res.headers['content-type'];const contentDisposition = res.headers['content-disposition'];//支持zip、csv、xlsxif (contentType &&(contentType.includes('application/zip') ||contentType.includes('application/x-zip-compressed') ||contentType.includes('application/octet-stream') ||contentType.includes('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') ||contentType.includes('text/csv'))) {// 根据文件类型设置默认文件名和扩展名let filename, fileType;if (contentType?.includes('text/csv')) {fileType = 'csv';} else if (contentType?.includes('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')) {fileType = 'xlsx';} else {fileType = 'zip';}filename=`download.${fileType}`// content-disposition为空时默认使用的文件名// 读取响应头content-disposition中filenameif (contentDisposition) {const filenameStarMatch = contentDisposition.match(/filename\*\s*=\s*(?:UTF-8''|)([^;\n]*)/i);if (filenameStarMatch && filenameStarMatch[1]) {try {filename = decodeURIComponent(filenameStarMatch[1].replace(/['"]/g, ''));} catch (_) {filename = filenameStarMatch[1].replace(/['"]/g, '');}} else {const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);if (filenameMatch && filenameMatch[1]) {filename = filenameMatch[1].replace(/['"]/g, '');}}}// 直接使用res.data(已经是Blob)const url = window.URL.createObjectURL(res.data);const link = document.createElement('a');link.href = url;link.download = filename;document.body.appendChild(link);link.click();// 清理document.body.removeChild(link);window.URL.revokeObjectURL(url);message({message: `导出成功: ${filename}`,type: 'success',duration: 1500,});} else if (contentType && contentType.includes('application/json')) {// 如果是JSON响应,说明是错误信息// 需要将Blob转换为文本,然后解析JSONconst text = await res.data.text();const jsonData = JSON.parse(text);message({message: jsonData.message || '导出失败',type: 'error',duration: 1500,});} else {// 处理其他类型的响应message({message: '导出失败:不支持的响应类型',type: 'error',duration: 1500,});}} catch (error) {message({message: error.message,type: 'error',duration: 1500,});}
}