JS如何下载资源文件,并且兼容IE、Edge

4,782 阅读4分钟

前言

昨天晚上产品经理对我说,这边需要做一个功能: 点击按钮,下载所有的资源文件。

这不挺简单,心想,直接通过 a.download 直接解决不就 ok 了。谁知...

我尿了... 对不起,是我年轻了。

毛毛躁躁

与后台确定了一下返回的数据,这边后台会返回一个 资源链接资源名。我们通过这个资源链接,去浏览器中输入,是可以查看的。

于是我兴致勃勃的通过 a.download 去下载,发现,问题出现了

下载下来的不知道是什么鸡儿玩意,根本无法打开,文件损坏

再理理思路

首先确保这个文件是否可以下载

例如我们的图片资源是 : httpp://images/b532cecf17544c7784c321937eaaba20 (故意打错https)

然后我们给他加个attname : httpp://www.baidu.com/images/b532cecf17544c7784c321937eaaba20?attname=test.jpg(故意打错https)

发现,通过这种方式,下载后就可以正常打开了,如果直接下载,那就会报 根本无法打开,文件损坏

最终解决

通过 download 属性指定下载的名字,然后模拟一个点击事件。

function downloadURL(url, name = '') {
  const link = document.createElement('a');
  link.download = name;
  link.href = url;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}

但是呢,有问题。来看看有啥问题,首先,这个兼容性上,就有问题了。可以看到 ie 不支持,低版本的 ios safari 也不行,兼容性问题。

我们可通过 Blob 二进制对象进行下载。

最终,通过 Chrome、FireFox、360 均可下载,但在 IE 和 Edge 中就不行,但是也没报错。

通过查询资料,发现 IE / Edge 和高大上的 Chrome /Firefox 对于 window.URL.createObjectURL 创建 Blob 链接最直观的区别在于 : 得到的 blob 链接形式不一样

Chrome 和 Firefox 会生成的带有当前域名的标准 blob 链接形式(例如 https : //www.baidu.com/86e01467-6654-4b74-98b3-ca25f396bc2f)

而 IE 和 Edge 会生成的不带域名的 blob 链接(例如 242CACD6-06D5-4145-A6DA-55DBE47409DB),所以如果用上面代码的方式,是下载不了文件的,并且浏览器也不会报错。

解决方案 : 使用 window.navigator.msSaveOrOpenBlob(blob, filename),代替 window.URL.createObjectURL

相关代码

/**
 * @desc 资源下载文 件
 * @Support Image/Audio/Video/Word/PDF
 * @param {Object} resource - 资源文件
 * resource: {
 *     link: '', 文件下载地址
 *     reName: '', 用户自定义导出的文件名
 *     fileType: '', 源文件类型,当文件名为空或不携带类型时,此字段必须需要
 *     fileName: '', 源文件名,尽可能带有类型,如 a.jpg、b.mp3
 * }
 */
function fetchBlob(url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.responseType = 'blob';

    xhr.onload = function() {
      if (xhr.status === 200) {
        resolve(xhr.response);
      } else {
        reject(new Error(xhr.statusText || 'Download failed.'));
      }
    };
    xhr.onerror = function() {
      reject(new Error('Download failed.'));
    };
    xhr.send();
  });
}

function downloadURL(url, name = '') {
  const link = document.createElement('a');
  link.download = name;
  link.href = url;
  if ('download' in document.createElement('a')) {
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  } else {
    // 对不支持download进行兼容
    click(link, (link.target = '_blank'));
  }
}

// 参考 FileSaver.js ,链接地址
// https://github.com/eligrey/FileSaver.js/blob/master/src/FileSaver.js
function click(node) {
  try {
    node.dispatchEvent(new MouseEvent('click'));
  } catch (e) {
    let evt = document.createEvent('MouseEvents');
    console.log(evt);
    evt.initMouseEvent(
      'click',
      true,
      true,
      window,
      0,
      0,
      0,
      80,
      20,
      false,
      false,
      false,
      false,
      0,
      null
    );
    node.dispatchEvent(evt);
  }
}

/**
 * @desc 拼接下载链接
 * 添加 attname 属性用于下载资源文件
 */
function retrieveReallyLink(link, name) {
  let originUrl = `${link}?attname=${name}`;
  if (/\?/.test(link)) {
    originUrl = `${link}&attname=${name}`;
  }
  return originUrl;
}
/**
 * @desc 处理文件名
 * 1,用户自定义导出的文件名reName为准,无reName,以fileName为准,无fileName,给定默认文件名
 * 2. type类型以fileName的为主,fileName不存在或不携带类型,则以传入的 fileType 为主
 */
function retrieveReallyName(resource) {
  const { fileName = '', fileType = '', reName = '' } = resource;
  let type = fileType;
  let originName = '';
  if (fileName && fileName.indexOf('.') > -1) {
    type = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
  }
  if (reName) {
    originName = `${reName}${type}`;
  } else if (fileName) {
    originName =
      fileName.indexOf('.') > -1 ? `${fileName}` : `${fileName}${type}`;
  } else {
    originName = `新建下载文件${type}`;
  }
  return originName;
}

/**
 * @desc 资源下载
 * @param {Object} resource
 */
export function downloadFile(resource) {
  const { link = '' } = resource;
  const originName = retrieveReallyName(resource);
  const originUrl = retrieveReallyLink(link, originName);

  return fetchBlob(originUrl)
    .then(resp => {
      if (resp.blob) {
        return resp.blob();
      } else {
        return new Blob([resp]);
      }
    })
    .then(blob => {
      if ('msSaveOrOpenBlob' in navigator) {
        window.navigator.msSaveOrOpenBlob(blob, originName);
      } else {
        const obj = URL.createObjectURL(blob);
        downloadURL(obj, originName);
        URL.revokeObjectURL(obj);
      }
    })
    .catch(err => {
      throw new Error(err.message);
    });
}

/**
 * @desc 获取文件类型,当后台文件名为空或不携带类型时,需要通过此方法获取文件类型
 * @param {Number} resourceType 资源类型,参考 constant 文件定义的resoureType
 */
export function retrieveFileType(resourceType) {
  let resultType = '';
  switch (resourceType) {
    case 1:
      resultType = '.mp4';
      break;
    case 2:
      resultType = '.png';
      break;
    case 3:
      resultType = '.doc';
      break;
    case 4:
      resultType = '.ppt';
      break;
    case 5:
      resultType = '.mp3';
      break;
    case 8:
      resultType = '.pdf';
      break;
    default:
      break;
  }
  return resultType;
}

最终效果图

友情链接