闽公网安备 35020302035485号
.触发下载行为
async function downloadFile(url) {
try {
// 堆代码 duidaima.com
const response = await fetch(url);
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = 'file'; // 默认文件名
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(downloadUrl);
document.body.removeChild(a);
} catch (error) {
console.error('下载失败:', error);
}
}
2.2 存在的问题.缺乏错误处理
Content-Disposition: attachment; filename="example.pdf"中文名时需编码下:
const filename = 'duidaima.xlsx';
// 设置响应头
ctx.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
const encodedFilename = encodeURIComponent(filename);
ctx.set('Content-Disposition', `attachment; filename*=UTF-8''${encodedFilename}`);
说明:// 通过contentDisposition 获取文件名
function getFilenameFromContentDisposition(contentDisposition) {
if (!contentDisposition) {
return null
}
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
const matches = filenameRegex.exec(contentDisposition)
if (matches && matches[1]) {
return matches[1].replace(/['"]/g, "").replace("UTF-8", "")
}
return null
}
四.处理特殊情况function decodeFilename(filename) {
// 处理UTF-8编码的文件名
if (filename.includes("%")) {
try {
return decodeURIComponent(filename)
} catch (e) {
return filename
}
}
// 处理ISO-8859-1编码的情况
if (/=\?/.test(filename)) {
try {
return decodeURIComponent(escape(filename))
} catch (e) {
return filename
}
}
return filename
}
五.完整示例/**
* 获取 blob
* @param {String} url 目标文件地址
* @return {Promise}
*/
export function getBlob(url) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
url = url + `?r=${Math.random()}`
xhr.open("GET", url, true)
xhr.responseType = "blob"
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response)
}
}
xhr.send()
})
}
/**
* 保存
* @param {Blob} blob
* @param {String} fileName 想要保存的文件名称
* @param {String} type 想要保存的文件名称
*/
export function downloadFileByBlob(data, fileName, type) {
let options = {}
if (type) {
options = { type: `application/${type}` }
}
const blob = new Blob([data], options)
if ("download" in document.createElement("a")) { // 非IE下载
const alink = document.createElement("a")
alink.download = fileName
alink.style.display = "none"
alink.href = URL.createObjectURL(blob)
document.body.appendChild(alink)
alink.click()
URL.revokeObjectURL(alink.href) // 释放URL 对象
document.body.removeChild(alink)
} else { // IE10+下载
navigator.msSaveBlob(blob, fileName)
}
}
/**
* 下载
* @param {String} url 目标文件地址
* @param {String} filename 想要保存的文件名称
*/
export function downloadFileByUrl(url, filename, type) {
getBlob(url).then(blob => {
downloadFileByBlob(blob, filename, type)
})
}
// 通过contentDisposition 获取文件名
export function getFilenameFromContentDisposition(contentDisposition) {
if (!contentDisposition) {
return null
}
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
const matches = filenameRegex.exec(contentDisposition)
if (matches && matches[1]) {
return decodeFilename(matches[1].replace(/['"]/g, "").replace("UTF-8", ""))
}
return null
}
function decodeFilename(filename) {
// 处理UTF-8编码的文件名
if (filename.includes("%")) {
try {
return decodeURIComponent(filename)
} catch (e) {
return filename
}
}
// 处理ISO-8859-1编码的情况
if (/=\?/.test(filename)) {
try {
return decodeURIComponent(escape(filename))
} catch (e) {
return filename
}
}
return filename
}
5.2 axios.js响应拦截中设置文件名import { getFilenameFromContentDisposition } from "@/utils/download"
.....
// respone拦截器
service.interceptors.response.use(
async(response) => {
const { data, config, status, request } = response
// 如果自定义代码不是200,则判断为错误。
if (status !== 200) {
modal.msgError(data.message || "Error")
return { ...data, config, responseErr: true }
} else {
// 二进制数据则直接返回
if (
request.responseType === "blob" ||
request.responseType === "arraybuffer"
) {
// 获取 Content-Disposition 头
const contentDisposition = response.headers["content-disposition"]
// 解析文件名
const fileName = getFilenameFromContentDisposition(contentDisposition) // 默认文件名 为空
if (fileName) {
// 存储文件名 用于设置和后端一样的文件名
localStorage.setItem("downloadFileName", fileName)
}
return response
}
if (data.code === 200) {
return { ...data, config }
}
if (data.code === 401 || data.code === 403) {
return { ...data, config, responseErr: true }
}
// 用户权限变更
if (data.code === 402) {
if (isShowingModal) {
return { ...data, config, responseErr: true }
}
isShowingModal = true
modal
.confirm(data.message, {
type: "warning",
showClose: false
})
.then(async() => {
// 刷新去首页吧
window.location.href =
window.location.origin + window.location.pathname
})
.catch(async(err) => {
// 关闭 MessageBox 后,手动移除 不能关闭的遮罩层
const maskModal = document.querySelector(".v-modal")
if (maskModal) {
maskModal.remove()
}
await store.dispatch("user/getUserInfo")
isShowingModal = false
})
return { ...data, config, responseErr: true }
}
modal.msgError(data.message || "Error")
return { ...data, config, responseErr: true }
}
},
(error) => {
// 加入try 否则 会导致 取消重复请求时 因为无config等字段导致报错 导致取消重复请求失效
try {
const { config, headers, status } = error.response
if (status === 520) {
// 导出文件接口报错时与后端约定 返回状态码为 520
// 约定好响应头添加 x-custom-header 且值为原本报错的 code
const code = Number(headers["x-custom-header"])
console.log("🚀 ~ code:", code)
// 当code为401 或者 403时 触发刷新token接口或者弹框提示过期重新登录
if (code === 401 || code === 403) {
return {
code,
message: "凭证已过期,请重新登录!",
config,
responseErr: true
}
} else {
modal.msgError(code === 409 ? "无权限" : error.message || "Error")
return Promise.reject(error)
}
}
} catch (err) {
console.log("🚀 ~ err:", err)
}
if (error.name !== "CanceledError") {
modal.msgError(error.message || "Error")
}
return Promise.reject(error)
}
)
代码解释,如果返回内容 是blob文件,则获取其响应头设置的文件名,将文件名存储到浏览器缓存。export function exportUserByQuery(data = {}, config = {
preventDuplicateRequestsType: "prevent",
responseType: "blob" // blob 或 arraybuffer
}) {
return axiosRequest.post(`url`, data, config)
}
5.4 页面中下载import { downloadFileByBlob } from "@/utils/download"
......
userApi.exportUserByQuery(this.queryParams).then((data) => {
// 从缓存中获取文件名 没有则设置 默认文件名
const fileName = localStorage.getItem("downloadFileName") || "用户数据.xlsx"
downloadFileByBlob(data, fileName, "octet-stream")
localStorage.removeItem("downloadFileName") // 清除缓存
this.$modal.msgSuccess("导出用户数据成功")
this.openExportWay = false
})
总结