字节笔记本字节笔记本

Vue3 Element Plus上传文件

2022-04-25

本文介绍了如何使用Vue3和Element Plus组件库实现文件上传功能,包括获取预览地址、下载Blob文件、处理文件列表和限制上传数量等。

根据当前环境变量获取预览地址

export const getPreviewUrl = () => import.meta.env.VITE_UPLOAD_PREVIEW_URL

下载Blob文件

参见此处

源文件


type computeFileUlrFunc = (upload: UploadResult) => string

const emit = defineEmits(['update:value'])
const slots = useSlots()
console.log('slots: ', slots)

const props = defineProps({
  value: {
    type: Array as () => UploadResult[],
    default: () => [],
  },
  type: {
    type: String as PropType<'file' | 'picture'>,
    validator: (type: string) => ['file', 'picture'].includes(type),
    default: 'file',
  },
  limit: {
    type: Number,
    default: () => undefined,
  },
  accept: {
    type: String,
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  onSuccess: {
    type: Function as PropType<(res: UploadResult) => void>,
  },
  showFileList: {
    type: Boolean,
    default: true,
  },
  btnShow: {
    type: Object,
    default: () => ({ view: true, downLoad: true, delete: true }),
  },
})

const _accept = computed(() => {
  if (props.type === 'picture')
    return 'image/*'
  else return props.accept
})

const fileList = ref<UploadResult[]>([])
const uploadRef = ref()

const canUpload = computed(() => {
  if (props.limit === undefined)
    return true

  return fileList.value.length < props.limit
})

const _fileList: ComputedRef<UploadUserFile[]> = computed(() =>
  fileList.value.map(e => ({
    name: e.fileName,
    status: 'success',
    url: e.fileUrl,
  })),
)

const getFileUrl = computed<computeFileUlrFunc>(
  () => upload => getPreviewUrl() + upload.fileUrl,
)

watchEffect(() => {
  fileList.value = props.value
})

const customUpload = async(params) => {
  const res = await uploadTempFile(params.file)
  if (isFunction(props.onSuccess))
    props.onSuccess(res)
  fileList.value.push(res)
  emit('update:value', fileList.value)
}

const handleOnExceed = () => {
  ElMessage.warning(`超过最大上传数量${props.limit}`)
}

const handleRemove = (index: number) => {
  fileList.value.splice(index, 1)
  emit('update:value', fileList.value)
}

const downloadFile = (file: UploadResult) => {
  downloadByUrl(getPreviewUrl() + file.fileUrl, file.fileName)
}

const handlePreview = (index: number) => {
  // viewerApi({
  //   options: {
  //     toolbar: true,
  //     initialViewIndex: index,
  //     movable: false,
  //     zIndex: 9999,
  //   },
  //   images: fileList.value.map(e => getFileUrl.value(e)),
  // })
}
</script>

<template>
  <div>
    <template v-if="type === 'file'">
      <el-upload
        ref="uploadRef"
        action="#"
        :http-request="customUpload"
        :show-file-list="false"
        :multiple="true"
        :on-exceed="handleOnExceed"
        :limit="limit"
        :file-list="_fileList"
        :disabled="disabled"
        :accept="_accept"
      >
        <el-button :disabled="!canUpload" type="primary">
          上传文件
        </el-button>
      </el-upload>

      <template v-if="showFileList">
        <transition-group v-if="fileList.length > 0" class="file-list" name="el-fade-in" tag="ul">
          <li v-for="(file, index) in fileList" :key="file.fileUrl" class="file-list__item">
            <el-link :underline="false" target="_blank" class="ml-5px" @click="downloadFile(file)">
              <span>{{ file.fileName }}</span>
            </el-link>
            <div class="mr-10px">
              <el-link :underline="false" type="danger" @click="handleRemove(index)">
                删除
              </el-link>
            </div>
          </li>
        </transition-group>
      </template>
    </template>

    <template v-else-if="type === 'picture'">
      <div class="flex mt-1">
        <transition-group class="picture-list" name="el-fade-in" tag="div">
          <div v-for="(file, index) in fileList" :key="file.fileUrl" class="picture-list__item">
            <img :src="getFileUrl(file)" alt="pic" class="picture-list__item-thumbnail">
            <span class="picture-list__item-actions">
              <span @click="handlePreview(index)">
                <IconEpView v-show="btnShow.view" />
              </span>
              <span @click="() => { }">
								下载
              </span>
              <span v-show="btnShow.delete" @click="handleRemove(index)">
                删除
              </span>
            </span>
          </div>
        </transition-group>

        <el-upload
          ref="uploadRef"
          :http-request="customUpload"
          :show-file-list="false"
          :multiple="true"
          :limit="limit"
          :on-exceed="handleOnExceed"
          :file-list="_fileList"
          :disabled="disabled"
          :accept="_accept"
          action="#"
        >
          <div v-if="canUpload" class="upload-picture-card">
            <IconEpPlus class="text-28px inline-block" />
          </div>
        </el-upload>
      </div>
    </template>
  </div>
</template>