oss-file-upload.vue 4.12 KB
<template>
  <div class="oss-file-upload">
    <el-upload
      action=""
      :show-file-list="false"
      :multiple="multiple"
      :limit="limit"
      :accept="accept"
      :disabled="disabled || uploading"
      :before-upload="beforeUpload"
      :http-request="customUpload"
      :on-exceed="onExceed"
    >
      <el-button size="mini" type="primary" plain :loading="uploading">
        <i class="el-icon-upload2"></i> {{ buttonText }}
      </el-button>
      <span slot="tip" class="upload-tip">{{ tip }}</span>
    </el-upload>
    <ul v-if="fileList.length" class="oss-file-list">
      <li v-for="(file, index) in fileList" :key="file.fileId || file.url || index" class="oss-file-item">
        <a class="oss-file-name" :href="file.url" target="_blank" rel="noopener noreferrer">
          <i class="el-icon-document"></i>
          <span>{{ file.name || '文件' }}</span>
        </a>
        <i v-if="!disabled" class="el-icon-close oss-file-remove" @click="handleRemove(index)"></i>
      </li>
    </ul>
  </div>
</template>

<script>
import { ossUploadFile } from '@/utils/oss-upload'

const SIZE_UNITS = { KB: 1024, MB: 1024 * 1024, GB: 1024 * 1024 * 1024 }

export default {
  name: 'OssFileUpload',
  props: {
    value: {
      type: Array,
      default: () => []
    },
    /** OSS 凭证类型,开单附件默认 annexpic */
    ossType: {
      type: String,
      default: 'annexpic'
    },
    buttonText: {
      type: String,
      default: '选择文件'
    },
    tip: {
      type: String,
      default: '支持图片、PDF,单文件不超过 10MB'
    },
    accept: {
      type: String,
      default: 'image/*,.pdf,.doc,.docx'
    },
    multiple: {
      type: Boolean,
      default: true
    },
    limit: {
      type: Number,
      default: 9
    },
    fileSize: {
      type: Number,
      default: 10
    },
    sizeUnit: {
      type: String,
      default: 'MB'
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      uploading: false
    }
  },
  computed: {
    fileList: {
      get() {
        return Array.isArray(this.value) ? this.value : []
      },
      set(list) {
        this.$emit('input', list)
      }
    }
  },
  methods: {
    beforeUpload(file) {
      const unit = SIZE_UNITS[this.sizeUnit] || SIZE_UNITS.MB
      if (this.fileSize && file.size / unit >= this.fileSize) {
        this.$message.warning(`文件大小不能超过 ${this.fileSize}${this.sizeUnit}`)
        return false
      }
      return true
    },
    onExceed() {
      this.$message.warning(`最多上传 ${this.limit} 个文件`)
    },
    async customUpload({ file }) {
      this.uploading = true
      try {
        const data = await ossUploadFile(file, this.ossType)
        const item = {
          name: data.name || file.name,
          fileId: data.name || data.fileId || '',
          url: data.url || ''
        }
        if (!item.url) {
          throw new Error('上传成功但未返回文件地址')
        }
        this.fileList = [...this.fileList, item]
        this.$message.success('上传成功')
      } catch (e) {
        this.$message.error(e.message || '上传失败')
      } finally {
        this.uploading = false
      }
    },
    handleRemove(index) {
      const next = [...this.fileList]
      next.splice(index, 1)
      this.fileList = next
    }
  }
}
</script>

<style lang="scss" scoped>
.oss-file-upload {
  width: 100%;
}
.upload-tip {
  margin-left: 8px;
  font-size: 11px;
  color: #94a3b8;
}
.oss-file-list {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
}
.oss-file-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px;
  margin-bottom: 4px;
  background: #f8fafc;
  border-radius: 8px;
  border: 1px solid #e2e8f0;
}
.oss-file-name {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  color: #2563eb;
  font-size: 12px;
  text-decoration: none;
  span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}
.oss-file-remove {
  flex-shrink: 0;
  margin-left: 8px;
  cursor: pointer;
  color: #94a3b8;
  &:hover {
    color: #ef4444;
  }
}
</style>