mirror of
https://github.com/fantastic-admin/basic.git
synced 2026-10-07 15:11:29 +08:00
278 lines
6.7 KiB
Vue
Executable File
278 lines
6.7 KiB
Vue
Executable File
<script setup lang="ts">
|
||
import type { UploadProps } from 'element-plus'
|
||
import { ElMessage } from 'element-plus'
|
||
|
||
defineOptions({
|
||
name: 'ImagesUpload',
|
||
})
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
action: UploadProps['action']
|
||
headers?: UploadProps['headers']
|
||
data?: UploadProps['data']
|
||
name?: UploadProps['name']
|
||
url?: string[]
|
||
size?: number
|
||
max?: number
|
||
width?: number
|
||
height?: number
|
||
placeholder?: string
|
||
notip?: boolean
|
||
ext?: string[]
|
||
}>(),
|
||
{
|
||
name: 'file',
|
||
url: () => [],
|
||
size: 2,
|
||
max: 3,
|
||
width: 150,
|
||
height: 150,
|
||
placeholder: '',
|
||
notip: false,
|
||
ext: () => ['jpg', 'png', 'gif', 'bmp'],
|
||
},
|
||
)
|
||
|
||
const emits = defineEmits<{
|
||
'update:url': [
|
||
url: string[],
|
||
]
|
||
'onSuccess': [
|
||
res: any,
|
||
]
|
||
}>()
|
||
|
||
const uploadData = ref({
|
||
dialogImageIndex: 0,
|
||
imageViewerVisible: false,
|
||
progress: {
|
||
preview: '',
|
||
percent: 0,
|
||
},
|
||
})
|
||
|
||
// 预览
|
||
function preview(index: number) {
|
||
uploadData.value.dialogImageIndex = index
|
||
uploadData.value.imageViewerVisible = true
|
||
}
|
||
// 关闭预览
|
||
function previewClose() {
|
||
uploadData.value.imageViewerVisible = false
|
||
}
|
||
// 移除
|
||
function remove(index: number) {
|
||
const url = props.url
|
||
url.splice(index, 1)
|
||
emits('update:url', url)
|
||
}
|
||
// 移动
|
||
function move(index: number, type: 'left' | 'right') {
|
||
const url = props.url
|
||
if (type === 'left' && index !== 0) {
|
||
url[index] = url.splice(index - 1, 1, url[index])[0]
|
||
}
|
||
if (type === 'right' && index !== url.length - 1) {
|
||
url[index] = url.splice(index + 1, 1, url[index])[0]
|
||
}
|
||
emits('update:url', url)
|
||
}
|
||
|
||
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||
const fileName = file.name.split('.')
|
||
const fileExt = fileName.at(-1) ?? ''
|
||
const isTypeOk = props.ext.includes(fileExt)
|
||
const isSizeOk = file.size / 1024 / 1024 < props.size
|
||
if (!isTypeOk) {
|
||
ElMessage.error(`上传图片只支持 ${props.ext.join(' / ')} 格式!`)
|
||
}
|
||
if (!isSizeOk) {
|
||
ElMessage.error(`上传图片大小不能超过 ${props.size}MB!`)
|
||
}
|
||
if (isTypeOk && isSizeOk) {
|
||
uploadData.value.progress.preview = URL.createObjectURL(file)
|
||
}
|
||
return isTypeOk && isSizeOk
|
||
}
|
||
const onProgress: UploadProps['onProgress'] = (file) => {
|
||
uploadData.value.progress.percent = ~~file.percent
|
||
}
|
||
const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||
uploadData.value.progress.preview = ''
|
||
uploadData.value.progress.percent = 0
|
||
emits('onSuccess', res)
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="upload-container">
|
||
<div v-for="(item, index) in (url as string[])" :key="index" class="images">
|
||
<ElImage v-if="index < max" :src="item" :style="`width:${width}px;height:${height}px;`" fit="cover" />
|
||
<div class="mask">
|
||
<div class="actions">
|
||
<span title="预览" @click="preview(index)">
|
||
<SvgIcon name="ep:zoom-in" class="icon" />
|
||
</span>
|
||
<span title="移除" @click="remove(index)">
|
||
<SvgIcon name="ep:delete" class="icon" />
|
||
</span>
|
||
<span v-show="url.length > 1" title="左移" :class="{ disabled: index === 0 }" @click="move(index, 'left')">
|
||
<SvgIcon name="ep:back" class="icon" />
|
||
</span>
|
||
<span v-show="url.length > 1" title="右移" :class="{ disabled: index === url.length - 1 }" @click="move(index, 'right')">
|
||
<SvgIcon name="ep:right" class="icon" />
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<ElUpload
|
||
v-show="url.length < max"
|
||
:show-file-list="false"
|
||
:headers="headers"
|
||
:action="action"
|
||
:data="data"
|
||
:name="name"
|
||
:before-upload="beforeUpload"
|
||
:on-progress="onProgress"
|
||
:on-success="onSuccess"
|
||
drag
|
||
class="images-upload"
|
||
>
|
||
<div class="image-slot" :style="`width:${width}px;height:${height}px;`">
|
||
<SvgIcon name="ep:plus" class="icon" />
|
||
</div>
|
||
<div v-show="uploadData.progress.percent" class="progress" :style="`width:${width}px;height:${height}px;`">
|
||
<ElImage :src="uploadData.progress.preview" :style="`width:${width}px;height:${height}px;`" fit="fill" />
|
||
<ElProgress type="circle" :width="Math.min(width, height) * 0.8" :percentage="uploadData.progress.percent" />
|
||
</div>
|
||
</ElUpload>
|
||
<div v-if="!notip" class="el-upload__tip">
|
||
<div style="display: inline-block;">
|
||
<ElAlert :title="`上传图片支持 ${ext.join(' / ')} 格式,单张图片大小不超过 ${size}MB,建议图片尺寸为 ${width}*${height},且图片数量不超过 ${max} 张`" type="info" show-icon :closable="false" />
|
||
</div>
|
||
</div>
|
||
<ElImageViewer v-if="uploadData.imageViewerVisible" :url-list="url as string[]" :initial-index="uploadData.dialogImageIndex" teleported @close="previewClose" />
|
||
</div>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
.upload-container {
|
||
line-height: initial;
|
||
}
|
||
|
||
.el-image {
|
||
display: block;
|
||
}
|
||
|
||
.images {
|
||
position: relative;
|
||
display: inline-block;
|
||
margin-right: 10px;
|
||
border: 1px dashed var(--el-border-color);
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
|
||
.mask {
|
||
opacity: 0;
|
||
position: absolute;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: var(--el-overlay-color-lighter);
|
||
transition: opacity 0.3s;
|
||
|
||
.actions {
|
||
width: 100px;
|
||
height: 100px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
@include position-center(xy);
|
||
|
||
span {
|
||
width: 50%;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
color: var(--el-color-white);
|
||
transition: color 0.1s, transform 0.1s;
|
||
|
||
&.disabled {
|
||
color: var(--el-text-color-disabled);
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
&:hover:not(.disabled) {
|
||
transform: scale(1.5);
|
||
}
|
||
|
||
.icon {
|
||
font-size: 24px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
&:hover .mask {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.images-upload {
|
||
display: inline-block;
|
||
vertical-align: top;
|
||
}
|
||
|
||
:deep(.el-upload) {
|
||
.el-upload-dragger {
|
||
display: inline-block;
|
||
padding: 0;
|
||
|
||
&.is-dragover {
|
||
border-width: 1px;
|
||
}
|
||
|
||
.image-slot {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
width: 100%;
|
||
height: 100%;
|
||
color: var(--el-text-color-placeholder);
|
||
background-color: transparent;
|
||
|
||
.icon {
|
||
font-size: 30px;
|
||
}
|
||
}
|
||
|
||
.progress {
|
||
position: absolute;
|
||
top: 0;
|
||
|
||
&::after {
|
||
content: "";
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
left: 0;
|
||
top: 0;
|
||
background-color: var(--el-overlay-color-lighter);
|
||
}
|
||
|
||
.el-progress {
|
||
z-index: 1;
|
||
|
||
@include position-center(xy);
|
||
|
||
.el-progress__text {
|
||
color: var(--el-text-color-placeholder);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|