Blog.

浏览器中的多文件保存

Cover Image for 浏览器中的多文件保存
Coda
Coda

过时的方式

使用动态创建的 a 标签 添加 link 和 download 属性设置文件名来模拟点击下载实现的文件保存,这样在多个文件的时候不够优雅,并且有些浏览器可能会因为防止恶意下载阻止掉一些文件

示例代码

function download(data: { blob: Blob; filename: string }[]) {
  for (const { blob, filename } of data) {
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url // file url
    a.download = filename // file name
    a.click()
    URL.revokeObjectURL(url)
  }
}

在搜索解决办法时还看到过使用 zip.js 把多个文件压缩成一个 zip 包来保存的技巧,现在有了新 API 就不用这么麻烦了。 这里提一嘴 wasm 包装的 7-zip 网站,支持所有桌面端的 7-zip 功能。目前受限于 RAM FS 只是不能处理超过 2GB 的文件,这个在将来可能会被解决。

在 chrome/edge 86 可以使用的 File System API

在网站首次调用 showDirectoryPicker 用户选择一个目录后会弹出一个权限请求,这个权限请求被允许后不会再次显示

async function saveFile(data: { blob: Blob; filename: string }[]) {
  const isSupportShowSaveFilePicker = 'showSaveFilePicker' in self
  if (!isSupportShowSaveFilePicker) return download(data) //

  // fileHandle is an instance of FileSystemFileHandle..
  async function writeFile(fileHandle: FileSystemFileHandle, contents: Blob) {
    // Create a FileSystemWritableFileStream to write to.
    const writable = await fileHandle.createWritable()
    // Write the contents of the file to the stream.
    await writable.write(contents)
    // Close the file and write the contents to disk.
    await writable.close()
  }

  // If only one file, use showSaveFilePicker to reduce permission prompts.
  if (data.length === 1) {
    try {
      const [{ blob, filename }] = data
      const [description, ext] = filename.split('.')
      const handle = await window.showSaveFilePicker({
        types: [
          {
            description,
            accept: {
              [blob.type as MIMEType]: [`.${ext}`],
            },
          },
        ],
      })
      await writeFile(handle, blob)
    } catch (error) {
      // continue regardless of error
    }

    return
  }

  try {
    const dirHandle = await window.showDirectoryPicker({
      mode: 'readwrite',
    })

    for (const { blob, filename } of data) {
      const fileHandle = await dirHandle.getFileHandle(filename, {
        create: true,
      })
      await writeFile(fileHandle, blob)
    }
  } catch (error) {
    // continue regardless of error
  }
}

这里给出的代码仅用于保存文件,读取文件夹列出目录/文件以及更多接口以及用法参考下面链接

Reference

https://developer.mozilla.org/docs/Web/API/File_System_API https://developer.chrome.com/docs/capabilities/web-apis/file-system-access

473