【sgUpload_v2】自定义组件:升级自定义上传组件,支持拖拽上传、弹窗上传单个或多个文件/文件夹,并且获取文件夹结构路径树,然后在右下角出现上传托盘。

简介: 【sgUpload_v2】自定义组件:升级自定义上传组件,支持拖拽上传、弹窗上传单个或多个文件/文件夹,并且获取文件夹结构路径树,然后在右下角出现上传托盘。


特性:

  1. 支持在拖拽上传单个文件、多个文件、单个文件夹、多个文件夹(包括文件夹结构)
  2. 获取filePath文件路径结构
  3. 获取文件MD5
  4. 可自定义headers
  5. 可自定义过滤上传格式
  6. 可自定义上传API接口
  7. 支持显示/隐藏右下角上传队列托盘
  8. 支持全局设置同时上传的最大任务数限制或增加上传请求数量,对超过限制数量的手动上传操作进行了有好提示

sgUpload_v2源码

<template>
  <!-- 支持获取文件夹下所有文件结构目录树 ------------------------------------------>
  <div :class="$options.name">
    <!-- 单个文件 -->
    <input ref="fileInput" type="file" :accept="accept" :multiple="multiple_" />
    <!-- 多个文件 -->
    <input ref="folderInput" type="file" :accept="accept" multiple webkitdirectory />
 
    <checkUploadingFilesDialog
      :data="checkErrorlistData"
      v-model="showCheckDialog"
      @save="continueUploading"
    />
  </div>
</template>
<script>
import BMF from "browser-md5-file"; //npm i browser-md5-file -S
import checkUploadingFilesDialog from "./checkUploadingFilesDialog";
// import SparkMD5 from "spark-md5"; //可以使用npm install --save spark-md5 安装第三方库来计算文件的MD5值。
import $ from "jquery"; //(提示:原生开发页面请前往https://jquery.com下载最新版jQuery)
export default {
  name: "sgUpload_v2",
  components: { checkUploadingFilesDialog },
  data() {
    return {
      // currentUploadFileIndex: 0, //当前上传文件的索引
      bmf: new BMF(),
      showCheckDialog: false, //是否显示校验弹窗
      checkErrorlistData: [], //校验出来的问题
 
      name: `FILE`, //上传的文件字段名
      headers: { kkToken: localStorage.token }, //获取token(注意仔细看后端接受token的字段名是不是叫做“token”)
      accept: `*`,
      actionUrl: `#`,
      actionData: {},
      uploadList: [],
      limit: 0, //限制上传文件个数(默认0不限制)
      minSize: 0, //支持最小上传文件大小(单位MB)(默认0不限制)
      maxSize: 0, //支持最大上传文件大小(单位MB)(默认0不限制)
 
      fileInput: null, //上传文件的input对象DOM
      folderInput: null, //上传文件夹的input对象DOM
      drop: null, //拖拽放入对象DOM
      count: 0,
      files: [],
      filesData: [],
      noCheckFile_: null,
      multiple_: null,
      hideMessageSuccessTip_: null,
 
      setData: {
        // uploadMaxCount: 1, //同时上传的最大任务数(来自sgUploadTray_v2的设置)
      }, //上传设置配置参数
    };
  },
  props: [
    "data", //上传可选参数
    "multiple", //默认是true,触发弹窗上传文件的时候,是否支持上传多个文件
    "noCheckFile", //无需校验文件
    "hideUploadTrayWhenDrag", //拖拽上传的时候隐藏上传托盘(默认显示)
    "hideMessageSuccessTip", //隐藏成功提示(默认不隐藏),主要应用于上传大批量文件的时候,会出现接连不断的提示框,会挡住整个屏幕,体验不好!
    "targetDropDOM", //拖拽放入DOM
    "drag", //是否支持拖拽上传文件(本组件仅支持拽上传单个或多个文件,不支持拖拽上传文件夹。如需支持拖拽上传文件夹请使用【sgUpload_v2】组件)
    "disabledWhenShowSels", //当出现对应['sel','sel','sel',...]的时候,屏蔽拖拽上传(譬如出现element的v-modal)
    "sgUploadTray", //引用外部公共托盘组件dom(这种方式主要是为了解决同一个页面有多个上传托盘组件导致冲突的问题)
  ],
  watch: {
    hideMessageSuccessTip: {
      handler(newValue, oldValue) {
        this.hideMessageSuccessTip_ = newValue === "" || newValue;
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
 
    multiple: {
      handler(newValue, oldValue) {
        this.multiple_ = newValue === undefined || newValue === "" ? true : newValue;
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
 
    "$store.getters._global.uploadSetData": {
      handler(d) {
        this.setData = d || {};
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
    noCheckFile: {
      handler(newValue, oldValue) {
        this.noCheckFile_ = newValue === "" || newValue;
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
 
    data: {
      handler(d) {
        if (d) {
          d.name && (this.name = d.name);
          d.headers && (this.headers = d.headers);
          d.accept && (this.accept = d.accept);
          d.actionUrl && (this.actionUrl = d.actionUrl);
          d.actionData && (this.actionData = d.actionData);
          d.limit && (this.limit = d.limit); //限制上传文件个数(默认是1000个)
          d.minSize && (this.minSize = d.minSize); //支持最小上传文件大小(单位MB)
          d.maxSize && (this.maxSize = d.maxSize); //支持最大上传文件大小(单位MB)
        }
      },
      deep: true,
      immediate: true,
    },
    //拖拽放入DOM
    targetDropDOM: {
      handler(newValue, oldValue) {
        if (newValue) {
          this.initDrag();
        }
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
    uploadList: {
      handler(d) {
        if (this.sgUploadTray) {
          this.sgUploadTray.uploadList || (this.sgUploadTray.uploadList = []);
          let uploadList = this.sgUploadTray.uploadList;
          d.forEach((newVal) => {
            // 避免重复添加到上传列表
            if (!uploadList.some((oldVal) => oldVal.uid === newVal.uid)) {
              uploadList.push(newVal);
              if (this.hideUploadTrayWhenDrag === "" || this.hideUploadTrayWhenDrag) {
                this.sgUploadTray.show = false; //不显示右下角上传托盘
              } else {
                this.sgUploadTray.show = true; //显示右下角上传托盘
              }
            }
          });
        }
      },
      deep: true,
      // immediate: true,
    },
  },
  computed: {
    checkDisabledDrag(d) {
      let aa = this.disabledWhenShowSels || [];
      aa && (Array.isArray(aa) || (aa = [aa]));
      let r = [];
      for (let i = 0, len = aa.length; i < len; i++) {
        let a = aa[i];
        let dom = document.querySelector(a);
        if (dom) {
          r.push(dom);
          return true;
        }
      }
      return r.length !== 0;
    },
    allowDrag() {
      return (this.drag === "" || this.drag) && !this.__checkDisabledDrag;
    },
  },
  mounted() {
    this.initClick2UploadFile();
    this.initClick2UploadFolder();
  },
  destroyed() {
    this.removeEvents();
  },
  methods: {
    removeEvents(d) {
      this.removeEvents4fileInput();
      this.removeEvents4folderInput();
      this.removeEvents4drop();
      this.removeEvents4document();
    },
    removeEvents4fileInput() {
      if (this.fileInput) {
        this.fileInput.unbind("change", this.fileInput_change);
      }
    },
    removeEvents4folderInput() {
      if (this.folderInput) {
        this.folderInput.unbind("change", this.folderInput_change);
      }
    },
    removeEvents4drop() {
      if (this.drop) {
        this.drop.unbind("dragenter", this.drop_dragenter);
        this.drop.unbind("dragleave", this.drop_dragleave);
        this.drop.unbind("drop", this.drop_drop);
      }
    },
    removeEvents4document() {
      $(document).unbind("dragover", this.document_dragover);
      $(document).unbind("drop", this.document_dragdrop);
      $(document).unbind("mouseenter", this.document_mouseenter);
    },
    getMD5({ file, fileIndex, fileTotal, cb }) {
      this.bmf.md5(
        file,
        (err, md5) => {
          cb && cb(md5 || "未获取到MD5");
          // console.log("err:", err);
          // console.log("md5 string:", md5);
        },
        (progress) => {
          let loadingText = `正在获取${this.$g.getOrderNumberCN({
            index: fileIndex,
            total: fileTotal,
          })}文件“${file.name}”的MD5${
            progress < 1 ? `,进度${parseFloat((progress * 100).toFixed(2))}%` : ``
          }`;
          this.$emit(`showMd5Loading`, {
            loadingText,
            file,
            fileIndex,
            fileTotal,
            progress,
          });
          // console.log("progress number:", progress);
        }
      );
    },
    // 初始化点击获取文件夹结构监听
    initClick2UploadFile() {
      this.fileInput = $(this.$refs.fileInput);
      this.removeEvents4fileInput();
      this.fileInput.bind("change", this.fileInput_change);
    },
    initClick2UploadFolder() {
      this.folderInput = $(this.$refs.folderInput);
      this.removeEvents4folderInput();
      this.folderInput.bind("change", this.folderInput_change);
    },
    // 获取上传单个文件
    fileInput_change(e) {
      this.$emit(`showLoading`, { loadingText: `文件获取中…` });
      this.clearData(); //每次上传重置临时数据
      this.files = Array.from(this.$refs.fileInput.files); //获取单个文件
      this.createFilesData({ files: this.files });
    },
    // 获取上传多个文件、文件夹
    folderInput_change(e) {
      this.$emit(`showLoading`, { loadingText: `文件获取中…` });
      this.clearData(); //每次上传重置临时数据
      this.files = Array.from(e.target.files); //获取文件数组
      this.createFilesData({ files: this.files });
    },
    //每次上传重置临时数据
    clearData() {
      this.count = 0;
      this.files = [];
      this.filesData = [];
    },
    // 获取文件总数
    getCount(entry) {
      if (entry.isFile) {
        entry.file(
          (file) => {
            this.count++;
          },
          (err) => {
            // console.log(err);
          }
        );
      } else {
        const entryReader = entry.createReader();
        entryReader.readEntries(
          (results) => {
            results.forEach((result) => {
              this.getCount(result);
            });
          },
          (error) => {
            // console.log(error);
          }
        );
      }
    },
    // 创建文件对象数组
    getFilesFromEntry(entry) {
      if (entry.isFile) {
        entry.file(
          (file) => {
            file.filePath = entry.fullPath.substr(1);
            this.files.push(file);
            if (this.files.length === this.count)
              this.createFilesData({ files: this.files });
          },
          (err) => {
            // console.log(err);
          }
        );
      } else {
        const entryReader = entry.createReader();
        entryReader.readEntries(
          (results) => {
            results.forEach((result) => {
              this.getFilesFromEntry(result);
            });
          },
          (error) => {
            // console.log(error);
          }
        );
      }
    },
    resetInput() {
      // 将当前input标签的value值设为空,解决input的change事件只触发一次
      this.fileInput.val(null); //上传开始就重置状态
      this.folderInput.val(null); //上传开始就重置状态
    },
    // 获取上传文件数组结构
    createFilesData({ files } = {}) {
      let loadingText = ``;
      // 限制上传文件数量----------------------------------------
      if (this.limit && files.length > this.limit) {
        loadingText = `本次提交了${files.length}个文件,文件数量超过了最大数${this.limit},分批次上传吧!`;
        this.$message.error(loadingText);
        this.$emit(`exceed`, files);
        this.$emit(`hideLoading`, { loadingText });
        return;
      }
 
      // 判断文件大小----------------------------------------
      let allowUploadFiles = [];
      files.forEach((file) => {
        let fileSizeMB = file.size / 1024 / 1024; //转换文件大小(单位MB)
        if (this.minSize && fileSizeMB < this.minSize) {
          loadingText = `“${file.name}”文件大小小于${this.$g.getSize(
            this.minSize * 1024 * 1024
          )},已从上传队列移除`;
          this.$nextTick(() => this.$message.error(loadingText)); //限制文件最小0KB(单位MB)
          this.$emit(`hideLoading`, { loadingText });
        } else if (this.maxSize && fileSizeMB > this.maxSize) {
          loadingText = `“${file.name}”文件大小超过${this.$g.getSize(
            this.maxSize * 1024 * 1024
          )},已从上传队列移除`;
          this.$nextTick(() => this.$message.error(loadingText)); //限制最大大小(单位MB)
          this.$emit(`hideLoading`, { loadingText });
        } else {
          allowUploadFiles.push(file);
        }
      });
 
      // ----------------------------------------
 
      this.filesData = allowUploadFiles.map((file) => ({
        UUID: this.$g.UUID(),
        /*
        文件路径的几个情况:
        1、拖拽才有fullPath
        2、弹窗上传文件夹才有webkitRelativePath
        3、弹窗上传单个文件没有fullPath和webkitRelativePath值
        */
        filePath: file.filePath || file.webkitRelativePath || file.name,
        file,
        actionData: JSON.parse(JSON.stringify(this.actionData)), //防止因为上传过程,点击了其他文件夹或栏目改变上传目标地址
      }));
 
      this.resetInput(); //获取完了上传数据,就立刻清空input值
 
      //无需校验直接上传
      if (this.noCheckFile_) {
        this.startUploadFiles({ filesData: this.filesData });
      } else {
        // 需校验后,用户选择上传
        this.checkBeforeUploading({
          filesData: this.filesData,
          success: ({ filesData } = {}) => {
            this.checkBeforeList = filesData;
            let repeatFiles = filesData.filter((v) => v.isHadResource); //路径重复的文件
            if (repeatFiles.length) {
              this.checkErrorlistData = repeatFiles;
              this.showCheckDialog = true;
            } else {
              this.startUploadFiles({
                filesData: this.createUploadFileList({
                  checkBeforeList: this.checkBeforeList,
                  filesData: this.filesData,
                }),
              }); //没有任何重复的文件,自动上传
            }
          },
        });
      }
    },
    // 根据上传重复的提示重新组织上传文件列表
    createUploadFileList({ SFFG, checkBeforeList, filesData } = {}) {
      let r = [];
      filesData.forEach((file) => {
        let checkFile = checkBeforeList.find((v) => v.UUID === file.UUID);
        if (checkFile.isHadResource) {
          file.SFFG = SFFG;
        }
        if (checkFile.isHadResourceMd5) {
          file.isHadResourceMd5 = true;
        }
        if (file.SFFG === 1 && file.isHadResourceMd5) {
          // 如果文件名路径和md5都重复,且选择了覆盖,就不加入上传队列,省一点流量
        } else {
          r.push(file);
        }
      });
      return r;
    },
    // 重复弹窗点击继续
    continueUploading(d) {
      let uploadFileList = this.createUploadFileList({
        SFFG: d.SFFG, //是否覆盖(空值 默认覆盖)
        checkBeforeList: this.checkBeforeList,
        filesData: this.filesData,
      });
      this.startUploadFiles({ filesData: uploadFileList });
    },
    // 初始化拖拽
    initDrag() {
      if (this.targetDropDOM) {
        // 局部区域监听拖拽
        this.drop = $(this.targetDropDOM);
        this.removeEvents4drop();
        this.removeEvents4document();
        this.drop.bind("dragenter", this.drop_dragenter);
        this.drop.bind("dragleave", this.drop_dragleave);
        this.drop.bind("drop", this.drop_drop);
        // 网页全局监听拖拽
        $(document).bind("dragover", this.document_dragover); //屏蔽拖拽文件在targetDropDOM之外弹出新浏览器选项卡
        $(document).bind("drop", this.document_dragdrop);
        $(document).bind("mouseenter", this.document_mouseenter);
      }
    },
    drop_dragenter(e) {
      if (!this.allowDrag) return;
      this.drop.removeAttr("dragleave2Upload");
      setTimeout(() => this.drop.attr("dragenter2Upload", true), 1);
      // console.log("松掉鼠标上传文件/文件夹");
      e.preventDefault();
      return false;
    },
    drop_dragleave(e) {
      if (!this.allowDrag) return;
      this.drop.removeAttr("dragenter2Upload");
      this.drop.attr("dragleave2Upload", true);
      // console.log("请将文件/文件夹拖拽到此");
      e.preventDefault();
      return false;
    },
    // 获取上传拖拽文件、文件夹
    drop_drop(e) {
      if (!this.allowDrag) return;
      this.$emit(`showLoading`, { loadingText: `文件获取中…` });
      this.drop.removeAttr("dragenter2Upload");
      this.drop.removeAttr("dragleave2Upload");
      this.clearData(); //每次上传重置临时数据
      const items = e.originalEvent.dataTransfer.items;
      for (let i = 0; i < items.length; i++) {
        const item = items[i];
        if (item.kind === "file") {
          let entry = item.webkitGetAsEntry();
          // if (!entry.isDirectory) { alert("请上传文件夹"); return; }// 不允许上传单个文件
          //递归解析文件夹
          this.getCount(entry);
          setTimeout(() => {
            this.getFilesFromEntry(entry);
          }, 300);
        }
      }
      e.preventDefault();
      return false;
    },
    document_dragover(e) {
      if (!this.allowDrag) return;
      e.preventDefault();
      return false;
    },
    document_dragdrop(e) {
      if (!this.allowDrag) return;
      this.drop.removeAttr("dragenter2Upload");
      this.drop.removeAttr("dragleave2Upload");
      e.preventDefault();
      return false;
    },
    document_mouseenter(e) {
      if (!this.allowDrag) return;
      this.drop.removeAttr("dragenter2Upload");
      this.drop.removeAttr("dragleave2Upload");
      e.preventDefault();
      return false;
    },
    // 预先检测校验是否存在重复路径名称的文件
    checkBeforeUploading({ filesData, success, fail }) {
      if (filesData && filesData.length) {
        this.getAllMd5({
          filesData,
          cb: (filesData) => {
            // console.log(filesData);
            this.$emit(`checkBeforeUploading`, { filesData, success, fail }); //后端校验是否有相同MD5
          },
        });
      }
    },
    // 递归获取所有文件的md5
    getAllMd5({ filesData, cb }) {
      let i = 0;
      let fileTotal = filesData.length;
      let _recursion_md5 = (fileData) => {
        this.getMD5({
          file: fileData.file,
          fileIndex: i + 1,
          fileTotal,
          cb: (md5) => {
            this.$set(fileData, "MD5", md5);
            i++;
            if (i < filesData.length) {
              _recursion_md5(filesData[i]);
            } else {
              // 获取完毕MD5
              this.$emit(`hideMd5Loading`, { loadingText: `MD5获取完毕` });
              cb && cb(filesData);
            }
          },
        });
      };
      _recursion_md5(filesData[i]);
    },
    clearXhr(xhr) {
      if (xhr) {
        xhr.ontimeout = null;
        xhr.onload = null;
        xhr.upload.onprogress = null;
        xhr.onerror = null;
      }
    },
    removeFromUploadList(UUID) {
      this.uploadList.splice(
        this.uploadList.findIndex((v) => v.uid === UUID),
        1
      ); //移除原始队列
      if (this.sgUploadTray) {
        this.sgUploadTray.uploadList.splice(
          this.sgUploadTray.uploadList.findIndex((v) => v.uid === UUID),
          1
        ); //移除托盘队列
      }
    },
    removeFileFromFileDatas(UUID) {
      let fileData = this.getUploadFileByUUID(UUID);
      delete fileData.file; //移除原始队列的file避免日积月累影响上传速度
    },
    abort_clear_xhr(xhr) {
      if (xhr) {
        xhr.abort(); //取消上传(API请求中断)
        this.clearXhr(xhr); //销毁XMLHttpRequest
      }
    },
    // 从队列移除所有文件
    removeAllFile() {
      this.uploadList.forEach(({ fileData }) => this.abort_clear_xhr(fileData.xhr));
      this.uploadList = []; //移除原始队列
      (this.sgUploadTray || {}).uploadList = []; //移除托盘队列
    },
    // 从队列移除1个文件
    removeFile(fileData) {
      this.abort_clear_xhr(fileData.xhr);
      this.removeFromUploadList(fileData.UUID); //从上传队列移除
      this.autoStartUploadNextFiles();
    },
    //获取还需要上传的数量
    getRemainUploadingCount() {
      let uploadingFiles = this.uploadList.filter((v) => v.status === "uploading");
      let remainUploadingCount =
        (this.setData.uploadMaxCount || 1) - uploadingFiles.length; //还需要上传的数量
      return remainUploadingCount;
    },
    //获取正在上传的最后一个文件索引值
    getUploadingLastIndex(d) {
      return this.uploadList.findLastIndex((v) => v.status === "uploading");
    },
    // 自动进行下一批文件的上传
    autoStartUploadNextFiles({ result } = {}) {
      // 如果当前正处于上传中的文件总数 小于 同时上传的最大任务数,就开始下一个文件上传
      let remainUploadingCount = this.getRemainUploadingCount(); //还需要上传的数量
      if (remainUploadingCount > 0) {
        //正在上传的最后一个文件索引值
        let uploadingLastIndex = this.getUploadingLastIndex();
        uploadingLastIndex = uploadingLastIndex + 1; //加一才刚好是下一个待上传的文件
        //如果没有正在上传的文件,就计算还未上传的第一个文件索引
        if (uploadingLastIndex === 0) {
          uploadingLastIndex = this.uploadList.findIndex((v) => v.status === "");
        }
        if (uploadingLastIndex >= 0) {
          // 计算下一批待上传文件索引(尤其是刚刚设置了上传最大任务数,紧接着就要发生上传数量变化)
          let nextUploadFiles = this.uploadList.slice(
            uploadingLastIndex,
            uploadingLastIndex + remainUploadingCount
          );
 
          // 没有被删除file且还没有开始上传的文件才进行自动上传
          nextUploadFiles.forEach((uploadListData) => {
            let file = (uploadListData.fileData || {}).file; //是否已经完成了上传
            let realUploading = uploadListData.percent === 0; //真正处在待上传状态
            file && realUploading && this.startUploadFile(uploadListData);
          });
        } else {
          this.$emit(`uploadSuccess`, result); //上传完毕
        }
      }
    },
    // 初始化上传监听(每一次触发队列上传)
    startUploadFiles({ filesData } = {}) {
      if (filesData.length === 0) return this.$message.success(`极速上传成功`);
      filesData.forEach((fileData, i) => this.createUploadTrayList({ fileData })); // 所有上传信息加入队列
 
      // 1、启动uploadMaxCount个上传进程----------------------------------------
      this.autoStartUploadNextFiles();
    },
    // 开始上传某一个队列文件
    startUploadFile(fileDataOrIndex) {
      let uploadListData =
        typeof fileDataOrIndex === `number`
          ? this.uploadList[fileDataOrIndex]
          : fileDataOrIndex;
      this.setUploadListFileData4Progress(uploadListData);
      uploadListData && uploadListData.startUpload();
    },
    // 获取response结果内容并转换为对象
    getResult({ response, file }) {
      let responseData = ((response || {}).currentTarget || {}).response;
      responseData = responseData ? JSON.parse(responseData) : {};
      return { response, responseData, file };
    },
    // 单个文件上传信息加入
    handleFileUpload(fileData, { handleTrigger } = {}) {
      // 如果是手动上传,计算当前上传中的文件数量是否小于最大同时上传任务数量,否者就不允许手动上传
      if (handleTrigger) {
        let remainUploadingCount = this.getRemainUploadingCount(); //还需要上传的数量
        if (remainUploadingCount > 0) {
        } else {
          return this.$message.error(
            `同时上传的最大任务数不能超过${this.setData.uploadMaxCount}。如需上传请前往左侧【设置】-【任务管理】,修改【同时上传的最大任务数】。`
          );
        }
      }
 
      // ----------------------------------------
      let UUID = fileData.UUID;
      let file = fileData.file;
      let xhr = new XMLHttpRequest();
      fileData.xhr = xhr;
 
      xhr.open("POST", this.actionUrl); // 设置服务器URL地址
      // xhr.open("POST", "https://jsonplaceholder.typicode.com/posts/"); // 测试上传服务器URL地址
 
      /* 【警告!千万不要设置请求头格式!!! 用<form>表单上传的时候, 请求自动会设置为multipart/form-data; boundary=----WebKitFormBoundary****************】 */
      // xhr.setRequestHeader( "Content-Type", `multipart/form-data; charset=UTF-8` ); //FormData方式提交数据
 
      Object.keys(this.headers).forEach((key) => {
        let value = this.headers[key];
        xhr.setRequestHeader(key, value); //令牌
      });
      xhr.timeout = 0; //默认值为 0,意味着没有超时。
      xhr.ontimeout = () => this.removeFile(fileData);
 
      xhr.onerror = (err) => {
        this.$emit(`hideLoading`, { loadingText: `` });
        this.setUploadListFileData4Error(this.getUploadListFileDataByUUID(UUID));
        handleTrigger || this.autoStartUploadNextFiles(); // 一个个文件逐次上传(上传失败了,继续传下一个)
        this.$emit(`uploadError`, err, file);
      };
      xhr.onload = (response) => {
        this.$emit(`hideLoading`, { loadingText: `` });
        if (xhr.response) {
          let err = JSON.parse(xhr.response);
          if (err.success === false || err.code === -1) {
            this.setUploadListFileData4Error(this.getUploadListFileDataByUUID(UUID));
            handleTrigger || this.autoStartUploadNextFiles(); // 一个个文件逐次上传(上传失败了,继续传下一个)
            this.$emit(`uploadError`, err, file);
            return;
          }
        }
 
        this.setUploadListFileData4Success(this.getUploadListFileDataByUUID(UUID)); //设置托盘的显示成功状态
 
        this.hideMessageSuccessTip_ ||
          this.$message.success(`${fileData.file.name}上传成功`);
 
        let result = this.getResult({ response, file });
        (this.isLargeFile(UUID) || handleTrigger) && this.$emit(`uploadSuccess`, result); //如果是大文件上传结束or手动触发上传完毕
        handleTrigger || this.autoStartUploadNextFiles({ result }); // 继续上传队列下一批(手动触发重新上传除外)
        this.clearXhr(xhr);
        // this.removeFileFromFileDatas(UUID);
      };
 
      xhr.upload.onprogress = (e) => {
        if (e.lengthComputable) {
          let percentComplete = (e.loaded / e.total) * 100;
          this.updateProgressBar(percentComplete, UUID); // 根据上传进度更新进度条
        }
      };
 
      // 组装上传参数----------------------------------------
      let formData = Object.assign({}, fileData.actionData, {
        SFFG: fileData.hasOwnProperty("SFFG") ? fileData.SFFG : null, //是否覆盖1覆盖 2不覆盖(重命名)
        [this.name || `FILE`]: fileData.isHadResourceMd5 ? null : fileData.file, //md5相同就不传file了,省一点流量
        FILE_PATH: fileData.filePath, //文件路径
        MD5: fileData.MD5 || this.$g.UUID(), //没有MD5就用UUID
      }); //合并对象不改变o1,相同字段o2会覆盖o1
 
      let form = this.$g.convertObejct2FormData(formData);
      xhr.send(form); //重新上传失败项
      // ----------------------------------------
    },
    createUploadTrayList({ fileData } = {}) {
      let { UUID, file, filePath } = fileData;
      // 组装上传托盘需要的数据
      this.uploadList.push({
        uid: UUID, //方便定位操作上传队列里面某一项
        fileData, //记录上传对象xhr等信息
        removeFile: () => this.removeFile(fileData), //移除上传队列1个文件
        removeAllFile: () => this.removeAllFile(), //移除上传队列所有文件
        startUpload: ({ handleTrigger } = {}) =>
          this.handleFileUpload(fileData, { handleTrigger }), // 向服务器发起上传请求文件流
        isLargeFile: file.size > (this.data.largeSize || 50) * 1024 * 1024, //默认是50M以上就是大文件
        name: `${file.name}`,
        filePath,
        size: file.size,
        type: file.type,
        webkitRelativePath: file.webkitRelativePath,
        percent: 0, //加载进度
        status: "", //上传状态(success|error)
        tip: "",
        color: "",
      });
    },
    // 进度条
    updateProgressBar(percentage, UUID) {
      let fileData = this.getUploadListFileDataByUUID(UUID);
      fileData && (fileData.percent = percentage);
    },
    // 设置队列文件上传中状态
    setUploadListFileData4Progress(fileData) {
      if (!fileData) return;
      fileData.percent = 0;
      fileData.status = "uploading";
      fileData.tip = "";
      fileData.color = "";
    },
    // 设置队列文件上传成功状态
    setUploadListFileData4Success(fileData) {
      if (!fileData) return;
      fileData.percent = 100;
      fileData.status = "success";
      fileData.tip = "上传成功";
      fileData.color = "green";
    },
    // 设置队列文件上传失败状态
    setUploadListFileData4Error(fileData) {
      if (!fileData) return;
      fileData.percent = 0;
      fileData.status = "error";
      fileData.tip = "上传失败";
      fileData.color = "red";
    },
    getUploadListFileDataByUUID(UUID) {
      return this.uploadList.find((v) => v.uid === UUID); //判断UUID是否相同,从自定义的uploadList获取fileData信息
    },
    getUploadFileByUUID(UUID) {
      return this.filesData.find((v) => v.UUID === UUID); //判断UUID是否相同,从自定义的uploadList获取fileData信息
    },
    // 完成了大文件上传也要实时刷新页面(因为上传完了一个大文件就可以执行列表刷新,给用户一种马上就看到文件上传成功的感觉)
    isLargeFile(UUID) {
      return (this.uploadList.find((v) => v.uid === UUID) || {}).isLargeFile;
    },
    // 触发上传文件弹窗
    triggerUploadFile(d) {
      this.$refs.fileInput.click();
      // console.log(`触发上传文件弹窗`);
    },
    // 触发上传文件夹弹窗
    triggerUploadFolder(d) {
      this.$refs.folderInput.click();
      // console.log(`触发上传文件夹弹窗`);
    },
  },
};
</script>
<style lang="scss">
.sgUpload_v2 {
  display: none;
}
 
@mixin areaText($content: "", $color: #409eff) {
  content: $content;
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  color: $color;
  font-size: 36px;
  font-family: "Microsoft YaHei";
  font-weight: bold;
  display: flex;
  justify-content: center;
  align-items: center;
}
 
@mixin borderAnimate(
  $backgroundColor: #409eff22,
  $borderColor: #409eff,
  $borderWidth: 3px
) {
  border: none;
  background-color: $backgroundColor;
  background: linear-gradient(90deg, $borderColor 60%, transparent 60%) repeat-x left
      top/10px $borderWidth,
    linear-gradient(0deg, $borderColor 60%, transparent 60%) repeat-y right
      top/$borderWidth 10px,
    linear-gradient(90deg, $borderColor 60%, transparent 60%) repeat-x right bottom/10px
      $borderWidth,
    linear-gradient(0deg, $borderColor 60%, transparent 60%) repeat-y left
      bottom/$borderWidth 10px,
    $backgroundColor;
 
  animation: border-animate 0.382s infinite linear;
  @keyframes border-animate {
    0% {
      background-position: left top, right top, right bottom, left bottom;
    }
 
    100% {
      background-position: left 10px top, right top 10px, right 10px bottom,
        left bottom 10px;
    }
  }
}
[dragleave2Upload] {
  display: block;
  &::after {
    @include areaText("请将文件/文件夹拖拽到此", #c6d1de);
    @include borderAnimate(#eff2f755, #c6d1de, 2px);
  }
}
[dragenter2Upload] {
  display: block;
  &::after {
    @include areaText("松掉鼠标上传文件/文件夹", #409eff);
    @include borderAnimate(#409eff22, #409eff, 5px);
  }
}
</style>

应用

<template>
  <div :class="$options.name">
    <div
      ref="drop"
      style="
        position: relative;
        width: 500px;
        height: 500px;
        border: 1px dashed #c6d1de;
        border-radius: 4px;
        background-color: #eff2f7;
        display: flex;
        justify-content: center;
        align-items: center;
        margin-bottom: 10px;
        color: #909399;
      "
    >
      <div>将文件/文件夹拖到这里进行上传</div>
    </div>
 
    <el-button type="primary" @click="triggerUploadFolder">上传文件夹</el-button>
 
    <!-- 拖拽上传组件(文件夹及其子文件夹目录树) -->
    <sgUpload_v2
      :disabledWhenShowSels="['.v-modal']"
      ref="sgUpload_v2"
      :data="uploadData"
      :targetDropDOM="targetDropDOM"
      @uploadSuccess="uploadSuccess"
      @uploadError="uploadError"
      @dragUploadTrayStart="disabledRectSelect = true"
      @dragUploadTrayEnd="disabledRectSelect = false"
      :sgUploadTray="sgUploadTray"
      hideMessageSuccessTip
    />
 
    <!-- 上传托盘(右下角)--共用上传托盘组件 -->
    <sgUploadTray
      v-if="!this.$store.getters._global.inWindow"
      :position="$options.name"
      ref="sgUploadTray"
      @dragStart="disabledRectSelect = true"
      @dragEnd="disabledRectSelect = false"
      @changeUploadingListClose="(d) => ($store.getters._global.uploadingListClose = d)"
      resizeable
    />
  </div>
</template>
<script>
import sgUpload_v2 from "@/vue/components/admin/sgUpload_v2";
import sgUploadTray from "@/vue/components/admin/sgUploadTray";
export default {
  name: "demoUpload_v2",
  components: {
    sgUpload_v2,
    sgUploadTray,
  },
  data() {
    return {
      disabledRectSelect: false,
      uploadData: {
        name: `FILE`,
        actionUrl: `${this.$d.API_ROOT_URL}/core/resource/upload`,
        actionData: {
          PID: "root",
          ZYGS: 1,
          LMID: "e4e6ace61fe240109779467ee15a1034", //测试文件夹
          BMID: "public",
          sgLog: `强哥请求来源:${this.$options.name}上传文件夹`,
        },
      },
      targetDropDOM: null,
      sgUploadTray: null,
    };
  },
  mounted() {
    this.targetDropDOM = this.$refs.drop;
    this.sgUploadTray = this.$refs.sgUploadTray; //获取挂载后的上传托盘组件
  },
 
  destroyed() {},
  methods: {
    // 上传按钮触发
    triggerUploadFolder(d) {
      this.$refs.sgUpload_v2.triggerUploadFolder();
    },
    // 拖拽上传
    uploadSuccess(d, f) {
      // this.refreshCurrentDirectory();
    },
    uploadError(d, f) {
      this.$message.error(d.msg);
    },
  },
};
</script>

基于sgUpload升级改版

image.png


相关文章
|
7月前
|
JavaScript 前端开发
vue前端下载,实现点击按钮弹出本地窗口,选择自定义保存路径
这个不用代码实现(网上也找不到方法可以调出另存为窗口),更改浏览器设置就可以,否则,现在的浏览器都是默认直接保存到下载路径中
625 3
|
7月前
|
API
【sgUpload】自定义组件:自定义上传组件,支持上传文件夹及其子文件夹文件、批量上传,批量上传会有右下角上传托盘出现,支持本地上传图片转换为Base64image。
【sgUpload】自定义组件:自定义上传组件,支持上传文件夹及其子文件夹文件、批量上传,批量上传会有右下角上传托盘出现,支持本地上传图片转换为Base64image。
|
7月前
|
JavaScript 前端开发 搜索推荐
不想要网页默认的右键菜单栏,怎么封装一个可以自定义的右键菜单组件?
不想要网页默认的右键菜单栏,怎么封装一个可以自定义的右键菜单组件?
126 0
针对FastAdmin新增上传多个图片,新增上传的视频的预览效果
针对FastAdmin新增上传多个图片,新增上传的视频的预览效果
919 0
若依的目录结构,有三种一种目录菜单,目录菜单,展开的都是页面的功能,不会跳转,第二种页面菜单,目录里面的一点击都是展示页面,第三种是按钮菜单,点击之后
若依的目录结构,有三种一种目录菜单,目录菜单,展开的都是页面的功能,不会跳转,第二种页面菜单,目录里面的一点击都是展示页面,第三种是按钮菜单,点击之后
若依如何创建二级页面菜单,点击目录后,右侧空白的悬浮空白的内容,点击系统管理的菜单管理,上级菜单点击主类木有个小三角,选择我们之前创建的主类目
若依如何创建二级页面菜单,点击目录后,右侧空白的悬浮空白的内容,点击系统管理的菜单管理,上级菜单点击主类木有个小三角,选择我们之前创建的主类目
|
7月前
|
搜索推荐
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。(一)
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。(一)
|
7月前
|
JavaScript
点击导出所选数据(原生js)
点击导出所选数据(原生js)
45 0
|
7月前
【sgUploadTray】自定义组件:上传托盘自定义组件,可实时查看上传列表进度。
【sgUploadTray】自定义组件:上传托盘自定义组件,可实时查看上传列表进度。
怎么添加文章目录,然后点击目录跳转到对应的内容目录标题+怎么打开MarkDown编辑
怎么添加文章目录,然后点击目录跳转到对应的内容目录标题+怎么打开MarkDown编辑
141 0