原生封装上传组件,实现文件夹点击上传以及拖拽上传文件夹以及多文件

我这里是封装的i一个vue2上传文件的组件,因为项目需求 需要上传各种类型的文件以及文件夹,所以采用原生HTML5实现,废话不多说下面直接上代码,喜欢的宝子们记得点赞加关注哦~

对了Jacob的博客-优快云博客这也是我,这上面会有一些平时我发现的问题,感兴趣的可以去看一看

实现效果:

代码:

<template>
  <div>
    <div
      ref="fileContainer"
      class="file-container"
      :style="{ height: height }"
      @click="handlUpload"
    >
      <img src="@/assets/image/add-icon.png" class="icon" alt="">
      <div class="text">将文件{{ isFolder ? "或文件夹" : "" }}拖拽至此,进行上传上传格式</div>
      <div class="text">仅支持:{{ getType() }}{{ isFolder ? "、文件夹" : "" }}</div>
      <input
        v-if="isFolder"
        ref="inputFile"
        type="file"
        style="display: none"
        webkitdirectory
        mozdirectory
        odirectory
        @change="inputChange"
      >
      <input
        v-else
        ref="inputFile"
        type="file"
        :multiple="multiple"
        style="display: none"
        @change="inputChange"
      >
    </div>
    <div>
      <div v-for="item in files" :key="item.fileId" class="file-item">
        <div>{{ item.fileName }}</div>
        <i class="el-icon-close item-icon" @click="deleteFile(item)" />
      </div>
    </div>
  </div>
</template>

<script>
import { upload } from "@/api/public"
export default {
  props: {
    height: {
      type: String,
      default: "155px"
    },
    // 上传文件类型
    accept: {
      type: Array,
      default: () => [
        "doc",
        "docx",
        "pdf",
        "xls",
        "xlsx",
        "jpg",
        "jpeg",
        "png",
        "gif",
        "dwg",
        "dxf"
      ]
    },
    /** 点击上传时 是否选择文件夹 为true multiple属性不生效 注意HTML5目前不支持同时选择文件夹和文件 */
    isFolder: {
      type: Boolean,
      default: true
    },
    // 点击上传非文件夹时 支持多选 当isFolder为true此属性不生效
    multiple: {
      type: Boolean,
      default: true
    },
    parentFiles: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      files: []
    }
  },
  watch: {
    parentFiles: {
      handler(newValue, oldValue) {
        // 当parentFiles发生变化时,这里会被调用
        this.files = newValue
      },
      deep: true // 开启深度监听
    }
  },
  mounted() {
    // 进入时触发
    this.$refs.fileContainer.addEventListener("dragenter", this.dragenter)
    // 悬停时触发
    this.$refs.fileContainer.addEventListener("dragover", this.dragover)
    // 拖拽放下时触发
    this.$refs.fileContainer.addEventListener("drop", this.drop)
  },
  destroyed() {
    // 页面销毁时移除监听的事件
    this.$refs.fileContainer?.removeEventListener("dragenter", this.dragenter)
    this.$refs.fileContainer?.removeEventListener("dragover", this.dragover)
    this.$refs.fileContainer?.removeEventListener("drop", this.drop)
  },
  methods: {
    getType() {
      const fileType = {
        doc: "word",
        docx: "word",
        pdf: "PDF",
        xls: "Excel",
        xlsx: "Excel",
        jpg: "图片",
        jpeg: "图片",
        png: "图片",
        gif: "GIF",
        dwg: "CAD",
        dxf: "CAD"
      }
      const accepts = []
      this.accept.forEach((item) => {
        if (!accepts.includes(fileType[item])) {
          accepts.push(fileType[item])
        }
      })
      return accepts.join("、")
    },
    async uploadFile(file) {
      try {
        //上传文件接口 替换为自己的即可
        const res = await upload(file)
        this.files.push({
          fileName: file.name,
          size: file.size,
          fileId: res.data
        })
      } catch (error) {
        console.log("err====", error)
      }
    },
    handlUpload() {
      this.$refs.inputFile.click()
    },
    // 点击触发上传
    async inputChange(e) {
      if (!this.multiple && this.files.length > 0) {
        this.$message.error(`只能上传单个文件!`)
        return
      }
      const fileList = e.target.files
      const promises = []
      for (const file of fileList) {
        const isAccept = this.accept.includes(this.getFileType(file.name))
        if (!isAccept) {
          this.$message.error(`文件${file.name}格式错误,`)
          this.files = []
          return false
        }
        // 需要使用fullPath替代webkitRelativePath  因为通过移入上传时拿不到                webkitRelativePath
        file.fullPath = file.webkitRelativePath
        promises.push(this.uploadFile(file))
      }
      e.target.value = ""
      await Promise.all(promises)
      this.$emit("change", this.files)
    },
    dragenter(e) {
      e.preventDefault()
    },
    dragover(e) {
      e.preventDefault()
    },
    // 文件后缀名
    getFileType(name) {
      const fileType = name.split(".")
      return fileType[fileType.length - 1]
    },
    /**
     * 判断是否为文件夹
     */
    async isDirectory(entry) {
      return new Promise((resolve, reject) => {
        // 为文件夹时
        if (entry.isDirectory) {
          if (!this.isFolder) {
            this.$message.error(`只能选择文件,而不是文件夹`)
            return reject(false)
          }
          // 为文件夹时 创建读取器读取文件夹里面得内容
          const reader = entry.createReader()
          reader.readEntries((entries) => {
            for (const entrie of entries) {
              if (entrie.isDirectory) {
                this.isDirectory(entrie)
              } else {
                entrie.file(async (file) => {
                  const isAccept = this.accept.includes(this.getFileType(file.name))
                  if (!isAccept) {
                    this.$message.error(`文件${file.name}格式错误,`)
                    this.files = []
                    return reject(false)
                  }
                  // 需要使用fullPath替代webkitRelativePath  这里拿不到webkitRelativePath
                  file.fullPath = entrie.fullPath
                  await this.uploadFile(file)
                  return resolve(true)
                })
              }
            }
          })
        } else {
          entry.file(async (file) => {
            const isAccept = this.accept.includes(this.getFileType(file.name))
            if (!isAccept) {
              this.$message.error(`文件${file.name}格式错误,`)
              this.files = []
              return reject(false)
            }
            file.fullPath = entry.fullPath
            await this.uploadFile(file)
            return resolve(true)
          })
        }
      })
    },
    // 文件移入上传
    async drop(e) {
      e.preventDefault()
      // this.files = []
      const promises = []
      const items = e.dataTransfer.items
      if (!this.multiple && (this.files.length > 0 || Object.keys(items).length > 1)) {
        this.$message.error(`只能选择单个文件`)
        return
      }
      for (const item of items) {
        const entry = item.webkitGetAsEntry()
        promises.push(this.isDirectory(entry))
        // await this.isDirectory(entry)
      }
        // 注意读取文件属于IO操作,也就是异步的,所以这里我用promises包了一层
      await Promise.all(promises)
      this.$emit("change", this.files)
    },
    /**
     * 删除
     * @param file
     */
    deleteFile(file) {
      this.$confirm("您确定删除该文件吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(() => {
          this.files = this.files.filter((item) => item.fileId != file.fileId)
          this.$emit("change", this.files)
        })
        .catch(() => {})
    }
  }
}
</script>
<style lang="scss" scoped>
.file-container {
  width: 100%;
  background: #ffffff;
  border-radius: 4px 4px 4px 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  font-weight: 400;
  font-size: 12px;
  color: #a0a6ae;
  cursor: pointer;
  &:hover {
    border-color: #44a5ff;
  }
  .icon {
    width: 19px;
    height: 24px;
    display: block;
    margin-bottom: 10px;
  }
  .text {
    line-height: 20px;
  }
}
.file-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: #333333;
  cursor: pointer;
  .item-icon {
    opacity: 0;
  }
  &:hover {
    color: #44a5ff;
  }
  &:hover .item-icon {
    opacity: 1;
  }
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值