【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


相关文章
|
7月前
|
JSON API 开发者
京东商品 API 接口,开发者详解与使用指南
京东开放平台提供商品API,包括获取商品详情、搜索商品、查询分类等功能。开发者可通过API实现商品数据获取与分析,适用于电商选品、价格监控、比价应用等场景。本文详解API调用流程、签名生成方式及Python代码示例,助力高效接入京东商品数据。
|
前端开发 JavaScript 安全
【前端相关】elementui使用el-upload组件实现自定义上传
【前端相关】elementui使用el-upload组件实现自定义上传
3524 0
|
5月前
|
XML JSON 算法
京东商品 SKU 信息接口(jingdong.ware.sku.get)技术干货:数据拉取、规格解析与字段治理(附踩坑总结 + 可运行代码)
本文详解京东商品SKU接口对接技术,涵盖核心参数、权限申请、签名生成、规格解析及常见坑点解决方案,结合可运行代码与实战经验,助力开发者高效集成SKU数据,实现库存、价格等关键信息精准获取。
|
8月前
|
API 开发者
深入研究:淘宝店铺所有商品API接口详解
淘宝店铺商品API接口(item_search_shop)用于获取指定店铺内所有商品信息,包括商品ID、名称、价格、库存、描述、图片URL等。开发者可通过必填参数shop_id及可选参数(如page、sort、category_id等)实现分页查询、排序和分类筛选功能。响应数据包含状态码、消息、商品总数及详细列表,适用于电商平台与第三方应用展示店铺商品场景。
|
8月前
|
数据采集 运维 BI
Python 文件操作进阶|使用 shutil 实现高效文件复制
在开发和运维中,处理大量文件是常见需求,如备份配置、归档日志或构建部署包。手动复制粘贴已无法满足高效需求!Python 的 `shutil` 模块提供了强大的文件操作功能,支持单文件复制、目录树迁移及自动化任务构建。本文详解 `shutil.copy()` 基础用法与进阶技巧,如批量复制、自动路径检测、时间戳命名备份等,助你实现高效自动化。结合实战案例(如自动备份系统),让你的代码更专业!学习后,欢迎交流心得,一起精进 Python 技能。关注我,获取更多编程技巧与源码分享!
|
Java 编译器 程序员
【潜意识Java】期末考试可能考的简答题及答案解析
为了帮助同学们更好地准备 Java 期末考试,本文列举了一些常见的简答题,并附上详细的答案解析。内容包括类与对象的区别、多态的实现、异常处理、接口与抽象类的区别以及垃圾回收机制。通过这些题目,同学们可以深入理解 Java 的核心概念,从而在考试中更加得心应手。每道题都配有代码示例和详细解释,帮助大家巩固知识点。希望这些内容能助力大家顺利通过考试!
411 0
非对称式多谐振荡电路的介绍
非对称式多谐振荡电路:实现多频率稳定振荡的关键 引言: 非对称式多谐振荡电路是一种能够产生多个频率的振荡信号的电路结构。它通过非对称的反馈回路和多个谐振网络的组合来实现多频率的振荡。本文将介绍非对称式多谐振荡电路的原理、应用、设计与实现方法,以及其优缺点。 一、原理 非对称式多谐振荡电路的原理是通过放大器和反馈回路的相互作用来实现多频率的振荡。具体原理如下: 1. 初始状态:当电路开始工作时,放大器的输出信号为零。 2. 放大器放大信号:输入信号经过放大器放大后,形成一个较大的输出信号。 3. 反馈信号:放大器的输出信号被送回到反馈回路中,与输入信号相叠加形成反馈信号。 4. 正反
532 0
|
存储 负载均衡 监控
Elasticsearch 集群分片
【8月更文挑战第24天】
411 12
|
存储 JSON Ubuntu
如何使用 Lua 脚本进行更复杂的网络请求,比如 POST 请求?
如何使用 Lua 脚本进行更复杂的网络请求,比如 POST 请求?
|
Java 测试技术 API
SpringBoot单元测试快速写法问题之计算测试用例的分支覆盖率如何解决
SpringBoot单元测试快速写法问题之计算测试用例的分支覆盖率如何解决