diff --git a/src/components/upload/index.vue b/src/components/upload/index.vue
index bbef816..31354bf 100644
--- a/src/components/upload/index.vue
+++ b/src/components/upload/index.vue
@@ -1,82 +1,152 @@
<template>
<el-upload
- class="avatar-uploader"
+ class="upload-component"
+ v-model:file-list="fileList"
:action="action"
:show-file-list="false"
- :on-success="handleAvatarSuccess"
- :before-upload="beforeAvatarUpload"
+ :disabled="limit !==1 && fileList.length >= limit"
+ :accept="accept.toString()"
+ :on-success="handleSuccess"
+ :on-exceed="onExceed"
>
- <img v-if="imageUrl" :src="baseUrl + imageUrl" class="avatar" />
- <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
+ <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 { computed, ref } from 'vue'
-import { ElMessage } from 'element-plus'
-import { Plus } from '@element-plus/icons-vue'
-import type { UploadProps } from 'element-plus'
+
+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;
+ 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): void
+ (e: 'update:modelValue', val: string | Array<string>): void
}
const props = withDefaults(defineProps<Props>(), {
- limit: 1
+ limit: 1,
+ resultKey: 'filePath',
+ modelType: 'string',
+ accept: () => [],
})
const emits = defineEmits<Emits>()
-const action = import.meta.env.VITE_API_URL + props.url
+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,
+})
-const imageUrl = computed({
- get () {
- return props.modelValue
- },
- set (val) {
- emits('update:modelValue', val || '')
+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 imageUrl = ref('')
-const handleAvatarSuccess: UploadProps['onSuccess'] = (
- response,
- uploadFile
-) => {
- // console.log('response', response)
- // imageUrl.value = URL.createObjectURL(uploadFile.raw!)
- imageUrl.value = response.data.shortPath
+const getSrcList = () => {
+ return fileList.value.filter(item => item.status == "success").map(item => item.url!)
}
-const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
- if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/jpg' && rawFile.type !== 'image/png') {
- ElMessage.error('图片格式不支持!')
- return false
- } else if (rawFile.size / 1024 / 1024 > 2) {
- ElMessage.error('Avatar picture size can not exceed 2MB!')
- return false
- }
- return true
+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
}
-</script>
-<style scoped>
-.avatar-uploader .avatar {
- width: 178px;
- height: 178px;
- display: block;
+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)
+ }
}
-</style>
+</script>
<style>
-.avatar-uploader .el-upload {
+.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;
@@ -85,15 +155,7 @@ const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
transition: var(--el-transition-duration-fast);
}
-.avatar-uploader .el-upload:hover {
+.avatar-uploader:hover {
border-color: var(--el-color-primary);
}
-
-.el-icon.avatar-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- width: 178px;
- height: 178px;
- text-align: center;
-}
</style>
\ No newline at end of file