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,});}
}