【sgUpload】自定义组件:自定义上传组件,支持上传文件夹及其子文件夹文件、批量上传,批量上传会有右下角上传托盘出现,支持本地上传图片转换为Base64image。

简介: 【sgUpload】自定义组件:自定义上传组件,支持上传文件夹及其子文件夹文件、批量上传,批量上传会有右下角上传托盘出现,支持本地上传图片转换为Base64image。


特性:

  1. 支持批量上传文件、文件夹
  2. 可自定义headers
  3. 可自定义过滤上传格式
  4. 可自定义上传API接口
  5. 支持drag属性开启可拖拽上传文件、文件夹
  6. hideMessageSuccessTip:支持隐藏顶部提示,隐藏成功提示(默认不隐藏),主要应用于上传大批量文件的时候,会出现接连不断的提示框,会挡住整个屏幕,体验不好!
  7. 支持limit限制一次性上传文件数,并抛出exceed事件

sgUpload源码

<template>
  <div :class="$options.name" :dragenter="isDragenter">
    <!-- 上传按钮_________________________________________________________ -->
    <!-- 上传文件 -->
    <el-upload
      ref="uploadFile"
      multiple
      :show-file-list="false"
      :headers="headers"
      :accept="accept.toString()"
      :action="actionUrl"
      :data="actionData"
      :before-upload="beforeUpload"
      :on-progress="uploadProgress"
      :on-success="uploadSuccess"
      :on-error="uploadError"
      :name="name"
      :auto-upload="autoUpload"
      :limit="limit"
      :on-exceed="exceed"
      :on-change="change"
      :on-remove="remove"
    >
    </el-upload>
 
    <!-- 上传文件夹 -->
    <el-upload
      ref="uploadFolder"
      multiple
      :show-file-list="false"
      :headers="headers"
      :action="actionUrl"
      :data="actionData"
      :before-upload="beforeUpload"
      :on-progress="uploadProgress"
      :on-success="uploadSuccess"
      :on-error="uploadError"
      :drag="(drag === '' || drag) && !__checkDisabledDrag()"
      :name="name"
      :auto-upload="autoUpload"
      :limit="limit"
      :on-exceed="exceed"
      :on-change="change"
      :on-remove="remove"
    >
    </el-upload>
    <!-- _________________________________________________________ -->
 
    <!-- 上传托盘(右下角) -->
    <sgUploadTray
      v-model="showUploadTray"
      :data="uploadList"
      @stopUpload="stopUpload"
      @dragStart="$emit(`dragUploadTrayStart`, true)"
      @dragEnd="$emit(`dragUploadTrayEnd`, true)"
      v-if="!(hideUploadTray === '' || hideUploadTray) && !sgUploadTray"
      resizeable
    />
  </div>
</template>
<script>
import sgUploadTray from "@/vue/components/admin/sgUploadTray";
export default {
  name: "sgUpload",
  components: { sgUploadTray },
  data() {
    return {
      // 上传----------------------------------------
      name: `FILE`, //上传的文件字段名
      headers: { kkToken: localStorage.token }, //获取token(注意仔细看后端接受token的字段名是不是叫做“token”)
      accept: `.${["png", "jpg", "jpeg", "bmp", "gif", "svg"].join(",.")}`, //默认只支持图片格式上传
      actionUrl: `#`,
      actionData: {},
      dur: 100,
      percent: 100,
      uploadList: [],
      showUploadTray: false,
      uploadFileBtn: null, //上传文件
      uploadFolderBtn: null, //上传文件夹
      isDragenter: false, //是否拖入
      leaveEvents: [
        "mouseenter",
        "mouseover",
        "mousemove",
        "mouseout",
        "blur",
        "visibilitychange",
      ],
      minSize: 0, //支持最小上传文件大小(单位MB)
      maxSize: null, //支持最大上传文件大小(单位MB)
      autoUpload: true, //是否在选取文件后立即进行上传
      limit: 1000, //限制上传文件个数(默认是1000个)
      currentRef: "", //当前ref
      // ----------------------------------------
    };
  },
  props: [
    "data", //上传可选参数
    "hideUploadTray", //不显示上传托盘
    "hideUploadTrayWhenDrag", //拖拽上传的时候隐藏上传托盘(默认显示)
    "hideMessageSuccessTip", //隐藏成功提示(默认不隐藏),主要应用于上传大批量文件的时候,会出现接连不断的提示框,会挡住整个屏幕,体验不好!
    "drag", //是否支持拖拽上传文件(本组件仅支持拽上传单个或多个文件,不支持拖拽上传文件夹。如需支持拖拽上传文件夹请使用【sgUploadFolder】组件)
    "disabledWhenShowSels", //当出现对应['sel','sel','sel',...]的时候,屏蔽拖拽上传(譬如出现element的v-modal)
    "sgUploadTray", //引用外部公共托盘组件dom(这种方式主要是为了解决同一个页面有多个上传托盘组件导致冲突的问题)
  ],
  watch: {
    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.minSize && (this.minSize = d.minSize); //支持最小上传文件大小(单位MB)
          d.maxSize && (this.maxSize = d.maxSize); //支持最大上传文件大小(单位MB)
          d.limit && (this.limit = d.limit); //限制上传文件个数(默认是1000个)
          typeof d.autoUpload !== "undefined" && (this.autoUpload = d.autoUpload);
        }
      },
      deep: true,
      immediate: true,
    },
    drag: {
      handler(d) {
        if (d === "" || d) {
          this.addEvents();
        } else {
          this.removeEvents();
        }
      },
      deep: true,
      immediate: true,
    },
    showUploadTray(newValue, oldValue) {
      if (this.sgUploadTray) {
        this.sgUploadTray.show = newValue;
      }
    },
    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.unshift(newVal);
              if (this.hideUploadTrayWhenDrag === "" || this.hideUploadTrayWhenDrag) {
                this.sgUploadTray.show = false; //不显示右下角上传托盘
              } else {
                this.sgUploadTray.show = true; //显示右下角上传托盘
              }
            }
          });
        }
      },
      deep: true,
      // immediate: true,
    },
  },
  mounted() {
    this.$nextTick(() => {
      this.uploadFileBtn = this.$refs.uploadFile.$children[0].$refs.input;
      this.uploadFolderBtn = this.$refs.uploadFolder.$children[0].$refs.input;
      this.uploadFolderBtn && (this.uploadFolderBtn.webkitdirectory = true); //让el-upload支持上传文件夹
    });
  },
  destroyed() {
    this.removeEvents();
  },
  methods: {
    // 取消上传文件请求
    abortUploadFile(file) {
      this.$refs.uploadFile.abort(file);
    },
    // 取消上传文件夹请求
    abortUploadFolder(file) {
      this.$refs.uploadFolder.abort(file);
    },
    __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;
    },
    // 监听----------------------------------------
    addEvents(d) {
      this.removeEvents();
      addEventListener("dragenter", this.dragenter);
      this.leaveEvents.forEach((v) => addEventListener(v, this.leave));
    },
    removeEvents(d) {
      removeEventListener("dragenter", this.dragenter);
      this.leaveEvents.forEach((v) => removeEventListener(v, this.leave));
    },
    dragenter(d) {
      this.isDragenter = true;
      this.currentRef = "uploadFolder";
    },
    leave(d) {
      this.isDragenter = false;
    },
    // 上传按钮触发----------------------------------------
    triggerUploadFile(d) {
      this.currentRef = "uploadFile";
      this.uploadFileBtn && this.uploadFileBtn.click();
    },
    triggerUploadFolder(d) {
      this.currentRef = "uploadFolder";
      this.uploadFolderBtn && this.uploadFolderBtn.click();
    },
    // 判断是否相同uid
    same_uid_lastModified(uid, file) {
      return uid == this.getFileID(file);
    },
    // 获取uid
    getFileID(file) {
      return file.uid || file.lastModified || (file.raw || {}).lastModified;
    },
    // 上传文件----------------------------------------------------------------
    // 真的加载
    showLoading(file) {
      this.$emit(`showLoading`, file);
      let fileData = this.uploadList.find((v) => this.same_uid_lastModified(v.uid, file)); //拖拽上传的时候没有uid
      fileData.percent = file.percentage || 0;
    },
    getUploadListFileData(file = {}) {
      return this.uploadList.find((v) => this.same_uid_lastModified(v.uid, file)); //判断UUID是否相同,从自定义的uploadList获取fileData信息
    },
    // 真的停止加载
    hideLoading(file, { status, tip, color } = {}) {
      this.$emit(`hideLoading`, file);
      if (!file) return;
      let fileData = this.getUploadListFileData(file);
      if (fileData) {
        switch (status) {
          case "error":
            fileData.percent = 0;
            break;
          case "success":
          default:
            fileData.percent = 100;
        }
        status && (fileData.status = status);
        tip && (fileData.tip = tip);
        color && (fileData.color = color);
      }
    },
    exceed(files, fileList) {
      /* 
      files:选中的文件数(文件夹或文件)
      fileList:上传的队列文件个数
      */
      this.$message.error(
        `本次提交了${files.length}个文件,文件数量超过了最大数${this.limit},分批次上传吧!`
      );
      this.$emit(`exceed`, files, fileList);
    },
    stopUpload(d) {
      this.$refs.uploadFolder.abort();
    },
    removeFileFromList(file) {
      this.uploadList.splice(
        this.uploadList.findIndex((v) => this.same_uid_lastModified(v.uid, file)),
        1
      );
    },
    //文件上传之前
    beforeUpload(file) {
      if (file.type === "" && !file.name.includes(`.`))
        return this.$message.error(`暂不支持拖拽上传文件夹`);
 
      this.uploadList.unshift({
        removeFile: () => {
          this.$refs[this.currentRef] && this.$refs[this.currentRef].abort(file);
          this.removeFileFromList(file);
        },
        isLargeFile: file.size > (this.data.largeSize || 50) * 1024 * 1024, //默认是50M以上就是大文件
        uid: this.getFileID(file), //拖拽上传的时候没有uid
        percent: 0, //加载进度
        name: file.name,
        size: file.size,
        type: file.type,
        webkitRelativePath: file.webkitRelativePath,
        status: "", //上传状态(success|error)
        tip: "",
        color: "",
      });
      if (this.hideUploadTrayWhenDrag === "" || this.hideUploadTrayWhenDrag) {
        this.showUploadTray = false; //不显示右下角上传托盘
      } else {
        this.showUploadTray = true; //显示右下角上传托盘
      }
      // 判断是不是特定的格式________________________
      let isFile =
        this.accept === "*"
          ? true
          : this.accept.includes(file.name.toLocaleLowerCase().split(".").pop());
      isFile || this.$message.error(`上传文件只能是${this.accept}格式`);
 
      let fileSizeMB = file.size / 1024 / 1024; //转换文件大小为MB单位
 
      const minSize = this.minSize; //限制文件最小0KB(单位MB)
      const isAllowMinSize = fileSizeMB >= minSize;
      isAllowMinSize || this.$message.error(`上传文件大小不能小于${minSize * 1000}KB`);
 
      const maxSize = this.maxSize || 500; //限制大小(单位MB)
      const isAllowMaxSize = fileSizeMB <= maxSize;
      isAllowMaxSize ||
        this.$message.error(
          `传文件大小不能超过${this.$g.getSize(maxSize * 1024 * 1024)}`
        );
 
      let allowUpload = isFile && isAllowMinSize && isAllowMaxSize;
      if (allowUpload) {
        this.showLoading(file);
        this.$nextTick(() => {
          this.$g.file2Base64Image(file, (d) => this.$emit(`resultBase64Image`, d));
          this.$emit(`beforeUpload`, file);
        });
      } else {
        this.hideLoading(file, { status: "error", tip: "上传失败", color: "red" });
      }
      return allowUpload; //若返回false则停止上传
    },
    // 真实加载进度
    uploadProgress(event, file, fileList) {
      this.showLoading(file);
    },
    change(file, fileList) {},
    remove(file, fileList) {},
    // 彻底完成了所有上传队列的上传
    isCompleteUploadFileList(fileList) {
      return !fileList.some((v) => v.percentage < 100);
    },
    // 完成了大文件上传也要实时刷新页面(因为上传完了一个大文件就可以执行列表刷新,给用户一种马上就看到文件上传成功的感觉)
    isLargeFile(file) {
      return this.getUploadListFileData(file).isLargeFile;
    },
    //上传成功
    uploadSuccess(response, file, fileList) {
      if (response.data && response.data.key) {
        // 部分导入失败
        this.hideLoading(file, { status: "error", tip: "上传失败", color: "red" });
        this.$emit(`importError`, response, file, fileList); // 下载导入失败原因的描述文件
      } else if (response.success) {
        if (this.isCompleteUploadFileList(fileList)) {
          this.$emit(`uploadSuccess`, response, file);
        } else if (this.isLargeFile(file)) {
          this.$emit(`uploadSuccess`, response, file);
        }
        // 上传成功了
        this.hideLoading(file, { status: "success", tip: "上传成功", color: "green" });
        this.hideMessageSuccessTip === "" ||
          this.hideMessageSuccessTip ||
          this.$message.success(`${file.name}上传成功`);
      } else {
        this.$emit(`uploadError`, response, file);
        // 其他失败原因
        this.hideLoading(file, { status: "error", tip: "上传失败", color: "red" });
      }
    },
    //上传失败
    uploadError(err, file, fileList) {
      file.raw && (file = file.raw);
      if (file.size === 0 && file.type === "") return;
      if (this.actionUrl === "#") return;
      this.$emit(`uploadError`, err, file);
      this.hideLoading(file, { status: "error", tip: "上传失败", color: "red" });
    },
  },
};
</script>
<style lang="scss">
.sgUpload {
  width: 0;
  height: 0;
 
  .el-upload-dragger {
    z-index: 999999; //根据情况自己拿捏
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    display: none;
    background-color: #ffffff99;
 
    &::after {
      content: "拖拽文件到此处";
      position: absolute;
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      color: #409eff;
      font-size: 18px;
      font-weight: bold;
      line-height: 1.2;
    }
  }
 
  &[dragenter] .el-upload-dragger {
    display: block;
    border-color: #409eff;
 
    &.is-dragover {
      background-color: #409eff22;
 
      &::after {
        content: "松掉鼠标上传文件";
      }
    }
  }
}
</style>

应用

<template>
  <div>
    <div style="width: 300px; height: 300px; position: relative">
      <img :src="src" style="width: 100%; height: 100%" />
      <!-- 上传组件 -->
      <sgUpload
        drag
        ref="sgUpload"
        :data="{
          accept: `*`,
          actionUrl: `${$d.API_ROOT_URL}/customer/importCustomerData`,
        }"
        @resultBase64Image="resultBase64Image"
        @success="uploadSuccess"
        @error="uploadError"
        @importError="importError"
        hideUploadTray
      />
    </div>
    <el-button
      type="primary"
      icon="el-icon-upload2"
      @click="(d) => $refs.sgUpload.triggerUploadFile()"
      >上传</el-button
    >
  </div>
</template>
 
<script>
import sgUpload from "@/vue/components/admin/sgUpload";
export default {
  components: {
    sgUpload,
  },
  data() {
    return {
      src: "",
    };
  },
  methods: {
    resultBase64Image(d, f) {
      this.src = d;
    },
    uploadSuccess(d, f) {
      // 上传成功刷新相关列表数据
    },
    uploadError(d, f) {
      this.$message.error(d.msg); //上传失败提示
    },
    // 导入失败
    importError(d, f) {
      let data = {
        key: d.data.key,
        sgLog: `强哥请求来源:${this.$options.name}下载导入失败原因`,
      };
      this.$d.column_downloadImportColumnExcel({
        data,
        r: {
          s: (d) => {
            this.$g.downloadFile(d, `${f.name}-上传失败原因`, ".xls");
            this.$message.error(`${f.name}-上传失败,请查看失败原因`);
            // 这里也需要刷新相关列表数据
          },
        },
      });
    },
  },
};
</script>

原始思想来源

image.png

升级版sgUploadFolder(可以用户上传文件夹的目录结构)

image.png


相关文章
|
6月前
ant-design Upload上传组件使用 编辑功能图片回显
ant-design Upload上传组件使用 编辑功能图片回显
656 0
针对FastAdmin新增上传多个图片,新增上传的视频的预览效果
针对FastAdmin新增上传多个图片,新增上传的视频的预览效果
852 0
|
6月前
|
存储
Obsidian 与 Typora 图片兼容保存路径一致设置
Obsidian 与 Typora 图片兼容保存路径一致设置
441 0
|
6月前
|
API
【sgUpload_v2】自定义组件:升级自定义上传组件,支持拖拽上传、弹窗上传单个或多个文件/文件夹,并且获取文件夹结构路径树,然后在右下角出现上传托盘。
【sgUpload_v2】自定义组件:升级自定义上传组件,支持拖拽上传、弹窗上传单个或多个文件/文件夹,并且获取文件夹结构路径树,然后在右下角出现上传托盘。
文件上传,下载,预览,删除(File),分页接口(四)
文件上传,下载,预览,删除(File),分页接口
51 0
文件上传,下载,预览,删除(File),分页接口(三)
文件上传,下载,预览,删除(File),分页接口
55 0
|
6月前
|
Java 应用服务中间件 Android开发
UEditor自定义图片/文件上传路径与回显
UEditor自定义图片/文件上传路径与回显
537 0
|
XML Java 数据库
文件上传,下载,预览,删除(File),分页接口(一)
文件上传,下载,预览,删除(File),分页接口
88 0
|
JavaScript Java
文件上传,下载,预览,删除(File),分页接口(二)
文件上传,下载,预览,删除(File),分页接口
173 0
实现手动上传表单数据+图片文件
在很多项目中都会有上传数据+图片的需求,我最近在项目中负责活动发布的板块,需要几个表单数据加两个图片和一个图片数组,我看到产品需求后头就很大,我之前没有做过相关的业务,所以这几天一直在尝试,看到接口文档我内心已经崩了。
161 1
实现手动上传表单数据+图片文件