AI介绍:
- 显示已上传的图片列表,并提供删除和预览功能。
- 支持上传新的图片,并在上传成功后显示在列表中。
代码的具体步骤如下:
- 在模板中,使用
v-for
指令遍历imageUrls
数组,显示已上传的图片,并为每张图片绑定鼠标移入和移出事件。 - 使用
el-upload
组件实现图片上传功能,配置了上传成功、上传前、上传进度等事件的处理函数。 - 使用
el-dialog
组件实现图片预览功能,点击图片时弹出对话框显示大图。 - 在脚本部分,使用
ref
和watch
来监听图片列表的变化,并处理上传成功、上传前、预览、删除等操作的逻辑。 - 使用
showIcons
函数控制鼠标移入移出时的图标显示和隐藏。
整体来说,该代码实现了一个功能完善的图片上传组件,用户可以上传、预览和删除图片。
父组件使用:
双向绑定后数据会更新
<UploadImg v-model:img-list="formData.afterRepairImg"></UploadImg>
子组件即封装
<template><div class="img-box"><div class="image-container" v-for="(item, i) in imageUrls" :key="item"><img class="image" :src="item" @mouseover="showIcons" @mouseleave="hideIcons" /><div class="icon-container" v-show="showIcons"><el-icon class="zoom-icon" @click="handlePictureCardPreview(item)"><zoom-in /></el-icon><el-icon class="delete-icon" @click="handleRemove(i)"><Delete /></el-icon></div></div><el-uploadclass="avatar-uploader"action="#"list-type="picture-card":http-request="customUpload":on-success="handleAvatarSuccess":before-upload="beforeAvatarUpload":on-preview="handlePictureCardPreview":file-list="fileList":show-file-list="false"multiple><el-icon class="avatar-uploader-icon"><Plus /></el-icon></el-upload></div><el-dialog v-model="dialogVisible" width="90%" top="20px"><img w-full :src="dialogImageUrl" alt="Preview Image" /></el-dialog>
</template><script lang="ts" setup>
import { ref, watch } from 'vue'import { UploadProps, ElMessage, UploadUserFile } from 'element-plus'
import { photoUploadFileAPI } from '@/api/upload'
import { Delete, Download, Files, Plus, ZoomIn } from '@element-plus/icons-vue'const props = defineProps({imgList: {type: Array,required: true}
})const imageUrl = ref('')
const imageUrls = ref([])
const fileList = ref<UploadUserFile[]>([])
const emit = defineEmits(['update:imgList'])// 监听 imgList 的变化
watch(() => props.imgList,(newVal, oldVal) => {if (newVal.length > 0) {// console.log('父组件图片数组', props.imgList, fileList)imageUrls.value = newVal as any}// 在这里可以执行其他逻辑}
)
const handleAvatarSuccess: UploadProps['onSuccess'] = (response, uploadFile) => {// console.log('你从', response, uploadFile)imageUrl.value = URL.createObjectURL(uploadFile.raw!)
}// 上传文件,请求接口,并且将结果存储
function customUpload(file: any) {let formData = new FormData()// console.log('查看', file.file)formData.append('file', file.file)photoUploadFileAPI(formData).then((res: any) => {const imgUrl = res.data.dataimageUrls.value.push(imgUrl)console.log('图片提交返回', res, imageUrls, fileList)emit('update:imgList', imageUrls)})
}
const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => {const isImage = rawFile.type.startsWith('image/') // 检查是否为图片类型if (!isImage) {ElMessage.error('只能上传图片文件!')return false}// if (rawFile.type !== 'image/jpeg') {// ElMessage.error('Avatar picture must be JPG format!')// return false// } else if (rawFile.size / 1024 / 1024 > 2) {// ElMessage.error('Avatar picture size can not exceed 2MB!')// return false// }return true
}const handleUploadProgress: UploadProps['onProgress'] = (event, file, fileList) => {// 处理上传进度的逻辑,例如更新进度条状态const uploadFile = fileList.find(item => item.uid === file.uid)if (uploadFile) {uploadFile.percentage = event.percent // 更新上传进度fileList.value = [...fileList] // 更新文件列表}const fileItem = fileList.value.files.find((f: any) => f.file === file);if (fileItem) {fileItem.progress = Math.round(event.percent);}
}const dialogImageUrl = ref('')
const dialogVisible = ref(false)
const disabled = ref(false)const handlePictureCardPreview: UploadProps['onPreview'] = uploadFile => {dialogImageUrl.value = uploadFile as anydialogVisible.value = true
}const handleRemove = (i: number) => {// 从 fileList 中移除被删除的文件// fileList.value = fileList.value.filter(file => file.uid !== uploadFile.uid)imageUrls.value.splice(i, 1)console.log(i, imageUrls)emit('update:imgList', imageUrls)
}const showControlsIcons = ref(false)
const showIcons = () => {// console.log('鼠标移入')showControlsIcons.value = true
}const hideIcons = () => {// console.log('鼠标移出')showControlsIcons.value = false
}
</script><style lang="scss" scoped>
.img-box {width: 100%;display: flex;flex-wrap: wrap;
.image-container {position: relative;display: inline-block;cursor: pointer;img {border: 1px dashed var(--el-border-color);font-size: 28px;color: #8c939d;width: 150px;height: 150px;text-align: center;border-radius: 10px;object-fit: cover;margin: 0 1px;}
}
}.icon-container {position: absolute;top: 0;left: 0;// top: 50%;// left: 50%;// transform: translate(-50%, -50%);display: flex;align-items: center;justify-content: center;background-color: rgba(0, 0, 0, 0.5);color: #fff;width: 150px;height: 150px;border-radius: 10px;opacity: 0;transition: opacity 0.3s;.el-icon {font-size: 20px;margin: 0 10px;
}
}.image-container:hover .icon-container {opacity: 1;
}.avatar-uploader .avatar {width: 178px;height: 178px;display: block;
}</style><style lang="scss">
.avatar-uploader .el-upload {border: 1px dashed var(--el-border-color);border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;transition: var(--el-transition-duration-fast);
}.avatar-uploader .el-upload:hover {border-color: var(--el-color-primary);
}.el-icon.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 178px;height: 178px;text-align: center;
}.el-upload-list__item {align-items: center;
}.el-dialog__body {margin-top: 20px;text-align: center;img {min-height: 50vh;}
}
</style>