index.vue 9.81 KB
<template>
    <div class="upload-container">
      <div class="tips" v-if="!options.disabled">
        请上传小于{{ options.limitSize }}M,格式为{{
          options.accept
        }}格式的图片,最多可上传{{ options.limit }}张
      </div>
      <div class="card-list-box">
        <div
          v-for="(file, i) in localValue"
          :key="'upload-BaseImage' + i"
          class="card-item-box"
        >
          <BaseImage
            :src="file.address"
            fit="contain"
            class="card-img"
            :code="options.reqParams.code"
            :resourceType="options.resourceType"
            @handleDelete="handleDeleteImg(file, i)"
            @handleSetCover="handleSetCover(file, i)"
            :isSetCover="cmpOption.isSetCover"
            :forIndex="i"
          />
          <div
            v-show="!options.disabled && !cmpOption.isSetCover"
            title="删除"
            class="delete-box"
            @click="handleDeleteImg(file, i)"
          >
            <i class="el-icon-close"></i>
          </div>
        </div>
        <div
          v-for="(file, i) in reactiveData.filesIng"
          :key="i"
          class="card-item-box"
        >
          <div
            v-show="reactiveData.status === 'uploading'"
            class="uploading-box flex-col-center-center"
          >
            <el-progress
              v-if="reactiveData.lengthComputable"
              type="circle"
              :width="50"
              :percentage="uploadPercentage()"
            />
            <template v-else>
              <i class="el-icon-loading"></i>
              {{ reactiveData.message }}
            </template>
          </div>
        </div>
        <div
          v-if="
            localValue && localValue.length < options.limit && !options.disabled
          "
          title="点击上传"
          class="card-action-box"
          @click="handleUpload"
        >
          <i class="el-icon-plus"></i>
          <span class="card-action-text">点击上传图片</span>
        </div>
      </div>
    </div>
  </template>
  
  <script>
//   import { uploadFiles, uploadStraightFile } from '@/api/commonApi/file';
  import { openLocalFolder } from '@/utils';
  import { ElMessage } from 'element-ui';
  
  export default {
    name: 'BaseUpload',
    props: {
      modelValue: {
        type: Array,
        default: () => [],
      },
      cmpOption: {
        type: Object,
        default: () => ({
          disabled: false,
          isSetCover: false,
        }),
      },
    },
    data() {
      return {
        localValue: [],
        options: {
          limitSize: 10,
          accept: '.bmp,.jpg,.jpeg,.png',
          multiple: false,
          limit: 10,
          disabled: false,
          field: 'files',
          reqParams: { code: 2000 },
          resourceType: 'business',
        },
        reactiveData: {
          files: [],
          filesIng: [],
          status: 'uploading',
          message: '上传中...',
          total: 0,
          loaded: 0,
          lengthComputable: false,
        },
      };
    },
    watch: {
      cmpOption: {
        handler() {
          this.setOptions();
        },
        immediate: true,
      },
      modelValue: {
        handler(n) {
          try {
            let arr = JSON.parse(n);
            if (typeof arr === 'object' && arr != null) {
              this.localValue = arr;
            } else {
              this.localValue = [];
            }
          } catch (error) {
            this.localValue = [];
          }
        },
        immediate: true,
      },
    },
    methods: {
      setOptions() {
        this.options = { ...this.options, ...this.cmpOption };
      },
      uploadPercentage() {
        return parseInt(String(this.reactiveData.loaded / (this.reactiveData.total / 100)));
      },
      handleUpload() {
        const { accept, multiple, field, reqParams } = this.options;
        openLocalFolder(accept, multiple).then((files) => {
          if (!this.filesCheck(files)) {
            return;
          }
          const fileData = new FormData();
          Object.keys(reqParams).forEach((key) => fileData.append(key, reqParams[key]));
          for (const file of Array.from(files)) {
            fileData.append(field, file);
            this.$emit('BaseFormChange', file);
          }
  
          this.reactiveData.filesIng = new Array(files.length);
          this.reactiveData.lengthComputable = false;
          this.reactiveData.status = 'uploading';
          this.reactiveData.total = 0;
          this.reactiveData.loaded = 0;
  
          this.asyncFileUpload(fileData);
        });
      },
      asyncFileUpload(formData) {
        const { resourceType } = this.options;
        let reqApiFunction;
        if (resourceType == 'sys') {
          reqApiFunction = uploadStraightFile;
        } else if (resourceType == 'business') {
          reqApiFunction = uploadFiles;
        }
        reqApiFunction(formData, ({ total, loaded, lengthComputable }) => {
          this.reactiveData.total = total;
          this.reactiveData.loaded = loaded;
        })
          .then((res) => {
            this.reactiveData.status = 'done';
            this.reactiveData.message = '完成';
            this.reactiveData.total = 0;
            this.reactiveData.loaded = 0;
  
            this.updateFileList(res);
          })
          .catch(() => {
            ElMessage.error('上传失败');
            this.reactiveData.filesIng = [];
          });
      },
      async handleDeleteImg(file, i) {
        let modelValue_ = this.localValue.filter((value, index) => index != i);
        this.$emit('update:model-value', JSON.stringify(modelValue_));
      },
      async handleSetCover(file, i) {
        let modelValue_ = this.localValue.filter((value, index) => index != i);
        modelValue_.unshift(this.localValue[i]);
        this.$emit('update:model-value', JSON.stringify(modelValue_));
      },
      filesCheck(files) {
        const { limitSize, accept, limit } = this.options;
        if (this.reactiveData.files.length + files.length > limit) {
          ElMessage.error(`最多上传${limit}个文件`);
          return false;
        }
        for (const file of Array.from(files)) {
          if (!this.checkFileType(file)) {
            ElMessage.error(`请上传${accept}等格式`);
            return false;
          }
          if (!this.checkSize(file)) {
            ElMessage.error(`文件大小不能超过 ${limitSize}MB!`);
            return false;
          }
        }
        return true;
      },
      updateFileList(res) {
        let tempArr = JSON.stringify([...this.localValue, ...res]);
        this.$emit('update:model-value', tempArr);
        this.reactiveData.filesIng = [];
      },
      checkSize({ size }) {
        return size <= this.options.limitSize * 1024 * 1024;
      },
      checkFileType(file) {
        let { accept } = this.options;
        if (!accept || accept === '*') {
          return true;
        }
        accept = accept.toLowerCase().replace(/\s/g, '');
        const acceptTypeList = accept.split(',');
        const fileType = file.name.match(/\.\w*$/)?.shift() ?? '';
        return acceptTypeList.includes(fileType.toLowerCase());
      },
    },
  };
  </script>
  
  <style lang="scss" scoped>
  .upload-container {
    width: 100%;
  
    .tips {
      color: #0006;
      font: 14px 'Alibaba PuHuiTi 2.0-55 Regular';
      line-height: 38px;
    }
  
    .action-box {
      cursor: pointer;
      padding-left: 10px;
  
      .default-action {
        color: #3f9b6a;
      }
    }
  
    .card-list-box {
      width: 100%;
      display: flex;
      flex-wrap: wrap;
  
      .card-item-box {
        position: relative;
        width: 112px;
        height: 112px;
        border: 1px dotted #dcdfe6;
        border-radius: 2px;
        overflow: hidden;
        margin: 0 10px 10px 0;
  
        .delete-box {
          padding: 2px 5px;
          position: absolute;
          right: 0;
          top: 0;
          line-height: normal;
          background-color: #000;
          color: #fff;
          border-radius: 50%;
          transform: translate(40%, -30%);
  
          .el-icon-close {
            font-size: 12px;
            transform: translate(-30%, 20%);
          }
        }
      }
  
      .card-action-box {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        width: 112px;
        height: 112px;
        border: 1px dashed #cdd0d6;
        border-radius: 2px;
        overflow: hidden;
        margin-bottom: 10px;
        background-color: #fafafa;
        cursor: pointer;
        font-size: 12px;
        color: #00000066;
  
        .el-icon-plus {
          font-size: 20px;
        }
  
        &:hover {
          border-color: #3f9b6a;
          color: #3f9b6a;
        }
      }
    }
  
    .text-list-box {
      .text-item-box {
        padding: 0 5px;
        border-radius: 2px;
        overflow: hidden;
  
        .file-name {
          flex: 1;
        }
  
        .text-icon {
          flex-shrink: 0;
          padding-right: 10px;
        }
  
        .el-icon-close {
          cursor: pointer;
          padding-left: 10px;
          flex-shrink: 0;
        }
  
        &:hover {
          background-color: rgba(63, 155, 106, 0.2);
  
          .file-name {
            color: #3f9b6a;
          }
        }
  
        &:nth-child(n + 2) {
          margin-top: 10px;
        }
      }
    }
  
    .uploading-box {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      color: #fff;
      background-color: #c0c4cc;
    }
  
    .progress-box {
      width: 100%;
      height: 100%;
      position: relative;
  
      .el-progress {
        position: absolute;
        right: 10px;
        top: 50%;
        transform: translateY(-50%);
      }
    }
  }
  </style>