问题:导出功能,无法获取对应文件名。
一般是从响应头的content-disposition字段获取对应的fileName。
贴上前端代码:
/**
* 下载模板
*/
function handleDownload() {
downloadExcel()
.then(res => {
let fileName //文件名
if (res && res.headers && res.headers['content-disposition']) {
fileName = decodeURIComponent(res.headers['content-disposition'].split(';')[1].split('filename=')[1])
} else {
fileName = `导入用户模板`
}
getExcelData(res.data, fileName)
})
.catch(err => {
proxy.$errorHandle(err)
})
}
/**
* 下载excel表格
* @param data file文件
* @param fileName 表格名
*/
export function getExcelData(data, fileName) {
const link = document.createElement('a')
const blob = new Blob([data], { type: 'application/vnd.ms-excel' }) //.xls文件
link.style.display = 'none'
const url = window.URL.createObjectURL(blob)
link.href = url
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(link)
}
要成功拿到res.headers['content-disposition'],需要注意以下几点,都是踩过的坑。
1. 响应拦截器是否拦截了res.headers
检查下项目是怎么封装http请求的,有时候响应拦截器会把res.headers过滤掉,直接返回res.data,这时候我们接收到的响应当然就没有请求头了,需要从响应拦截器处修改。
贴一个能正常返回returnCode、data和headers的代码,具体还得看你项目怎么封装的:
// 响应拦截器
instance.interceptors.response.use(
// 默认的拦截器
function (response: AxiosResponse<any, any>): AxiosResponse<any, any> | Promise<AxiosResponse<any, any>> {
// 包含了code>=200和code<400的请求(但是业务系统会自己定义一些业务code)returnCode=000000也表示成功
if (response?.data && Object.prototype.hasOwnProperty.call(response.data, 'returnCode')) {
if (response.data.returnCode === ResponseStatus.SUCCESS) {
return response.data
} else {
return Promise.reject({
code: response?.data?.returnCode,
message: response?.data?.message ?? '业务功能异常!'
})
}
} else {
// 处理返回流
if (response.data && Object.prototype.toString.call(response.data) === '[object Blob]') {
return {
// 重新组装响应
returnCode: parseInt(ResponseStatus.SUCCESS),
data: response.data,
headers: response.headers
} as any
}
throw new Error(`${response.config.url}接口返回数据格式错误`)
}
},
function (error) {
// TODO: 404和没有权限处理
// 401未授权
// 404请求地址不存在
// 500 网络错误
if (error.response && String(error.response.code).startsWith('5')) {
return Promise.reject({ code: '500', message: '网络异常,请重试' })
//路由切换异常的处理
} else if (error.message === 'Request Cancel') {
return Promise.reject({ code: ResponseStatus.REQUEST_CANCEL, message: error.message })
}
return Promise.reject(error)
}
)
2. 检查对应接口的请求头是否包含Content-Disposition
确保后端已经设置并且Content-Disposition字段
response.setHeader("Content-Disposition", ...)
3. 是否设置了请求头暴露对应的Content-Disposition
十分重要,不知道为啥对接的俩后端都没设置过暴露这个参数,我怎么拿都拿不到。
解决办法:让你的后端加上这行!
// 暴露Content-Disposition
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition")
解释下为什么要加这个
原因:
MDN文档:Access-Control-Expose-Headers
默认情况下,header 只有7种 simple response headers (简单响应首部)可以暴露给外部:
名称 | |
---|---|
Cache-Control | 通用消息头字段,被用于在http请求和响应中,通过指定指令来实现缓存机制 |
Content-Language | 说明访问者希望采用的语言或语言组合,这样的话用户就可以根据自己偏好的语言来定制不同的内容 |
Content-Length | 指明发送给接收方的消息主体的大小,即用十进制数字表示的八位元组的数目 |
Content-Type | 告诉客户端实际返回的内容的内容类型 |
Expires | 响应头包含日期/时间, 即在此时候之后,响应过期 |
Last-Modified | 包含源头服务器认定的资源做出修改的日期及时间 |
Pragma | HTTP/1.0 中规定的通用首部,用来向后兼容只支持 HTTP/1.0 协议的缓存服务器 |
content-disposition 不在headers默认暴露的范围内,即使服务器在响应头里加了该字段,但因没”暴露“给外部,客户端就“看得到,吃不到”。
响应首部 Access-Control-Expose-Headers 就是控制“暴露”的开关,它列出了哪些首部可以作为响应的一部分暴露给外部。
所以如果想要让客户端可以访问到其他的首部信息,服务器不仅要在 header 里加入该首部,还要将它们在 Access-Control-Expose-Headers 里面列出来。