修改图片上传组件,优化架构
zk authored at 2024-01-02 15:27:59
5.56 KiB
NewLife.QuickVue
<template>
  <el-upload
    class="upload-component"
    v-model:file-list="fileList"
    :action="action"
    :show-file-list="false"
    :disabled="limit !==1 && fileList.length >= limit"
    :accept="accept.toString()"
    :on-success="handleSuccess"
    :on-exceed="onExceed"
  >
    <draggable v-model="fileList" :item-key="(val: UploadUserFile) => getItemSrc(val)" class="flex flex-wrap" :animation="200">
      <template #item="{element, index}">
        <div class="relative mr-2 flex group" @click.stop :key="getItemSrc(element)" v-if="fileList.length <= limit || index >= limit">
          <el-image
            fit="cover"
            class="w-28 h-28 rounded mb-2"
            :preview-src-list="fileList.map(v => getItemSrc(v))"
            :initial-index="index"
            :src="getItemSrc(element)">
            <template #error>
              <div class="bg-gray-200 w-full h-full flex justify-center items-center font-bold text-green-500 text-xl flex-col border border-green-500" v-if="element.status === 'success'">
                <el-icon class="!text-4xl !text-green-500"><CircleCheck /></el-icon>
                {{ getItemSrc(element).split('.').reverse()[0] }}
              </div>
            </template>
          </el-image>
          <div class="absolute inset-0 flex justify-center items-center p-4" v-if="element.status === 'ready' || element.status === 'uploading'">
            <el-progress :text-inside="true" :stroke-width="20" :percentage="element.percentage" class="w-full" status="success"/>
          </div>
          <div class="!absolute right-0 top-0 bg-black/80 w-6 h-6 flex justify-center items-center rounded-tr opacity-0 group-hover:opacity-100 duration-75" @click="fileList.splice(index, 1)">
            <el-icon class="!text-white !text-xl"><Close /></el-icon>
          </div>
        </div>
      </template>
      <template #footer>
        <div class="avatar-uploader w-28 h-28 flex justify-center items-center flex-col" key="btn" :class="{'opacity-50': limit !== 1 && fileList.length >= limit}">
          <template v-if="limit === 1 && fileList.length === 1">
            <el-icon class="!text-3xl !text-gray-500"><Switch /></el-icon>
            <div class="text-gray-500 text-sm mt-2">更换</div>
          </template>
          <template v-else-if="limit !== 1 && fileList.length >= limit">
            <Icon class="text-gray-500 text-3xl" icon="system-uicons:no-sign"></Icon>
            <div class="text-gray-500 text-sm mt-2">(上传数量已满)</div>
          </template>
          <template v-else>
            <el-icon class="!text-3xl !text-gray-500"><Plus /></el-icon>
            <div class="text-gray-500 text-sm mt-2" v-if="limit > 1">({{fileList.length + ' / ' + limit }})</div>
          </template>
        </div>
      </template>
    </draggable>
  </el-upload>
</template>

<script lang="ts" setup>

import { ref } from 'vue'
import { Plus, Close, CircleCheck, Switch } from '@element-plus/icons-vue'
import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
import { AcceptEnum } from './enum';
import { watch } from 'vue';
import draggable from 'vuedraggable'

interface Props {
  modelValue?: string | Array<string>;
  maxSize?: number;
  limit?: number;
  url?: string;
  resultKey?: string;
  accept?: Array<AcceptEnum | string>;
  modelType?: 'string' | 'array';
}
interface Emits {
  (e: 'update:modelValue', val: string | Array<string>): void
}

const props = withDefaults(defineProps<Props>(), {
  limit: 1,
  resultKey: 'filePath',
  modelType: 'string',
  accept: () => [],
})
const emits = defineEmits<Emits>()

const fileList = ref<UploadUserFile[]>([]);
const action = (import.meta.env.DEV ? '/base-api' : import.meta.env.VITE_API_URL) + props.url
const baseUrl = import.meta.env.VITE_IMG_BASE_URL
const onExceed = (_: any, uploadFiles: UploadUserFile[]) => {
  uploadFiles.splice(0, 1)
}

watch(fileList, () => {
  let srcList = getSrcList();
  if (props.modelValue?.toString() !== srcList.toString()) {
    emits('update:modelValue', props.modelType === 'string' ? srcList.toString() : srcList)
  }
}, {
  deep: true,
})

watch(() => props.modelValue, (val) => {
  if (val?.toString() !== getSrcList().toString()) {
    if (!val || !val.length) {
      fileList.value = []
    } else {
      fileList.value = (typeof val === 'string' ? val.split(',') : val).map(url => ({
        name: url,
        url,
        status: "success",
      }))
    }
  }
})

const getSrcList = () => {
  return fileList.value.filter(item => item.status == "success").map(item => item.url!)
}

const getItemSrc = (item: UploadUserFile & { rawUrl?: string }) => {
  let url = ""
  if (item.rawUrl)
    url = item.rawUrl;
  else if (item.raw && item.raw.type.indexOf('image') === 0) {
    item.rawUrl = URL.createObjectURL(item.raw)
    url = item.rawUrl
  } else if (item?.url)
    url = baseUrl + item.url
  else if (item?.response)
    url = baseUrl + (item.response as any).data[props.resultKey]
  return url
}

const handleSuccess: UploadProps['onSuccess'] = (_, uploadFile: UploadFile) => {
  uploadFile.url = (uploadFile.response as any).data[props.resultKey]
  if (props.limit === 1 && fileList.value.length > 1) {
    fileList.value.splice(0, fileList.value.length - 1)
  }
}
</script>

<style>
.upload-component .el-upload {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: flex-start;
}
.avatar-uploader {
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: var(--el-transition-duration-fast);
}

.avatar-uploader:hover {
  border-color: var(--el-color-primary);
}
</style>