HOOKS

Public Hooks package file download useDownloadBlob

Public Hooks package file download useDownloadBlob

Foreword

For front-end development that often needs to develop enterprise management background, it is essential to use tables to operate on data and encapsulate some public Hooks when optimizing code for existing projects. And in the previous article has summarized the package ‘useDownloadFile.js’ related content. Why do you need to encapsulate another ‘useDownloadBlob.js’? In fact, the previous encapsulation is code optimization based on the actual situation to meet more scenarios.

Hooks encapsulated based on the personal project environment, only this article introduces the idea of encapsulating Hooks, so the relevant code may not be applicable to others.

Project Environment

Vue 3.x + Ant Design Vue 3.x + Vite 3.x

encapsulation decomposition: create a tag download file

export function createDownload(blob, fileName, fileType) {
  if (!blob || !fileName || !fileType) return;
  const element = document.createElement('a');
  const url = window.URL.createObjectURL(blob);
  element.style.display = 'none';
  element.href = url;
  element.download = `${fileName}.${fileType}`;
  document.body.appendChild(element);
  element.click();
  if (window.URL) {
    window.URL.revokeObjectURL(url);
  } else {
    window.webkitURL.revokeObjectURL(url);
  }
  document.body.removeChild(element);
}

Encapsulation and Decomposition: Downloading Blob Files

const downloadBlob = (url, fileName = '', fileType = '', autoDownload = false) => {
  return new Promise((resolve, reject) => {
    xhr = new XMLHttpRequest();
    xhr.responseType = 'blob';
    xhr.open('get', url, true);
    xhr.onprogress = function (e) {
      progress.value = Math.floor((e.loaded / e.total) * 100);
      if (progress.value === 100) {
        progress.value = 0;
        downloading = false;
      }
    };
    xhr.onloadend = function (e) {
      if ([200, 304].includes(e.target.status)) {
        const blob = e.target.response;
        if (autoDownload) {
          createDownload(blob, fileName, fileType);
        }
        xhr = null;
        resolve(blob);
      }
    };
    xhr.onerror = function (e) {
      downloading = false;
      Modal.error({
        title: '温馨提示',
        content: '下载发生异常,请重试',
      });
      reject(e);
    };
    xhr.send();
  });
};

I believe that some readers have seen that the above two pieces of code are consistent with the’ useDownloadFile.js’ content, which is also due to the consideration of code optimization in actual work. The package should be as modular as possible, so that the content is still applicable in dealing with multiple scenarios. The previous encapsulation ‘useDownloadFile’ can already solve the requirements for file downloading in the management background. Combined with actual business, when multiple data in the same user/business need to be managed and packaged in a unified way, you can try to encapsulate the downloaded file compression package to solve the business scenario. The following section introduces the relevant implementation methods and encapsulation strategies.

Encapsulation and Decomposition: JavaScript Compression — JSZip

‘A library for creating, reading and editing .zip files with JavaScript, with a lovely and simple API .’ JSZip supports various types of resources’ uint8array, blob, arraybuffer, nodebuffer, string’, etc. It is very suitable for combining with existing packages. Two APIs are actually used: zip.file() and zip.generateAsync(). The relevant API documents have been introduced in great detail, so I won’t repeat them here. after all, the official documents are still very good ~ Promise style API is very comfortable to use ~

const zip = new JSZip(); // 创建一个Zip对象
for (let i = 0, len = fileList.length; i < len; i++) {
  const item = fileList[i];
  const fileType = item.fileType ? item.fileType : item.url.split('.').pop();
  curDownloadFileName.value = item.fileName;
  const blob = await downloadBlob(item.url);
  zip.file(`${item.fileName}.${fileType}`, blob); // 创建/更新文件到Zip File内,blob数据流
  successCount.value++;
}
downloading = false;
infoModal && infoModal.destroy();
zip.generateAsync({ type: 'blob' }  // 在当前文件夹级别生成完整的 zip 文件

Component Decomposition: User Experience Design

During the download process, the Ant Design Vue framework used in conjunction with the project can enhance the user’s perception of file download progress.

infoModal = Modal.info({
    title: '文件批量下载',
    okText: '取消下载',
    icon: h('span'),
    width: 580,
    content: () => {
      return h('div', { class: 'mt-4' }, [
      h('div', { class: 'fs-16 font-bold' }, ['文件下载过程中请勿关闭当前页面']),
      h('div', { className: 'mt-2' }, [`总文件数:${fileList.length},已下载文件数:${successCount.value}`]),
      h('div', { className: 'mt-2 ellipsis' }, [`当前下载文件名:${curDownloadFileName.value}`]),
      h('div', { className: 'mt-2' }, [`当前文件下载进度:${progress.value}%`]),
      ]);
    },
    onOk() {
      xhr.abort();
      xhr = null;
      return Promise.resolve();
    },
  });

Package decomposition: download file compression package Zip

const downloadZip = async (fileList = [], fileName) => {
  let infoModal;
  const successCount = ref(0);
  const curDownloadFileName = ref('');
  infoModal = Modal.info({
    title: '文件批量下载',
    okText: '取消下载',
    icon: h('span'),
    width: 580,
    content: () => {
      return h('div', { class: 'mt-4' }, [
      h('div', { class: 'fs-16 font-bold' }, ['文件下载过程中请勿关闭当前页面']),
      h('div', { className: 'mt-2' }, [`总文件数:${fileList.length},已下载文件数:${successCount.value}`]),
      h('div', { className: 'mt-2 ellipsis' }, [`当前下载文件名:${curDownloadFileName.value}`]),
      h('div', { className: 'mt-2' }, [`当前文件下载进度:${progress.value}%`]),
      ]);
    },
    onOk() {
      xhr.abort();
      xhr = null;
      return Promise.resolve();
    },
  });
  const zip = new JSZip();
  for (let i = 0, len = fileList.length; i < len; i++) {
    const item = fileList[i];
    const fileType = item.fileType ? item.fileType : item.url.split('.').pop();
    curDownloadFileName.value = item.fileName;
    const blob = await downloadBlob(item.url);
    zip.file(`${item.fileName}.${fileType}`, blob);
    successCount.value++;
  }
  downloading = false;
  infoModal && infoModal.destroy();
  zip
  .generateAsync({ type: 'blob' })
  .then(content => {
    createDownload(content, fileName, 'zip');
  })
  .catch(error => {
    console.error(error);
  });
};

Here, it is the main content of the previous ‘useDownloadFile’ transformation ~ In actual work, in fact, a good habit is to keep the code “updated”, after all, with the passage of time, everyone will harvest growth, then the code written before is more or less unreasonable. Or maybe you took over the “excrement mountain” code buried by your predecessor, but after all, not every boss/company is willing to give you time to drastically reconstruct ~ not only to encapsulate Hooks, but also to optimize the code with spare time. after all, in this “cold environment”, proper optimization is also part of improving human efficiency ~

Complete code for useDownloadFile.js


export function useDownloadFile() {
  let xhr = null;
  let downloading = false; // 限制同一文件同时触发多次下载
  const progress = ref(0);

  onBeforeUnmount(() => {
    if (xhr) {
      xhr.abort();
      xhr = null;
    }
  });

  // 下载文件blob
  const downloadBlob = (url, fileName = '', fileType = '', autoDownload = false) => {
    return new Promise((resolve, reject) => {
      xhr = new XMLHttpRequest();
      xhr.responseType = 'blob';
      xhr.open('get', url, true);
      xhr.onprogress = function (e) {
        progress.value = Math.floor((e.loaded / e.total) * 100);
        if (progress.value === 100) {
          progress.value = 0;
          downloading = false;
        }
      };
      xhr.onloadend = function (e) {
        if ([200, 304].includes(e.target.status)) {
          const blob = e.target.response;
          if (autoDownload) {
            createDownload(blob, fileName, fileType);
          }
          xhr = null;
          resolve(blob);
        }
      };
      xhr.onerror = function (e) {
        downloading = false;
        Modal.error({
          title: '温馨提示',
          content: '下载发生异常,请重试',
        });
        reject(e);
      };
      xhr.send();
    });
  };

  // 下载文件
  const downloadFile = async options => {
    try {
      let infoModal;
      if (downloading || !options.url || !options.fileName) return;
      downloading = true;
      options.url = options.url.replace('http://', 'https://');
      let fileType = '';
      if (options.fileType) {
        fileType = options.fileType;
      } else {
        fileType = options.url.split('.').pop();
      }
      infoModal = Modal.info({
        title: '文件下载',
        okText: '取消下载',
        icon: h('span'),
        content: () => {
          return h('div', { class: 'mt-4' }, [
          h('div', { class: 'fs-16 font-bold' }, ['文件下载过程中请勿关闭当前页面']),
          h('div', { className: 'mt-2' }, [`当前下载进度 ${progress.value}%`]),
          ]);
        },
        onOk() {
          xhr.abort();
          xhr = null;
          return Promise.resolve();
        },
      });
      await downloadBlob(options.url, options.fileName, fileType, true);
      downloading = false;
      infoModal && infoModal.destroy();
    } catch (e) {
      console.error(e);
    }
  };

  // 下载文件压缩包zip
  const downloadZip = async (fileList = [], fileName) => {
    let infoModal;
    const successCount = ref(0);
    const curDownloadFileName = ref('');
    infoModal = Modal.info({
      title: '文件批量下载',
      okText: '取消下载',
      icon: h('span'),
      width: 580,
      content: () => {
        return h('div', { class: 'mt-4' }, [
        h('div', { class: 'fs-16 font-bold' }, ['文件下载过程中请勿关闭当前页面']),
        h('div', { className: 'mt-2' }, [`总文件数:${fileList.length},已下载文件数:${successCount.value}`]),
        h('div', { className: 'mt-2 ellipsis' }, [`当前下载文件名:${curDownloadFileName.value}`]),
        h('div', { className: 'mt-2' }, [`当前文件下载进度:${progress.value}%`]),
        ]);
      },
      onOk() {
        xhr.abort();
        xhr = null;
        return Promise.resolve();
      },
    });
    const zip = new JSZip();
    for (let i = 0, len = fileList.length; i < len; i++) {
      const item = fileList[i];
      const fileType = item.fileType ? item.fileType : item.url.split('.').pop();
      curDownloadFileName.value = item.fileName;
      const blob = await downloadBlob(item.url);
      zip.file(`${item.fileName}.${fileType}`, blob);
      successCount.value++;
    }
    downloading = false;
    infoModal && infoModal.destroy();
    zip
    .generateAsync({ type: 'blob' })
    .then(content => {
      createDownload(content, fileName, 'zip');
    })
    .catch(error => {
      console.error(error);
    });
  };

  return {
    downloadFile,
    downloadZip,
  };
}

Notes and Remarks

Since this article is based on the optimization of the actual package, the Hooks code in the actual project is still called useDownloadFile. [Optimize code/overlay function without destroying the original structure or introducing as much as possible]]

Related posts

View all →