记录一下代码,方便以后自己使用,大部分代码都是抄过来的。。。看懂后根据自己的需求改的
<div class="upload_demo">
<div class="table_info">
<div>
<el-button @click="clearList">
清空列表
</el-button>
<el-button
:disabled="tableData.length >= 100"
@click="uploadFile(fileRef)"
>
上传文件
</el-button>
<el-button
:disabled="tableData.length >= 100"
@click="uploadFile(folderRef)"
>
上传文件夹
</el-button>
</div>
<div class="info">
<span>{{ tableData.length }}/ 100文件</span>
<span>大小 {{ handleStorage(totalSize) }}</span>
</div>
</div>
<div
ref="dragRef"
draggable="true"
class="drag tableBox"
:class="{ drag_border: tableData.length }"
@dragover="dragover"
@drop="onDrop"
>
<div
v-show="!tableData.length"
class="el-upload__text"
>
<i
class="el-icon-upload"
style="margin-right: 6px"
/>拖拽文件/文件夹到此处或者
<el-button
link
type="primary"
@click="addFiles"
>
添加文件
</el-button>
</div>
<div
v-show="!tableData.length"
class="el-upload__text"
>
文件上传数量不能超过100个,总大小不超过5GB
</div>
<el-table
v-show="tableData.length"
:data="tableData"
class="table_Height"
stripe
height="240px"
>
<el-table-column
prop="name"
label="名称"
>
<template #default="{ row }">
<span
v-if="row.isFolder"
style="margin-right: 6px"
>
<i class="el-icon-folder-opened" />
</span>
<span
v-else
style="margin-right: 6px"
>
<i class="el-icon-document" />
</span>
<span>{{ row.name }}</span>
</template>
</el-table-column>
<el-table-column
prop="size"
label="文件大小"
width="200px"
>
<template #default="{ row }">
{{ handleStorage(row.size) }}
</template>
</el-table-column>
<el-table-column
label="操作"
width="100px"
>
<template #default="{ row, $index }">
<el-button
link
type="primary"
:disabled="!!row.loaded"
@click.stop="deleteFile(row, $index)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 文件读取中不能上传,文件上传数量不能超过100,总大小不能超过 5 GB -->
<div
class="flex_center"
style="margin-top: 20px"
>
<el-button
type="primary"
:loading="loading"
:disabled="
loading ||
tableData.length > 100 ||
totalSize > 5 * 1024 * 1024 * 1024
"
@click="uploadFileList"
>
{{ loading ? '文件读取中' : '上传' }}
</el-button>
</div>
<!-- 上传文件 -->
<input
ref="fileRef"
type="file"
hidden
multiple
@change="uploadeFile"
>
<!-- 上传文件夹-->
<input
ref="folderRef"
type="file"
hidden
multiple
webkitdirectory
@change="folderChange"
>
</div>
import { ref, computed, nextTick } from 'vue'
// 文件过滤
const props = defineProps({
filterSuffix: {
type: Array,
default: () => []
}
})
const fileRef = ref()
const folderRef = ref()
const dragRef = ref()
const tableData = ref([])
const loading = ref(false)
const totalSize = computed(() => {
let total = 0
tableData.value.forEach((item) => {
total += item.size
})
return total
})
function addFiles() {
nextTick().then(() => {
fileRef.value.click()
})
}
function dragover(e) {
e.preventDefault()
}
function onDrop(e) {
e.preventDefault()
const dataTransfer = e.dataTransfer
if (
dataTransfer.items &&
dataTransfer.items[0] &&
dataTransfer.items[0].webkitGetAsEntry
) {
webkitReadDataTransfer(dataTransfer, e)
}
}
function webkitReadDataTransfer(dataTransfer) {
let fileNum = dataTransfer.items.length
const files = []
loading.value = true
// 递减计数,当fileNum为0,说明读取文件完毕
const decrement = () => {
if (--fileNum === 0) {
handleFiles(files)
loading.value = false
}
}
// 递归读取文件方法
const readDirectory = (reader) => {
// readEntries() 方法用于检索正在读取的目录中的目录条目,并将它们以数组的形式传递给提供的回调函数。
reader.readEntries((entries) => {
if (entries.length) {
fileNum += entries.length
entries.forEach((entry) => {
if (entry.isFile) {
entry.file((file) => {
readFiles(file, entry.fullPath)
}, readError)
} else if (entry.isDirectory) {
readDirectory(entry.createReader())
}
})
readDirectory(reader)
} else {
decrement()
}
}, readError)
}
// 文件对象
const items = dataTransfer.items
// 拖拽文件遍历读取
for (let i = 0; i < items.length; i++) {
const entry = items[i].webkitGetAsEntry()
if (!entry) {
decrement()
return
}
if (entry.isFile) {
readFiles(items[i].getAsFile(), entry.fullPath, 'file')
} else {
// entry.createReader() 读取目录。
readDirectory(entry.createReader())
}
}
function readFiles(file, fullPath) {
file.relativePath = fullPath.substring(1)
files.push(file)
decrement()
}
function readError(fileError) {
throw fileError
}
}
function isFilePass(filename) {
const allowedExtensions = props.filterSuffix
if (allowedExtensions.length === 0) {
return true
}
const extension = filename.substring(filename.lastIndexOf('.'))
return allowedExtensions.includes(extension)
}
function handleFiles(files) {
// 按文件名称去存储列表,考虑到批量拖拽不会有同名文件出现
const dirObj = {}
files.forEach((item) => {
// relativePath 和 name 一致表示上传的为文件,不一致为文件夹
// 文件直接放入table表格中
if (item.relativePath === item.name && isFilePass(item.name)) {
tableData.value.push({
name: item.name,
filesList: [item],
isFolder: false,
size: item.size
})
}
// 文件夹,需要处理后放在表格中
if (item.relativePath !== item.name) {
const filderName = item.relativePath.split('/')[0]
if (dirObj[filderName]) {
// 放入文件夹下的列表内
const dirList = dirObj[filderName].filesList || []
if (isFilePass(item.name)) {
dirList.push(item)
dirObj[filderName].filesList = dirList
// 统计文件大小
const dirSize = dirObj[filderName].size
dirObj[filderName].size = dirSize ? dirSize + item.size : item.size
}
} else {
if (isFilePass(item.name)) {
dirObj[filderName] = {
filesList: [item],
size: item.size
}
}
}
}
})
// 放入tableData
Object.keys(dirObj).forEach((key) => {
tableData.value.push({
name: key,
filesList: dirObj[key].filesList,
isFolder: true,
size: dirObj[key].size
})
})
}
function folderChange(e) {
const filesList = e.target.files
console.log(filesList)
const filterFileList = Array.from(filesList).filter((item) => {
return isFilePass(item.name)
})
let size = 0
const fileName = filterFileList[0].webkitRelativePath.split('/')[0]
filterFileList.forEach((item) => {
item.relativePath = item.webkitRelativePath
size += item.size
})
const fileObj = {
name: fileName,
filesList: filterFileList,
isFolder: true,
size
}
tableData.value.push(fileObj)
}
function deleteFile(row, index) {
// 至于为什么不用filter,而是通过下标删除,需要考虑文件同名同样大小问题。
// 当然通过index去删除也不是最好办法,最好办法是生成为一hash,可以通过md5去计算。大批量文件md5也比较耗费时间
tableData.value.splice(index, index + 1)
}
function uploadFile(ref, e) {
if (e) e.stopPropagation()
ref.click()
}
// 清空文件
function clearList() {
tableData.value = []
}
// 选择文档后的处理
async function uploadeFile() {
try {
const dom = fileRef.value
const files = dom.files
if (files.length > 200) {
return
}
Array.from(files).forEach((file) => {
if (isFilePass(file.name)) {
tableData.value.push({
name: file.name,
filesList: [file],
isFolder: false,
size: file.size
})
}
})
dom.value = ''
} catch (error) {
console.log(error)
}
}
const emit = defineEmits(['handleGetUploadModelList'])
async function uploadFileList() {
if (tableData.value.length === 0) {
return
}
const arr = tableData.value.reduce((perv, cur) => {
return [...perv,...cur.filesList]
}, [])
tableData.value = []
emit('handleGetUploadModelList', arr)
}
function handleStorage(value) {
let size = ''
if (value / Math.pow(1024, 3) > 1024) {
size = (value / Math.pow(1024, 4)).toFixed(2) + ' TB'
} else if (value / Math.pow(1024, 2) > 1024) {
size = (value / Math.pow(1024, 3)).toFixed(2) + ' GB'
} else if (value / 1024 > 1024) {
size = (value / Math.pow(1024, 2)).toFixed(2) + ' MB'
} else if (value > 1024) {
size = (value / 1024).toFixed(2) + ' KB'
} else {
size = value + ' B'
}
return size
}
.upload_demo {
width: 600px;
margin: 100px auto;
}
.flex_center {
display: flex;
align-items: center;
justify-content: center;
}
.el-upload__text {
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
line-height: 20px;
text-align: center;
color: #999999;
}
.table_info {
display: flex;
justify-content: space-between;
align-items: center;
.info {
font-size: 12px;
color: #999999;
& span:first-child {
margin-right: 10px;
}
}
}
.el-icon-upload {
font-size: 19px;
margin: 0;
}
.el-upload__text:first-child {
margin-top: 40px;
}
.addFiles {
color: #337dff;
}
.drag {
width: 100%;
height: 240px;
margin-top: 10px;
border: 2px dashed #edeff3;
}
.tableBox {
height: 100%;
min-height: 240px;
}
.drag_border {
border: 2px dashed #dfe1e5;
}
.el-upload__text:first-child {
margin-top: 80px;
}
使用方式
<FileUpload
:filter-suffix="['.glb','.gltf']"
@handle-get-upload-model-list="getUploadModelList"
/>