Skip to content

文件上传组件使用示例

错误示例:

  • 点击上传按钮,应避免使用弹窗,让用户二次点击上传按钮,进行文件上传
  • 应该避免使用el-upload组件中的file-list属性,show-file-list应该设置成false

WX20230915-193329@2x.png

正确示例:

  • 需自行实现文件列表回显文件
  • 避免使用组件自带的文件列表,避免使用el-upload组件中的file-list属性,show-file-list应该设置成false
  • 模块id,需要根据实际的模块信息设置
  • 为避免element-ui组件问题,每次上传前,需要清空文件列表
  • 页面增加loading,防止重复点击上传
  • 如果上传的是图片,需要对图片进行压缩
  • 上传成功的钩子函数也需要关闭loading
  • 上传错误时,需要关闭loading
  • 删除文件时,应该对删除操作进行提示

实现效果示例:

WX20230915-192808@2x.png

代码示例:

vue
<!-- 表单上传 -->
<template>
  <div class="home-index">
    <!-- 上传按钮 -->
    <el-button @click="onUpload" size="small" type="primary">上传文件</el-button>
    <!-- 文件列表TODO:需自行实现文件列表回显文件 -->
    <div class="fix mt10">
      <!-- 文件项目 -->
      <div class="home-index-file-item l" v-for="(e, i) in fileList" :key="e.id">
        <img class="home-index-file-item-img" :src="fileDisplay(e)" alt="">
        <!-- 蒙层区域 -->
        <div class="home-index-file-item-shade" @click="onPreview(e.id)">
          <v-svg class="home-index-file-item-preview" type="bs-show" color="#fff" size="24" />
          <el-button type="text" @click.stop="onPreview(e.id)">预览</el-button>
          <el-divider direction="vertical"></el-divider>
          <el-button type="text" @click.stop="onDownload(e)">下载</el-button>
          <template v-if="canOperate">
            <el-divider direction="vertical"></el-divider>
            <el-button type="text" @click.stop="onRemove(e.id, i)">删除</el-button>
          </template>
        </div>
        <div class="home-index-file-item-label ell" :title="e.name">{{e.name}}</div>
      </div>
    </div>
    <!-- 上传组件,TODO:show-file-list属性,应设置为false,避免使用组件自带的文件列表 -->
    <el-upload ref="upload" :class="`home-index-upload dn`" :accept="ACCEPT_TYPE" :action="`${CONTEXT_PATH}/pub/attachmentFileUpload`" :headers="uploadHeaders" :data="uploadData" multiple :show-file-list="false" :before-upload="onBeforeUpload" :on-success="onSuccess" :on-error="onError">
    </el-upload>
  </div>
</template>
<script>
import $ from 'jquery';
import { mapGetters } from 'vuex';
import { GET_TOKEN, GET_LOGIN_INFO } from '@/store/login';
import {
  fileDisplay,
  fileDownload,
  filePreview,
  fileRemove,
  imageCompress,
} from '@/mixin/file';
import { getTimestamp } from '@/mixin/system';
import util from '@/assets/js/util';
import validate from '@/assets/js/validate';
import { CONTEXT_PATH, FILE_TYPE, ACCEPT_TYPE } from '@/assets/js/constant';

export default {
  data() {
    return {
      uploadHeaders: {
        Authorization: '', //token信息
        Timestamp: '', //时间戳摘要
      },  //上传头信息
      uploadData: {
        sysId: 'SYS', //TODO:模块id,需要根据实际的模块信息设置
        attachId: '', //附件id
        contentType: '', //附件类型
        mofDivCode: '', //区划
        creator: '',  //用户代码
        creatorName: '',  //用户名称
      }, //附加参数信息
      fileList: [],
      CONTEXT_PATH,
      ACCEPT_TYPE,
    };
  },
  methods: {
    /**
     * 处理上传文件
     * 1、TODO:为避免element-ui组件问题,每次上传前,需要清空文件列表
     * 2、手动触发click事件来触发上传
     */
    onUpload() {
      this.$refs.upload.clearFiles();
      $(`.home-index-upload .el-upload`).click();
    },
    /**
     * 处理上传前的业务逻辑
     * 1、校验文件类型
     * 2、设置上传时请求头信息、时间密钥
     * 3、设置上传时的附加参数
     * 4、TODO:页面增加loading,防止重复点击上传
     */
    onBeforeUpload(file) {
      if (!validate.validateFileType(file.name)) {
        this.$message({
          type: 'warning',
          message: '文件非法!',
        });
        return false;
      }
      this.uploadHeaders.Authorization = this.GET_TOKEN;
      this.uploadHeaders.Timestamp = getTimestamp();
      this.uploadData.attachId = util.generateUUID();
      this.uploadData.contentType = file.type;
      this.uploadData.mofDivCode = this.GET_LOGIN_INFO.mofDivCode;
      this.uploadData.creator = this.GET_LOGIN_INFO.userCode;
      this.uploadData.creatorName = this.GET_LOGIN_INFO.userName;
      this.$loading();
      return true;
    },
    /**
     * 上传成功时的钩子
     * 1、如果上传的是图片,上传完图片之后,需要生成缩略图(解决因文件体积过大导致预览速度慢的问题)
     */
    async onSuccess(response, { name }) {
      try {
        let { code, data } = response;
        if (code === '200') {
          // TODO:如果上传的是图片,需要对图片进行压缩
          if (util.getFileType(name) === FILE_TYPE.IMAGE) {
            await imageCompress(data);
          }
          this.fileList.push({
            id: data,
            name
          });
        } else {
          //可以上传会返回200,如果code不是200,说明后端校验拦截了,需要将错误抛出
          this.$message({
            type: 'error',
            message: response.msg,
          });
        }
      } catch (err) {
        console.error(err);
        this.$message({
          type: 'error',
          message: err.msg,
        });
      } finally {
        //TODO:上传成功的钩子函数也需要关闭loading
        this.$loadingClose();
      }
    },
    /**
     * 处理上传失败
     */
    onError(err) {
      console.error(err);
      // TODO:上传错误时,需要关闭loading
      this.$loadingClose();
      this.$message({
        type: 'error',
        message: '上传失败,请稍后重试!',
      });
    },
    /**
     * 处理预览
     */
    onPreview(id) {
      filePreview(id, this.fileList);
    },
    /**
     * 处理下载附件
     */
    onDownload({ id }) {
      fileDownload(id);
      this.$message({
        type: 'success',
        message: '正在下载,请稍等!',
      });
    },
    /**
     * 处理删除附件
     */
    async onRemove(id, index) {
      try {
        this.$loading();
        // TODO:删除文件时,应该对删除操作进行提示
        await this.$confirm('确定要删除当前附件?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
        });
        await fileRemove(id);
        this.fileList.splice(index, 1);
        this.$message({
          type: 'success',
          message: '删除成功!',
        });
      } catch (err) {
        console.error(err);
        if (err !== 'cancel') {
          this.$message({
            type: 'error',
            message: err.msg,
          });
        }
      } finally {
        this.$loadingClose();
      }
    },
    /**
     * 文件显示
     */
    fileDisplay({id, name}) {
      return fileDisplay(id, name);
    }
  },
  computed: {
    ...mapGetters([GET_TOKEN, GET_LOGIN_INFO]),
  },
};
</script>
<style lang="scss" scoped>
@import '~@/assets/style/variables.scss';
.home-index {
  height: 100vh;
  padding: 10px;
  background: $-bs-color-white;
  text-align: center;
  .home-index-file-item {
    position: relative;
    width: 160px;
    height: 90px;
    margin-right: 10px;
    margin-bottom: 20px;
    display: flex;
    align-items: center;
    border: $-bs-border;
    border-radius: $-bs-border-radius;
    background: $-bs-color-white;
    .home-index-file-item-label {
      position: absolute;
      bottom: -21px;
      left: 0;
      width: 100%;
      font-size: 13px;
      color: $-bs-text-color-light;
    }
    .home-index-file-item-img {
      width: 72px;
      margin-left: 15px;
    }
    .home-index-file-item-shade {
      position: absolute;
      top: 0;
      left: 0;
      height: 100%;
      width: 100%;
      padding: 10px;
      line-height: 120px;
      box-sizing: border-box;
      text-align: right;
      background: rgba(0, 0, 0, 0.3);
      opacity: 0;
      border-radius: $-bs-border-radius;
      cursor: pointer;
      &:hover {
        opacity: 1;
      }
      .home-index-file-item-preview {
        position: absolute;
        top: 8px;
        right: 8px;
      }
      .el-button--text {
        padding: 0;
        color: $-bs-color-white;
        &:hover {
          color: $-bs-color-blue;
        }
      }
    }
    ::v-deep .el-divider--vertical {
      margin: 0 5px;
    }
  }
}
</style>