关于前端实现文件下载功能

在Web开发中,前端下载文件是一个常见的需求。根据文件来源和下载方式的不同,前端下载文件可以采用多种方案。下面将介绍几种常见的前端下载文件方案。

1、通过 window.open() 打开新页面下载文件

js 复制代码
window.open(`url`, "_self");

使用场景:下载 excel 文件,后端提供接口,接口返回的是文件流,可以直接使用 window.open(),最简单的方式。

  • 优点:最简洁;
  • 弊端:当参数错误时,或其它原因导致接口请求失败,这时无法监听到接口返回的错误信息,需要保证请求必须是正确的且能正确返回数据流,不然打开页面会直接输出接口返回的错误信息,体验不好。

2、通过 a 标签打开新页面下载文件

js 复制代码
export const exportFile = (url, fileName) => {
  const link = document.createElement("a");
  const body = document.querySelector("body");

  link.href = url;
  link.download = fileName;

  // fix Firefox
  link.style.display = "none";
  body.appendChild(link);

  link.click();
  body.removeChild(link);
};

通过 a 标签下载的方式,同 window.open()是一样的,唯一的优点是可以自定义下载后的文件名,a 标签里有 download 属性可以自定义文件名。

弊端:同 window.open()方式一样,无法监听错误信息。

问题:以上两种方式,当在下载.mp3 格式,或者视频文件时,浏览器会直接播放该文件,而达不到直接下载的功能,此时,当下载音视频文件时无法使用以上两种方式。

3、通过文件流的方式下载

为了解决.mp3 文件下载所带来的问题,通过 ajax 请求返回 Blob 对象,或者 ArrayBuffer 对象。

获取文件

如下:通过原生 ajax 请求返回 Blob 对象

js 复制代码
const getBlob = (url) => {
  return new Promise((resolve) => {
    const xhr = new XMLHttpRequest();

    xhr.open("GET", url, true);
    xhr.responseType = "blob";
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response);
      }
    };

    xhr.send();
  });
};

同样,也可以通过 axios 返回 ArrayBuffer 对象来处理

js 复制代码
import axios from "axios";
const getFile = (url) => {
  return new Promise((resolve, reject) => {
    axios({
      method: "get",
      url,
      responseType: "arraybuffer",
    })
      .then((data) => {
        resolve(data.data);
      })
      .catch((error) => {
        reject(error.toString());
      });
  });
};

ArrayBuffer(又称类型化数组)

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 不能直接操作,而是要通过类型数组对象或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。 Blob(Binary Large Object): 二进制大数据对象

Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是 JavaScript 原生格式的数据。File 接口基于 Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。 注意:

如果下载文件是文本类型的(如: .txt, .js 之类的), 那么用 responseType: 'text'也可以, 但是如果下载的文件是图片, 视频之类的, 就得用 arraybuffer 或 blob,更多详情请查看 MDN 通过 ajax 请求的方式下载文件,可以解决第 1、2 中存在的弊端,当请求错误时或捕获到错误信息

保存文件

当获取到文件后,这时需要保存文件

js 复制代码
const saveAs = (blob, filename) => {
  if (window.navigator.msSaveOrOpenBlob) {
    navigator.msSaveBlob(blob, filename);
  } else {
    const link = document.createElement("a");
    const body = document.querySelector("body");

    link.href = window.URL.createObjectURL(blob); // 创建对象url
    link.download = filename;

    // fix Firefox
    link.style.display = "none";
    body.appendChild(link);

    link.click();
    body.removeChild(link);

    window.URL.revokeObjectURL(link.href); // 通过调用 URL.createObjectURL() 创建的 URL 对象
  }
};

为了解决 IE(ie10 - 11)和 Edge 无法打开 Blob URL 链接的方法,微软自己有一套方法 window.navigator.msSaveOrOpenBlob(blob, filename),打开并保存文件,以上代码做了简单的兼容,navigator.msSaveBlob(blob, filename)是直接保存。注意,此为非标准功能,详情请查看相关文档。

以下为完整代码(原生版)

js 复制代码
const getBlob = ({ method = "GET", url }) => {
  return new Promise((resolve) => {
    const xhr = new XMLHttpRequest();

    xhr.open(method, url, true);
    xhr.responseType = "blob";
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response);
      }
    };

    xhr.send();
  });
};

const saveAs = (blob, filename) => {
  if (window.navigator.msSaveOrOpenBlob) {
    navigator.msSaveBlob(blob, filename);
  } else {
    const link = document.createElement("a");
    const body = document.querySelector("body");

    link.href = window.URL.createObjectURL(blob); // 创建对象url
    link.download = filename;

    // fix Firefox
    link.style.display = "none";
    body.appendChild(link);

    link.click();
    body.removeChild(link);

    window.URL.revokeObjectURL(link.href); // 通过调用 URL.createObjectURL() 创建的 URL 对象
  }
};

export const download = (url, filename = "") => {
  getBlob({ url }).then((blob) => {
    saveAs(blob, filename);
  });
};

用后台的 filename 的话,可以从 header 的 content-disposition 中去获取。

4、如何实现批量下载,且打包文件

在第 3 点的基础上,如果要实现批量下载,那能做到的只是连续多次调用 download 方法,这样无法批量集中的下载文件。这个时候就需要能够对已获取到的文件流,进行一个打包的操作,然后一次下载完毕。

这时,需要用到两个库 jszip 和 file-saver

完整的思路:通过 ajax 获取文件,然后用 jszip 压缩文件, 再用 file-saver 生成文件

  • 获取文件 同上(第 3 点)

  • 打包文件

js 复制代码
export const download = () => {
  const urls = ["url", "url"]; //需要下载的路径
  const zip = new JSZip();
  const cache = {};
  const promises = [];
  urls.forEach((item) => {
    const promise = getBlob(item).then((data) => {
      // 下载文件, 并存成ArrayBuffer对象
      zip.file("下载文件名", data, { binary: true }); // 逐个添加文件
      cache[item.fileName] = data;
    });
    promises.push(promise);
  });

  Promise.all(promises).then(() => {
    zip.generateAsync({ type: "blob" }).then((content) => {
      // 生成二进制流
      FileSaver.saveAs(content, `打包下载.zip`); // 利用file-saver保存文件
    });
  });
};

完整代码

js 复制代码
/**
 * 获取文件
 * @param url
 * @returns {Promise<any>}
 */
const getBlob = (url) => {
  return new Promise((resolve) => {
    const xhr = new XMLHttpRequest();

    xhr.open("GET", url, true);
    xhr.responseType = "blob";
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response);
      }
    };

    xhr.send();
  });
};

/**
 * 批量打包zip包下载
 * @param urlArr Array [{url: 下载文件的路径, fileName: 下载文件名称}]
 * @param filename zip文件名
 */
export const download = (urlArr, filename = "打包下载") => {
  if (!urlArr.length > 0) return;
  const zip = new JSZip();
  const cache = {};
  const promises = [];
  urlArr.forEach((item) => {
    const promise = getBlob(item.url).then((data) => {
      // 下载文件, 并存成ArrayBuffer对象
      zip.file(item.fileName, data, { binary: true }); // 逐个添加文件
      cache[item.fileName] = data;
    });
    promises.push(promise);
  });

  Promise.all(promises).then(() => {
    zip.generateAsync({ type: "blob" }).then((content) => {
      // 生成二进制流
      FileSaver.saveAs(content, `${filename}.zip`); // 利用file-saver保存文件
    });
  });
};

注意:由于通过浏览器进行打包压缩,如果文件过大,或者下载的内容过多,可能导致浏览器崩溃。

总结

综上所述,前端下载文件的方案多种多样,开发者应根据具体需求和场景选择合适的方案。对于简单的静态文件下载,可以直接使用<a>标签;对于动态生成的文件或需要通过Ajax请求获取的文件,可以使用JavaScript或第三方库来实现下载功能。无论选择哪种方案,都应注意文件的安全性、兼容性和性能问题,以确保良好的用户体验。

本文由mdnice多平台发布

相关推荐
GISer_Jing34 分钟前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
落霞的思绪1 小时前
CSS复习
前端·css
咖啡の猫3 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲6 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5816 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路6 小时前
GeoTools 读取影像元数据
前端
ssshooter7 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
Jerry8 小时前
Jetpack Compose 中的状态
前端
dae bal8 小时前
关于RSA和AES加密
前端·vue.js
柳杉9 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化