vue-simple-uploader implements multiple files/folders and drag-and-drop upload


vue-simple-uploader is a vue upload component based on simple-uploader.js

  1. Support multiple file/folder upload, drag and drop upload
  2. You can pause and continue uploading
  3. upload error handling
  4. Support "second transmission", judge whether the server already exists through the file to achieve "second transmission"
  5. Support progress, estimated remaining time, error automatic retry, retransmission and other operations

There are reference documents and examples here

1. Rendering display

Later, upload files/folders using drag and drop

insert image description here
When uploading a file with the same name, there will be a pop-up prompt and related operations
insert image description here

2. Install

npm install vue-simple-uploader --save

3. Vue2 use (vue3 use will report an error)

import uploader from 'vue-simple-uploader'
//已经创建Vue实例了
Vue.use(uploader)

4. Code

Here I only post the main content, and the code is also commented.

 <uploader
    ref="uploader"
    :options="options"
    :auto-start="false"
    :file-status-text="fileStatusText"
    class="uploader-example"
    @file-added="onFileAdded"
    @files-added="onFilesAdded"
    @file-error="onFileError"
    @file-complete="onFileComplete"
  >
    <uploader-unsupport /> //不支持上传时显示内容
    //这个组件也有两个上传的按钮 但是我这边给隐藏了 自定义了下拉框 点击的时候 获取对应的实例 然后触发点击事件
    <uploader-btn
      id="uploader_btn"
      ref="uploadBtn"
    >选择文件</uploader-btn>
    <uploader-btn
      id="uploader_btn"
      ref="uploadFolderBtn"
      :directory="true"
    >选择文件夹</uploader-btn>
    //可拖拽的区域
    <uploader-drop class="drop">
    	//我把这个table区域变成可拖拽的了 具体看自己写的内容吧
    	<el-table></el-table>
    </uploader-drop>
    //这是上传文件显示的上传弹框,在右下角 有文件上传时显示 默认是隐藏
    <div
      v-show="isShowDropUploadFileLists"
      class="drog_list"
    >
      <uploader-list>
        <div
          slot-scope="props"
          class="file-panel"
          :class="{ collapse: collapse }"
        >
          <div class="file-title">
            <div class="title">文件列表</div>
            <div class="operate">
              <el-button
                type="text"
                :title="collapse ? '展开' : '折叠'"
                @click="collapse = !collapse"
              >
                <i :class="collapse ? 'el-icon-full-screen' : 'el-icon-minus'" />
              </el-button>
              <el-button
                type="text"
                title="关闭"
                @click="CloseFilesUploadList"
              >
                <i class="el-icon-close" />
              </el-button>
            </div>
          </div>
          <ul class="file-list">
            <li
              v-for="file in props.fileList"
              :key="file.id"
              class="file-item"
              :class="`file-${file.id}`"
            >
              <uploader-file
                ref="files"
                :class="'file_' + file.id"
                :file="file"
                :list="true"
              />
            </li>
            <div
              v-if="!props.fileList.length"
              class="no-file"
            >
              <i class="iconfont icon-empty-file" /> 暂无待上传文件
            </div>
          </ul>
        </div>
      </uploader-list>
    </div>
  </uploader>

I took out the relevant js separately and mixed them into the current vue file through mixins

import {
    
     GetBatchFilesId, GetDocumentSameNameInfo } from "@/api/file"; //批量上传获取ID,对比上传文件是否跟已存在的同名的两个接口
import {
    
     CreateFilePath, CreateFileName } from "@/utils/handleFile";//这个是入参对文件/文件夹路径和名字的处理
export default {
    
    
  data () {
    
    
    return {
    
    
      options: {
    
    
        target: "http://test.hhh.com.cn/api/Files",//上传地址,如果有文件上传地址不同时 可以是个函数来改变
        testChunks: false,//测试每个块是否在服务端已经上传了
        allowDuplicateUploads: true, // 一个文件以及上传过了是否还允许再次上传
        query: (file, chunk) => {
    
     //上传时所带的参数 可以是个函数在选择文件时 自定义(拿到的两个参数分别是Uploader.File 实例、当前块 Uploader.Chunk 以及是否是测试模式)
          return {
    
    
            ...file.params,
            isUseLastVersionFieldValueWhenUpdate: false, // 請帶false
            path: CreateFilePath(file.relativePath, true),
            autoIdNamingType: "Auto", // 自动编码
            directoryId: 14, // 项目ID
          };
        },
      },
      fileStatusText: {
    
    //显示的状态
        success: "上传成功",
        error: "上传失败",
        uploading: "上传中",
        paused: "已暂停",
        waiting: "等待上传",
      },
    }
  },
  
 computed: {
    
    
    // 获取上传文件实例
    uploaderRef() {
    
    
      return this.$refs.uploader.uploader;
    },
 },
 
  methods: {
    
    
  //值得注意的是我这个方法是@files-added="onFilesAdded",是对当前一次性上传的所有文件做的处理,比如说上传5个文件 那就是会获得一个数组里面包括5个文件,这个函数是在file-added全部执行完后走一次
 // @file-added="onFileAdded"获取到的单个当前上传文件,按照上传顺序来的,比如说上传5个文件,那这个函数会执行5次
    // 全部文件处理 
    onFilesAdded (files, filelist) {
    
    
      this.nowUploadFiles = [...files]//当前上传的文件列表保存了一下 后面有同名弹框操作后 方便继续上传文件 不然触发到同名文件弹框了 后续的操作获取不到已经上传到一半的文件了
      let getBatchIds = ''
      const filesLength = files.length
      // 调用文件同名验证接口,这边入参用的都formData格式
      var formdata = new FormData();
      formdata.append("id", 3);
      formdata.append("directoryId", 6);
      formdata.append("isReserveDirStructure", true);
      this.fileList.forEach((v) => {
    
    
        formdata.append("paths[]", v);
      });
      this.fileNameList.forEach((v) => {
    
    
        formdata.append("filenames[]", v);
      });
      this.fileList = []
      this.fileNameList = []
      const getSameFileInfo = new Promise((resolve, reject) => {
    
    
        formdata.append("autoIdNamingType", "Auto");
        GetDocumentSameNameInfo(formdata).then(_res => {
    
    
          resolve(_res)
        }).catch(err => reject(err))
      })
      if (filesLength > 1) {
    
     //多文件或者文件夹上传的时候 入参需要带个批量ID 看自己需求哈
        // 批量上传文件
        getBatchIds = new Promise((resolve, reject) => {
    
    
          formdata.append("isTemp", false);
          GetBatchFilesId(formdata).then(_res => {
    
    
            resolve(_res)
          }).catch(err => reject(err))
        })
      }
      Promise.all([getSameFileInfo, getBatchIds]).then(res => {
    
    
        if (res && res.length) {
    
    
          // 同名文件对比弹框弹出
          this.isShowSameFiledialog = !!(res[0] && res[0].length)
          this.isShowSameFileInfo = res[0] // 同名文件列表
          // 保存批量文件ID
          this.BatchUploadId = res[1].BatchUploadId || ''
          if (!this.isShowSameFiledialog) {
    
    
            // 单个文件无同名时直接上传,不显示弹框
            console.log(files[0].name)
            files.forEach(item => {
    
    
              item.params = {
    
    
                sameNameUpdateisTrue: this.sameNameUpdateisTrue, // 遇到同名檔案是否更版
                isReserveDirStructure: false,
              }
              item.resume();
            })
            // 显示上传文件弹框
            this.isShowDropUploadFileLists = true
            console.log('单个文件无同名时直接上传,不显示弹框')
          }
        }
      }).catch(err => console.log(err, '哈哈哈'))
    },

    // 文件上传获取单个文件
    onFileAdded (file, fileList) {
    
    
      const _dataPath = file.relativePath;
      // 获取到文件的路径数组和对应的文件名数组
      this.fileList.push(CreateFilePath(_dataPath, this.isFromDragDrop));
      this.fileNameList.push(CreateFileName(_dataPath));
    },

    // 根文件上传成功
    onFileComplete (rootFile) {
    
    
      this.$message({
    
    
        message: "上传成功!",
        type: "success",
      });
    },

    // 文件上传失败
    onFileError (file) {
    
    
      this.$message({
    
    
        message: "上传失败,请重试!",
        type: "error",
      });
    },

    // 点击关闭按钮
    CloseFilesUploadList () {
    
    
      this.uploaderRef.cancel();
      this.isShowDropUploadFileLists = false;
    },

  }
}


The operation of popping up a file with the same name

  <SameFileInfo
      ref="sameFileDailog"
      :isshow="isShowSameFiledialog" //显示弹框
      :same-file-info="isShowSameFileInfo"//同名文件数据列表
      @handelUpdateFile="handelUpdateSameNameFile"
   />
 // 同名文件弹框操作
    handelUpdateSameNameFile(type) {
    
    
      if (type === "cancel") {
    
     //取消操作
        this.nowUploadFiles &&
          this.nowUploadFiles.forEach((item) => {
    
    
            item.ignored = true; 
          });
        this.uploaderRef.cancel(); //关闭上传 uploaderRef是组件uploader的实例
        this.isShowDropUploadFileLists = false; //关闭上传文件列表
        // 关闭弹框
        this.isShowSameFiledialog = false;
        return false;
      }
      if (type === "skip") {
    
    //跳过
        // 点击跳过
        this.sameNameUpdateisTrue = false;
        // 过滤掉重名的文件
        const _this = this;
        _this.nowUploadFiles &&
          _this.nowUploadFiles.forEach((v) => {
    
    
            _this.isShowSameFileInfo.forEach((val) => {
    
    
              if (val.DocumentName.split(".")[0] === v.name.split(".")[0]) {
    
    
                v.ignored = true; //给当前上传的文件列表中同名文件都加一个属性,后续过滤掉同名文件不上传
              }
            });
          });
      } else if (type === "replace") {
    
    
        this.sameNameUpdateisTrue = true;
      } else if (type === "add") {
    
    
        this.sameNameUpdateisTrue = false;
      }
      // 给上传插件赋值(传参)
      this.nowUploadFiles &&
        this.nowUploadFiles.forEach((item) => {
    
    
          item.params = {
    
    
            sameNameUpdateisTrue: this.sameNameUpdateisTrue, // 遇到同名檔案是否更版
            isReserveDirStructure: true, // 是否保留本地文件夾結構 (單檔可帶false)
            batchUploadId: this.BatchUploadId,
          };
          if (item.ignored) {
    
    
            this.uploaderRef.removeFile(item); //如果有同名过滤属性的则选择不上传
          }
          item.resume();
        });
      // 关闭弹框
      this.isShowSameFiledialog = false;
      // 显示上传文件弹框
      this.isShowDropUploadFileLists = true;
    },

The general logic is here.
Finally, post the style code.

<style lang="scss">
#uploader_btn {
    
    
  position: absolute;
  clip: rect(0, 0, 0, 0);
}
.drog_list {
    
    
  position: fixed;
  z-index: 20;
  right: 15px;
  bottom: 15px;
  width: 520px;
  box-sizing: border-box;
  .file-panel {
    
    
    background-color: #fff;
    border: 1px solid #e2e2e2;
    border-radius: 7px 7px 0 0;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);

    .file-title {
    
    
      display: flex;
      height: 40px;
      line-height: 40px;
      padding: 0 15px;
      border-bottom: 1px solid #ddd;

      .operate {
    
    
        flex: 1;
        text-align: right;

        i {
    
    
          font-size: 18px;
        }
      }
    }

    .file-list {
    
    
      position: relative;
      height: 240px;
      overflow-x: hidden;
      overflow-y: auto;
      background-color: #fff;
      transition: all 0.3s;
      list-style: none;
      padding: 0 2%;
      font-size: 12px;
      .file-item {
    
    
        background-color: #fff;
      }

      ::v-deep .uploader-file-size {
    
    
        text-indent: 0 !important;
      }
      ::v-deep .uploader-file-status {
    
    
        width: 22%;
      }
      ::v-deep .uploader-file-name {
    
    
        display: flex;
        align-items: center;
        .uploader-file-icon {
    
    
          margin-top: 0;
          margin-right: 0;
        }
      }
      ::v-deep .uploader-file-meta {
    
    
        display: none;
      }
      ::v-deep .uploader-file-actions {
    
    
        width: 12%;
        float: right;
      }
    }

    &.collapse {
    
    
      .file-title {
    
    
        background-color: #e7ecf2;
      }
      .file-list {
    
    
        height: 0;
      }
    }
  }
}
</style>

Guess you like

Origin blog.csdn.net/weixin_45324044/article/details/127089382