目前组件库中关于桌面拖拽文件上传基本都是开箱即用的,这里就不在给大家讲解,但是拖拽文件夹上传实现是比较少的,原因是拖拽拿到的fils中的仅仅对文件夹的描述,至于文件夹内部信息是不会像使用input标签上传文件夹那样,将内部文件信息全部拍平汇入files中为一个fileList,所以需要进行特殊处理,将内部数据拿到。
一、使用input标签上传文件夹
<input
multiple="false"
type="file"
ref="folderUploader"
webkitdirectory
class="file-input"
@change="uploadHandler($event, 'folder')"
title="文件与文件夹无法同时上传,单次仅支持上传一个文件夹"
/>
在uploadHandler的event中拿到如下信息:
二、使用拖拽上传文件夹
<!-- 容器组件-->
<FileDragWrap
v-show="!!draggingStatus"
:draggingStatus="draggingStatus"
:uploadExts="uploadExts"
:uploadHint="uploadHint"
@change="(val) => (draggingStatus = val)"
@success="handleDragSuccess"
@error="handleDragError"
/>
<!-- 拖拽区域组件FileDragWrap-->
<div
class="cme-drop-file-hint__wrap"
@dragover="handleDragWrapDragOver"
@drop="handleDragWrapDrop"
>
<div class="cme-drop-file-hint__icon">
<i class="icon-cme icon-cme-upload"></i>
</div>
<p class="cme-drop-file-hint__text">释放鼠标上传文件</p>
</div>
在handleDragWrapDrop的event中拿到的信息:
我们发现没有文件夹内部信息描述,所以我们需要使用chrome特有的方法webkitGetAsEntry 去递归拿到所有的信息进行拍平,转换为可上传使用的File对象。
/**
* @description 拖拽事件处理
* @param 拖拽事件体
*/
async handleDragWrapDrop(event: DragEvent) {
const { dataTransfer } = event
if (!dataTransfer) return
const { files } = dataTransfer
// 检查类型
const allowFile = find(files, (item) => {
return some(this.uploadExts, (ext) => {
return item.name.toLowerCase().endsWith(`.${ext.toLowerCase()}`) || item.type === ''
})
})
if (allowFile) {
if (files.length === 1) {
const { items, files } = dataTransfer
const item = items[0].webkitGetAsEntry()// 获取当前文件夹的Entry(webkit内核特有),然后去递归Entry
if (item) {
// 说明是文件夹
if (item.isDirectory) {
const filesList: any[] = []
await this.scanFiles(item, filesList)
const copyEvent: any = { dataTransfer: { files: filesList } }
this.$emit('success', { event: copyEvent, msg: '', type: 'folder' })
} else {
this.$emit('success', { event, msg: '', type: 'file' })
}
}
} else {
this.$emit('success', { event: {}, msg: '单次拖拽仅支持单个文件上传' })
}
} else {
this.$emit('error', { event: {}, msg: '上传文件格式错误' })
}
}
递归扫描文件:
/**
* @description 扫描文件夹中所有的文件夹子和文件,将数据拍平为可上传使用的File对象
* @param item FileSystemDirectoryEntry 对象实例(目录实体)
*/
scanFiles(entry: any, filesList: any[]) {
return new Promise((resolve, reject) => {
if (entry.isDirectory) {
const directoryReader = entry.createReader()
directoryReader.readEntries(
async (entries: any[]) => {
entries.forEach(async (entry: any, index: number) => {
await this.scanFiles(entry, filesList)
if (index === entries.length - 1) {
resolve(1)
}
})
},
(e: any) => {
reject(e)
}
)
} else {
entry.file(
async (file: any) => {
const path = entry.fullPath.substring(1)
/**修改webkitRelativePath 是核心操作,原因是拖拽会的事件体中webkitRelativePath是空的,而且webkitRelativePath 是只读属性,普通赋值是不行的。所以目前只能使用这种方法将entry.fullPath 赋值给webkitRelativePath**/
const newFile = Object.defineProperty(file, 'webkitRelativePath', {
value: path,
})
filesList.push(newFile)
resolve(1)
},
(e: any) => {
reject(e)
}
)
}
})
}
最后我们通过这种scanFiles方式获得filesList 给到上传接口就可以进行上传文件夹中的内容,当然上面的组件,文件和文件夹都是支持的,希望能够帮到您~