纯原创文章,转载请说明来源。
一、背景
要实现一个下载功能,后端直接返回了一个图片的地址https://xxxxx/pic.jpg
。如果我们直接通过window.open(url, '_blank')
的方式去下载这个图片,会发现 Chrome 浏览器会对这个图片进行预览,而不是期望的下载。那要怎么做,才能实现这个下载呢?
二、代码实现
Step1. 封装下载通用的方法:
/**
* 判断是否IE浏览器,为IE返回true
* @returns {boolean}
*/
export const isIE = (): boolean => {
const userAgent = navigator.userAgent // 取得浏览器的userAgent字符串
const isIE = userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1 // 判断是否IE<11浏览器
const isEdge = userAgent.indexOf('Edge') > -1 && !isIE // 判断是否IE的Edge浏览器
const isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf('rv:11.0') > -1
return isIE || isEdge || isIE11
}
/**
* 尝试从Blob中保存文件作为JSON
* 先尝试将Blob转换为文本并解析为JSON,如果失败(即不是有效的JSON),则调用另一个函数来处理文件的保存
* @param fileName 文件名
* @param blob
* @param fileMime MIME 类型
*/
export const attemptSaveFileFromBlobAsJson = (fileName: string, blob: Blob, fileMime: string) => {
const fileReader: any = new FileReader()
fileReader.readAsText(blob)
fileReader.onloadend = () => {
try {
// 转换解析结果,转换成功代表后端抛错 { code: 400, msg: 'xxx' }
JSON.parse(fileReader.data)
} catch (err) {
saveFileFromBlobDirectly(fileName, blob, fileMime)
}
}
}
/**
* 直接保存Blob为文件
* @param fileName 文件名
* @param blob
* @param fileMime MIME 类型
*/
function saveFileFromBlobDirectly(fileName: string, blob: Blob, fileMime: string) {
try {
// 下载文件
const fileBlob = new Blob([blob], {
type: fileMime
})
if (!isIE()) {
// 非IE下载
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.target = '_blank'
elink.href = URL.createObjectURL(fileBlob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // 释放URL 对象
document.body.removeChild(elink)
} else {
// IE10+下载
;(navigator as any).msSaveBlob(fileBlob, fileName)
}
} catch (err) {
console.error(err)
}
}
/**
* 通过url获取文件名
* @param url
* @returns
*/
export function getFileNameFromUrl(url: string): string | null {
try {
const parsedUrl = new URL(url)
const path = parsedUrl.pathname
const lastIndex = path.lastIndexOf('/')
return lastIndex === -1 ? null : path.substring(lastIndex + 1)
} catch (error) {
// 如果URL格式不正确,捕获错误并返回null或抛出错误
console.error('Invalid URL:', error)
return null
}
}
Setp2. 封装发送图片请求的方法
import http from '@/plugins/axios'
/**
* 获取图片
*/
export const gePicBlob = (url: string) => {
return http.get<never, any>(url, { responseType: 'blob' })
}
// axios实例需要做一点点改造
const instance = axios.create({
// ... 省略相关配置
})
/**
* 返回后置拦截
*/
instance.interceptors.response.use(
function (response) {
// blob 类型 直接返回
if (response?.config?.responseType === 'blob') return response.data
// 省略其余的业务逻辑
}
)
Step3. 使用刚刚封装的方法
/**
* 点击下载按钮时触发
*/
async function downloadOnClick(downloadUrl?: string) {
if (!downloadUrl) {
return
}
try {
const fileName = getFileNameFromUrl(downloadUrl) || '下载.jpg'
const data = await gePicBlob(downloadUrl)
attemptSaveFileFromBlobAsJson(fileName, data, data.type)
} catch (ex) {
console.error(ex)
}
}
三、实现逻辑
-
获取图片 Blob:
gePicBlob
函数通过 GET 请求从指定的 URL(后端返回的图片链接)获取图片数据,并设置响应类型为 blob。这意味着返回的响应体将直接作为 Blob 对象处理。
注意事项:发送请求时,如果因为图片资源的域名和当前域名不一致导致跨域,那么我们需要在 nginx 进行配置,通过Access-Control-Allow-Origin
配置允许当前的域名访问图片资源。 -
处理 Blob 对象:
在downloadOnClick
函数中,首先通过gePicBlob
获取图片的 Blob 对象及其 MIME 类型(通过data.type
获取)。
然后,调用attemptSaveFileFromBlobAsJson
函数,一旦遇到解析错误(即 Blob 不是有效的 JSON),就会直接调用saveFileFromBlobDirectly
来下载文件。 -
直接下载文件:
saveFileFromBlobDirectly
函数是实际执行下载操作的函数。它首先创建一个新的 Blob 对象(为了确保 MIME 类型被正确设置)。
接着,根据浏览器类型(是否为 IE),使用不同的方法来触发下载。对于非 IE 浏览器,它创建一个临时的<a>
标签,设置其 href 为 Blob 对象的 URL(通过URL.createObjectURL
生成),并模拟点击该链接来触发下载。对于 IE 浏览器,则使用navigator.msSaveBlob
方法。
四、结果
实现下载啦~