简介
通过浏览器上传文件可以说是一个极为常见的需求,而如果想要上传一个文件夹,那应该如何实现呢?
首先,需要实现<input>组件选取本地文件夹;其次,则是上传文件夹里的所有文件,包括子孙文件夹。对于多文件上传,则分两种情况:
1)服务端支持压缩包格式上传,支持自动压缩包解压,或者手动解压;
2)服务端不支持压缩包解压,不论是自动还是手动,比如第三方文件服务。
这里分享的是,服务端支持压缩包解压的前提下,前端将文件夹压缩打包后上传。如此,服务端解压后可以获得完整的文件夹内容,不仅是所有的文件,还包括目录结构,即使是空文件夹,也在其中。本文的实现代码以React项目为例。
代码
1)<input>组件实现本地文件夹选取
<Input
type="file"
webkitdirectory: 'true',
directory: 'true',
onClick={(e) => {
// Reset input's files
(e.target as HTMLInputElement).files = null
(e.target as HTMLInputElement).value = ''
}}
onChange={(e) =>
e.target.files && uploadFiles(e.target.files)
}
/>
大多数情况,是需要使用自定义的UI组件(比如按钮、菜单、drag&drop等)实现文件选择弹窗的触发。那就可以隐藏<input>组件,利用它的ref.current.select()触发文件选择弹窗。
2)生成压缩包
在上传之前,先利用JSZip实现多文件(包括文件夹)的压缩打包。其中,参数onProgress是打包过程进度中的回调方法,可以获知已经添加到压缩包中的文件数量。
import JSZip from 'jszip'
async function zipFile(
fileList: FileList,
onProgress: (added: number) => void,
) {
const zip = new JSZip()
let i = 0
for await (let f of fileList) {
const fileData = await readAsArrayBuffer(f)
zip.file(f.webkitRelativePath, fileData, { createFolders: true })
i++
onProgress && onProgress(i)
}
return zip.generateAsync({ type: 'blob' })
}
async function readAsArrayBuffer(file: Blob): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
let reader = new FileReader()
reader.readAsArrayBuffer(file)
reader.onload = () => {
resolve(reader.result as ArrayBuffer)
}
reader.onloadend = () => {
reject('FileReader failed')
}
})
}
3)上传压缩包
获取压缩包文件数据之后,关于上传的实现代码,因为跟文件服务是什么有关,这里就不啰嗦了。
let zipData = await zipFile(fileList, (added) => {})