Vue3拖拽上传组件

235 阅读3分钟

记录一下代码,方便以后自己使用,大部分代码都是抄过来的。。。看懂后根据自己的需求改的

<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"
/>

原文地址:blog.csdn.net/mingwei_zhu…