前端多文件打包下载方案

1,988 阅读2分钟

这周优化了一个批量下载的功能,简单做一下记录。

原来的下载方式是使用在DOM上挂载iframe或a标签实现批量下载,这种方式的问题在于:**乱。**所有的文件一股脑全部丢到浏览器默认的下载文件夹里,所有文件的文件名、下载顺序都无法控制。

后来产品提出了需要统一打包下载,于是设计了如下方案

  • 通过http请求获取图片内容
  • 使用第三方库 JSZip 创建压缩包并写入文件
  • 调用第三方库 FileSaver 将文件保存到本地

关于上面提到两个三方库的作用:

JSZip:是一个用于创建、读取、编辑zip的JavaScript库

FileSaver:提供了保存web文件到本地硬盘的API

开始

我们在项目中安装相关依赖

npm install jszip file-saver --save

创建一个js文件,并引入依赖

import JSZip from 'jszip'
import { saveAs } from 'file-saver'

首先,我们需要先写一个请求图片内容的方法。这里使用axios发送http请求,响应类型设置为blob

import axios from 'axios'
function getContent(url) {
  return new Promise((resolve, reject) => {
    axios({
      headers: {
        'Cache-control': 'no-cache'
      },
      url,
      method: 'GET',
      responseType: 'blob'
    }).then(data => {
      resolve(data.data)
    }).catch(error => {
      reject(error)
    })
  })
}

接下来,创建一个最终对外暴露的方法。通过JSZip提供的API创建压缩包对象,遍历文件列表调用上面的 getContent 方法获取图片二进制并写入压缩包对象,最终将整个压缩包对象转换城二进制对象,通过 FileSaver 提供的 saveAs 方法下载到本地

export function download(images, folderName) {
  const zip = new JSZip()
  const folder = zip.folder(folderName)
  const promises = images.map(image => {
    return getContent(image).then(data => {
      // 获取图片名
      const name = image.split('/')[image.split('/').length - 1]
      folder.file(name, data, { binary: true })
    })
  })
  Promise.all(promises).then(() => {
    zip.generateAsync({ type: 'blob' }).then(content => {
      saveAs(content, folderName)
    })
  })
}

这里需要注意的是,此业务场景是针对下载开放跨域权限对象存储上的文件,没有开放跨域设置的需要在阿里云或腾讯云控制台设置允许跨域请求规则。

随着项目扩展,出现了一些需要下载常规网络图片的场景,然而使用axios请求一般网络图片总是出现跨域问题,我们总不能去要求别人的服务器开放允许跨域吧。于是,我把目光放到另一个http请求工具:fetch

在mdn官方介绍下看到这样一句话:fetch不会发送跨域 cookie

于是,我们把 getContent 方法替换一下,最终变成下面这样的方法:

export function download(imageUrls, folderName = '下载') {
  const zip = new JSZip()
  const folder = zip.folder(folderName)
  const downloadTasks = imageUrls.map(imageUrl => {
    return fetch(imageUrl).then(imageContent => {
      const name = imageUrl.split('/')[imageUrl.split('/').length - 1]
      // 写入二进制内容文件
      folder.file(name, imageContent.blob(), { binary: true })
    })
  })
  Promise.all(downloadTasks).then(() => {
    zip.generateAsync({ type: 'blob' }).then(content => {
      // 写入本地
      saveAs(content, folderName)
    })
  })
}