前端文件下载方法总结

前端文件下载方法总结

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以下不可用

相关推荐
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062065 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb5 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角5 小时前
CSS 颜色
前端·css
九酒6 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
浪浪山小白兔7 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579657 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me8 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者8 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架