纯原创文章,转载请说明来源。
一、背景
要实现一个下载功能,后端直接返回了一个图片的地址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') > -1return 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 = fileNameelink.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.pathnameconst 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
方法。
四、结果
实现下载啦~