预览效果:


组件使用:
<template>
<ImageUploader ref="user_ImageUploaderRef" :default-img-url="defaultUserAvatar" size="small"></ImageUploader>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import ImageUploader from '@/components/ImageUploader/index.vue' // 图片上传组件
import defaultUserAvatar from '@/assets/images/Ai-Train/trainPaper/userAvatar.png'
const user_ImageUploaderRef = ref<InstanceType<typeof ImageUploader>>()
// 发起请求 获取用户之前设置的图片
const sendRequest = () => {
let info = ......
if(info.avatar){
user_ImageUploaderRef.value?.setImageUrl(info.avatar)
}
}
// 发起请求 更新用户现在设置的图片
const submitForm = () => {
let userAvatarData = user_ImageUploaderRef.value?.getUploadInfo()
......
}
</script>
注意:手机端 el-upload 组件不能使用 accept 属性,否则可能会出现点击文件后组件无反应bug
代码实现:
<!-- 图片上传组件 -->
<template>
<el-upload
ref="uploadRef"
class="avatar-uploader"
action="#"
:limit="1"
:disabled="Boolean(uploadImgUrl)"
accept="image/*"
:auto-upload="false"
:show-file-list="false"
:on-change="handleUploadFile"
>
<div class="avatar-img-box" :class="$props.size">
<template v-if="uploadImgUrl">
<el-image class="avatar-img" :src="uploadImgUrl" />
<div class="mask-box" :class="$props.size">
<el-icon class="mask-icon" @click.stop="previewImage"><ZoomIn /></el-icon>
<el-icon class="mask-icon" @click.stop="removeImage"><Delete /></el-icon>
</div>
</template>
<template v-else>
<el-image class="avatar-img" :src="$props.defaultImgUrl" alt="默认图片" />
</template>
</div>
</el-upload>
<!-- 图片预览弹窗 -->
<el-image-viewer
v-if="isPreviewAvatar"
:url-list="previewUrlList"
teleported
hide-on-click-modal
@close="handleClosePreviewer"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { UploadInstance, UploadRawFile, UploadFile, ElMessage } from 'element-plus'
const $props = defineProps({
// 默认图片路径
defaultImgUrl: {
type: String,
default: '',
required: true
},
// 组件整体尺寸大小
size: {
type: String as () => 'small' | 'default' | 'large',
default: 'default'
}
})
const uploadRef = ref<UploadInstance>()
// 用户上传文件
const uploadFile = ref<UploadRawFile>()
// 图片路径
const uploadImgUrl = ref<string>('')
// 上传文件
const handleUploadFile = (file: UploadFile) => {
if (file.raw && checkUploadFile(file.raw)) {
uploadFile.value = file.raw
uploadImgUrl.value = URL.createObjectURL(file.raw)
} else {
uploadRef.value?.clearFiles()
uploadFile.value = undefined
}
}
// 图片校验
function checkUploadFile(rawFile: UploadRawFile) {
const isImg = rawFile.type.startsWith('image/')
const isLt2M = rawFile.size / 1024 / 1024 < 2
if (!isImg) {
ElMessage.error('上传文件只能是图片格式!')
}
if (!isLt2M) {
ElMessage.error('文件大小不能超过 2MB!')
}
return isImg && isLt2M
}
// 移除图片
const removeImage = () => {
uploadFile.value = undefined
uploadImgUrl.value = ''
uploadRef.value?.clearFiles()
}
const isPreviewAvatar = ref(false)
const previewUrlList = ref<string[]>([])
// 预览图片
const previewImage = () => {
previewUrlList.value = [uploadImgUrl.value]
isPreviewAvatar.value = true
}
// 关闭预览组件
const handleClosePreviewer = () => {
isPreviewAvatar.value = false
previewUrlList.value = []
}
// 设置预设图片
const setImageUrl = (imgUrl: string) => {
uploadImgUrl.value = imgUrl
}
// 获取上传的图片数据
const getUploadInfo = () => {
let result = {
type: uploadFile.value ? 'file' : 'url',
data: uploadFile.value || uploadImgUrl.value
}
return result
}
defineExpose({
setImageUrl,
getUploadInfo
})
</script>
<style scoped lang="scss">
.avatar-uploader {
.avatar-img-box {
position: relative;
padding: 5px;
border: 1px dotted var(--el-border-color);
border-radius: 6px;
&:hover {
border-color: var(--el-color-primary);
}
.avatar-img {
width: 100%;
height: 100%;
}
&:hover {
.mask-box {
display: flex;
}
}
}
.mask-box {
position: absolute;
left: 5px;
top: 5px;
align-items: center;
justify-content: space-evenly;
opacity: 0.8;
display: none;
background-color: #666;
cursor: auto;
.mask-icon {
color: #fff;
font-size: 16px;
cursor: pointer;
&:hover {
color: var(--el-color-primary);
}
}
}
.small {
width: 40px;
height: 40px;
}
.default {
width: 60px;
height: 60px;
}
.large {
width: 80px;
height: 80px;
}
}
</style>