前端文件下载方法总结

前端文件下载方法总结

1、open或location.href

最简单最直接的方式,跟a标签访问下载链接一样

前提:下载地址不需要鉴权 以及 不存在跨域问题

javascript 复制代码
window.location.href = url // url为下载地址
window.open('downloadFile.zip');

优点:简单方便

缺点:

  • 直接访问可能会覆盖当前页面地址
  • 不能添加header,也就不能进行鉴权
  • 会出现URL长度限制问题
  • 需要注意url编码问题
  • 浏览器可直接浏览的文件类型是不提供下载的,如txt、png、jpg、gif、pdf等
  • 无法知道下载的进度

2、用a标签下载

a标签可以访问下载文件的地址。如果 是对于浏览器可以直接浏览的文件类型:jpg\png\gif\txt等,a标签不能直接下载。

所以,在html5中,a标签提供了 download属性。

简单用法:

javascript 复制代码
<a href="example.jpg" download>点击下载</a>

//如果带属性值 指定下载的文件名,即重命名下载文件。不设置的话默认是文件原本名。
<a href="example.jpg" download="test">点击下载</a>

动态a标签:

下面的url即文件或接口的地址

如需要额外参数,通过url后问号拼接参数,后端get请求方式接收

服务端需要配置url资源的响应头Content-disposition值为attachment,浏览器识别会调用下载弹窗,进一步配置filename值即下载的默认文件名。

javascript 复制代码
// 封装
function downloadFile (url, name) {
  url = url || ''
  name = name || ''
  let ele = document.createElement('a')
  ele.target = '_blank'
  ele.href = url
  ele.download = name
  ele.click()
  ele = null
}

// 调用
downloadFile(url)

判断浏览器是否支持download属性:

javascript 复制代码
const isSupport = 'download' in document.createElement('a');

需要注意一些信息:

  • Edge 13在尝试下载data url链接时会崩溃。
  • Chrome 65及以上版本只支持同源下载链接。
  • Firefox只支持同源下载链接。

基于上面描述,如果你尝试下载跨域链接,那么其实download的效果就会没了,跟不设置download表现一致。即浏览器能预览的还是会预览,而不是下载。

对于在跨域下不能下载可浏览的文件,得跟后端协商,在后端层做多一层转发,最终返回给前端的文件链接跟下载页同域。

优点:download属性能解决不能直接下载浏览器可浏览的文件

缺点:

  • 不能下载跨域下的浏览器可浏览的文件
  • 不能进行鉴权
  • url资源或接口响应头配置了filename时,前端无法自定义下载文件名。
  • 有兼容性问题,特别是IE
  • 前端需要自定义展示下载进度条需求时无法支持。

3、利用Blob下载,发送ajax请求api获取文件流进行下载

这种方法除了能利用已知文件地址路径进行下载外,还适用于需要调取API获取文件流下载的形式。

有些时候后端不会直接提供一个下载地址给你直接访问,而是要调api。

方法:

前端请求接口设置responseType: 'blob', 统一 转为Blob 数据,然后利用URL.createObjectUrl生成url地址,赋值在a标签的href属性上,结合download进行下载.

blob 是 js 里表示二进制文件的对象。
target.response就是一个Blob对象,打印出来会看到两个属性size和type。

2种方法代码参考

文件下载方法参考,第二种可设置下载进度。

参考:https://blog.csdn.net/u010059669/article/details/122623034

javascript 复制代码
/**
 * 第一种方法:
 * @param {String} path - 下载地址/下载请求地址。
 * @param {String} name - 下载文件的名字/重命名(考虑到兼容性问题,最好加上后缀名)
 */
downloadFile (path, name) {
    const xhr = new XMLHttpRequest();
    xhr.open('get', path);
    xhr.responseType = 'blob';
    xhr.send();
    xhr.onload = function () {
        if (this.status === 200 || this.status === 304) {
            // 如果是IE10及以上,不支持download属性,采用msSaveOrOpenBlob方法,但是IE10以下也不支持msSaveOrOpenBlob
            if ('msSaveOrOpenBlob' in navigator) {
                navigator.msSaveOrOpenBlob(this.response, name);
                return;
            }
            // const blob = new Blob([this.response], { type: xhr.getResponseHeader('Content-Type') });
            // const url = URL.createObjectURL(blob);
            const url = URL.createObjectURL(this.response);
            const a = document.createElement('a');
            a.style.display = 'none';
            a.href = url;
            a.download = name;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }
    };
}

/**
 * 第二种方法:
 * @description: ajax下载/导出文件
 * @param {string} url 资源或接口地址,可携带参数
 * @param {function} progressCallback 选填,前端自定义的下载进度回调
 */
function downloadFromApi (url, progressCallback) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('get', url)
    xhr.responseType = 'blob'
    xhr.onload = e => {
      if (xhr.status === 200) {
        const response = xhr.response
        resolve(response)

        const dispoition = xhr.getResponseHeader('Content-Disposition') || ''
        const nameStr = dispoition.split(';')[1] || ''
        const fileName = decodeURIComponent(nameStr.split('=')[1] || '')

        if (window.navigator.msSaveOrOpenBlob) {
          // 兼容处理,ie下的blob下载
          window.navigator.msSaveOrOpenBlob(response, fileName)
        } else {
          const href = URL.createObjectURL(response)
		  let ele = document.createElement('a')
		  ele.target = '_blank'
		  ele.href = href
		  ele.download = fileName
		  ele.click()
		  ele = null
		  URL.revokeObjectURL(href)
        }
      } else {
        reject(new Error(`${xhr.status}:请求失败`))
      }
    }
    xhr.error = err => {
      reject(err)
    }
    xhr.onprogress = e => {
      if (e.lengthComputable) {
        const percentComplete = e.loaded / e.total
        progressCallback && progressCallback(percentComplete)
      }
    }
    xhr.send(null)
  })
}

下载进度设置的原理

服务端需要配置响应头的Content-Length,值为文件大小(未配置的话会在文件传输完成后才弹窗提示下载,响应感知太慢)。

前端监听xhr对象的progress事件:

javascript 复制代码
xhr.onprogress = e => {
  if (e.lengthComputable) {
    const percentComplete = e.loaded / e.total
    // 打印当前已完成的进度比例
    console.log(percentComplete)
  }
}

另一种封装方式(axios 请求)

如果是封装了axios请求,可以在调请求后,使用下面的封装的downLoadFile 方法,使用示例:

js 复制代码
/*
httpRequest: 封装的下载API请求,记得设置responseType = 'blob'
url: 接口
*/
 httpRequest(url).then(async (res) => {
   console.log('下载请求返回:', res);
   const { data, response } = res;
   if (data && response.status === 200) {
   	// 调用封装好的下载文件的方法
     downLoadFile(data, response);
   } 
 }).catch(async (err) => {
   console.log(err);
 });
 
/**
 * 下载文件
 * @param {any} data - 下载请求返回blob对象的data, 打印出来会有一个size和type 属性。
 * @param {Response} response- 请求的response
 */
const downLoadFile = (data: any, response: Response) => {
  let contentType = response.headers.get('content-type') || undefined;
  let contentDisposition = response.headers.get('content-disposition') || undefined;

  contentType = isExistValue(contentType) ? contentType : undefined;
  contentDisposition = isExistValue(contentDisposition) ? contentDisposition : undefined;

	// 文件名提取
  let filename = 'xxx';
  if (typeof contentDisposition === 'string') {
    contentDisposition = valueToLowerCase(contentDisposition);
    try {
      // 文件名提取
      filename = contentDisposition.match(/filename=(.*)/)[1];
    } catch (err) {
      console.log(err);
      // @ts-ignore
      filename = contentDisposition;
    }
  }
  // 创建一个 Blob 对象
  const blob = new Blob([data], { type: contentType });
  // @ts-ignore
  if (typeof window.navigator.msSaveBlob !== 'undefined') {
    // 兼容IE,window.navigator.msSaveBlob:以本地方式保存文件
    window.navigator.msSaveBlob(blob, decodeURI(filename));
  } else {
    const href = window.URL.createObjectURL(blob);
    const eLink = document.createElement('a'); // 创建一个<a>标签
    eLink.style.display = 'none'; // 隐藏标签
    eLink.href = href; // 配置href,指向本地文件的内存地址
    eLink.download = decodeURI(filename);
    document.body.appendChild(eLink);
    eLink.click();
    // 释放URL 对象
    document.body.removeChild(eLink);
    // 释放掉blob对象
    URL.revokeObjectURL(href);
  }
};

图片预览

如果是图片预览,也是后端返回的地址,需要带上token去请求。需要用到base64。伪代码参考思路:

javascript 复制代码
const [imgSrc, setImgSrc] = useState('');// 图片预览的src

useEffect(() => {
 const url = 'xxx.xx.xx/img/wx.png';
  // httpRequest自己封装的axios请求 , responseType 为 'blob'
  httpRequest(url).then(async (res) => {
    console.log('下载返回:', res);
    const { data, response } = res;
    if (data && response.status === 200) {
      // 生成图片预览的src
      const src = await blobToBase64(data);
      setImgSrc(src);
    }
  });
}, []);


/**
 * Blob 图像对象预览
 * */
const blobToBase64 = (file: Blob) => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result);
    reader.onerror = (error) => reject(error);
  });
};

// 在Image标签中使用:
<Image width="80%" alt={name} src={imgSrc} />

优点:

  • 能解决不能直接下载浏览器可浏览的文件
  • 可添加鉴权信息

缺点: 兼容性问题,IE10以下不可用

相关推荐
庸俗今天不摸鱼10 分钟前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下17 分钟前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox27 分钟前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞30 分钟前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行30 分钟前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_5937581031 分钟前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周34 分钟前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队1 小时前
Vue自定义指令最佳实践教程
前端·vue.js
Jasmin Tin Wei1 小时前
蓝桥杯 web 学海无涯(axios、ecahrts)版本二
前端·蓝桥杯
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring