使用element 卡片el-card设置阻止冒泡点击事件

在Vue中,尝试通过`@click`阻止事件冒泡时遇到错误:无法设置Event对象的`bubbles`属性。错误提示表明该属性只读。解决方法是使用修饰符`@click.self`,这将确保事件处理函数仅在事件发生在元素本身时触发,从而阻止冒泡行为。

最开始使用@click=“hand”

在hand方法里边设置阻止冒泡事件 报错:Cannot set property bubbles of #<Event> which has only a getter 只能获取不能修改属性

解决方法:@click.self="hand",阻止冒泡

<template> <div class="uploadBox "> <el-upload ref="uploadRef" class="avatar-uploader" :action="uploadAction" :headers="initData.headers" :show-file-list="false" :on-success="handleAvatarSuccess" :on-error="handleUploadError" :before-upload="beforeAvatarUpload" :disabled="disabled || !!imageUrl" > <!-- 已上传的图片:点击预览 --> <div v-if="imageUrl" class="image-preview-wrapper" @click.stop="handlePreview"> <img :src="imageUrl" class="avatar" :style="{ objectFit: fitMode }" /> <div class="preview-mask"> <el-icon class="preview-icon"><ZoomIn /></el-icon> <span class="preview-text">{{ $t('components.clickPreview') }}</span> </div> </div> <!-- 未上传:显示上传区域 --> <div v-else class="upload-img"> <div class="img-box" :style="imageStyle"></div> <div class="upload-text"> <icon-add class="iconColor" /> <span class="upload-title">{{ title || $t('components.image') }}</span> </div> </div> </el-upload> <!-- 删除按钮 --> <el-button v-if="imageUrl && !disabled" class="delete-btn " type="danger" size="small" circle @click.stop="handleDelete" > <el-icon><Delete /></el-icon> </el-button> <!-- 图片预览器 --> <el-image-viewer v-if="showViewer" :url-list="[imageUrl]" :initial-index="0" @close="closeViewer" :z-index="3000" hide-on-click-modal /> </div> </template> <script setup> import notification from '@/utils/notification' import { ElMessageBox } from 'element-plus' import { ref, computed, watch, onMounted } from 'vue' import { Delete, ZoomIn, Upload } from '@element-plus/icons-vue' import { ElImageViewer } from 'element-plus' import { getToken } from '@/utils/auth' import uploadImg from '@/assets/images/upload-img.png' import licenImg from '@/assets/images/licen.png' import obverseImg from '@/assets/images/obverselmg.png' import { getVisitorId } from '@/utils/fingerprintjs.js' import { useI18n } from 'vue-i18n' const { t } = useI18n() // 定义 props:接受父组件传递的参数 const props = defineProps({ // 标题 title: { type: String, default: '' }, // 类型:front-正面, obverse-反面, enterprise-企业 type: { type: String, default: '' }, index: { type: Number, default: 0 }, // 上传接口地址 action: { type: String, default: '/csc/member/upload' }, // 最大文件大小(MB) maxSize: { type: Number, default: 20 }, // 是否禁用 disabled: { type: Boolean, default: false }, // 回显图片URL modelValue: { type: String, default: '' }, // 是否检查文件名包含身份证关键词 checkIdCardKeyword: { type: Boolean, default: false }, // 图片显示模式:contain-完整显示, cover-填充裁剪 fitMode: { type: String, default: 'fill', validator: (value) => ['contain', 'cover', 'fill', 'scale-down'].includes(value) } }) // 定义事件 const emit = defineEmits(['update:modelValue', 'uploadSuccess', 'uploadError', 'delete']) // 上传 ref const uploadRef = ref(null) // 图片 URL const imageUrl = ref(props.modelValue || '') // 上传状态 const isUploaded = ref(false) // 图片预览器显示状态 const showViewer = ref(false) // 监听 modelValue 变化(回显) watch( () => props.modelValue, (newVal) => { imageUrl.value = newVal || '' }, { immediate: true } ) // 计算背景图片样式 const imageStyle = computed(() => { if (props.type === 'business_license') { return { 'background-image': `url(${licenImg})` } } if (props.type === 'front') { return { 'background-image': `url(${uploadImg})` } } if (props.type === 'obverse') { return { 'background-image': `url(${obverseImg})` } } return {} }) // 上传接口地址 const uploadAction = computed(() => props.action) // 上传请求头 // const uploadHeaders = computed(() => ({ // Authorization: `Bearer ${getToken()}` // })) const initData = ref({ maskVisible: false, headers: { Authorization: 'Bearer ' + getToken() }, uploadImg: '', progressVisible: false, progressPercentage: 0, progressStatus: '' }) // 上传前验证 const beforeAvatarUpload = (file) => { console.log('开始上传文件:', file) // 验证文件类型(修复:使用 === 而不是 =) const isJPG = file.type === 'image/jpeg' const isPNG = file.type === 'image/png' const isJPEG = file.type === 'image/jpeg' const isWebP = file.type === 'image/webp' if (!isJPG && !isPNG && !isJPEG && !isWebP) { notification.error(t('components.uploadFailed') + ',' + t('components.onlySupportJpgPngJpegWebp')) return false } // 验证文件大小 const isLtMaxSize = file.size / 1024 / 1024 < props.maxSize if (!isLtMaxSize) { notification.error(`${t('components.uploadFailed')},${t('components.imageSizeCannotExceed')} ${props.maxSize}MB`) return false } // 可选:验证文件名包含身份证关键词 if (props.checkIdCardKeyword) { const fileName = file.name.toLowerCase() const idCardKeywords = ['身份证', 'idcard', 'id-card', '身份证电子版'] const hasKeyword = idCardKeywords.some(keyword => fileName.includes(keyword)) if (hasKeyword) { notification.error(t('components.uploadFailed') + ',' + t('components.noElectronicIdCard')) return false } } return true } // 上传成功处理 const handleAvatarSuccess = (response, uploadFile) => { console.log('上传成功:', response, uploadFile) // 获取图片 URL(根据实际接口返回格式调整) const url = response.data?.url || response.url || URL.createObjectURL(uploadFile.raw) imageUrl.value = url isUploaded.value = true // 通知父组件 emit('update:modelValue', url) emit('uploadSuccess', { type: props.type, url: url, file: uploadFile },props.index) notification.success(t('components.imageUploadSuccess')) } // 上传失败处理 const handleUploadError = (error, uploadFile) => { console.error('上传失败:', error) emit('uploadError', { type: props.type, error: error, file: uploadFile }) notification.error(t('components.imageUploadFailed')) } // 删除图片 const handleDelete = async () => { try { await ElMessageBox.confirm(t('components.confirmDeleteImage'), t('common.info'), { confirmButtonText: t('common.confirm'), cancelButtonText: t('common.cancel'), type: 'warning', }) imageUrl.value = '' isUploaded.value = false // 通知父组件更新 v-model emit('update:modelValue', '') emit('delete', props.type,props.index) notification.success(t('components.deleteSuccess')) } catch { // 用户取消删除 } } // 手动触发上传 const triggerUpload = () => { uploadRef.value?.$refs?.['upload-inner']?.$refs?.input?.click() } // 清空上传 const clearUpload = () => { imageUrl.value = '' isUploaded.value = false emit('update:modelValue', '') } // 预览图片 const handlePreview = () => { if (imageUrl.value) { showViewer.value = true } } // 关闭预览 const closeViewer = () => { showViewer.value = false } const getvisitorData = async () => { let visitorId = localStorage.getItem('X-VISITOR-ID') if (!visitorId) { visitorId = await getVisitorId() // 安全调用 async } if (visitorId) { initData.value.headers['X-VISITOR-ID'] = visitorId } else { initData.value.headers['X-VISITOR-ID'] = 'default-visitor-id' } } // 暴露方法给父组件 defineExpose({ uploadRef, isUploaded, imageUrl, triggerUpload, clearUpload }) onMounted(() => { getvisitorData() }) </script> <style lang="scss" scoped> .uploadBox { position: relative; width: 200px; height: 180px; background: #ffffff0c; border: 1px solid #ffffff19; border-radius: 8px; overflow: hidden; transition: all 0.3s ease; &:hover { border-color: #1D5CCC; box-shadow: 0 2px 12px rgba(29, 92, 204, 0.15); } .avatar-uploader { width: 100%; height: 100%; } .avatar { width: 100%; height: 100%; display: block; /* // object-fit 通过 props.fitMode 动态设置 // 默认 contain:完整显示图片,保持比例 // cover:填充容器,可能裁剪 // fill:拉伸填充 // scale-down:取 none 或 contain 中较小者 */ } /* // 图片预览包装器 */ .image-preview-wrapper { position: relative; width: 100%; height: 100%; cursor: pointer; overflow: hidden; background: #f5f7fa; /* 添加背景色,避免透明区域不清晰*/ .avatar { width: 100%; height: 100%; display: block; transition: transform 0.3s ease; /* // object-fit 通过 props.fitMode 动态设置 */ } /* // 预览遮罩层 */ .preview-mask { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; flex-direction: column; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s ease; .preview-icon { font-size: 32px; color: #fff; margin-bottom: 8px; } .preview-text { color: #fff; font-size: 14px; font-weight: 500; } } &:hover { .preview-mask { opacity: 1; } .avatar { transform: scale(1.05); } } } .upload-img { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; cursor: pointer; .img-box { width: 90px; height: 62px; margin: 28px 0; background-repeat: no-repeat; background-size: 100% 100%; background-position: center; } .upload-text { display: flex; align-items: center; justify-content: center; text-align: center; margin-top: 10px; gap: 6px; .upload-title { color: #808080; font-size: 14px; transition: color 0.3s ease; } } &:hover .upload-title { color: #1D5CCC; } } /* // 删除按钮 */ .delete-btn { position: absolute; top: 8px; right: 8px; z-index: 10; background: rgba(255, 77, 79, 0.9); border: none; transition: all 0.3s ease; &:hover { background: rgba(255, 77, 79, 1); transform: scale(1.1); } } /* // 重新上传按钮 */ .reupload-btn { position: absolute; top: 8px; right: 48px; z-index: 10; background: rgba(29, 92, 204, 0.9); border: none; transition: all 0.3s ease; &:hover { background: rgba(29, 92, 204, 1); transform: scale(1.1); } } :deep(.el-upload) { width: 100%; height: 100%; } :deep(.el-upload--text) { width: 100%; height: 100%; text-align: center; display: flex; align-items: center; justify-content: center; } :deep(.el-upload.is-disabled) { cursor: not-allowed; opacity: 0.6; } } .iconColor { width: 16px; height: 16px; fill: #1D5CCC; transition: fill 0.3s ease; } .upload-img:hover .iconColor { fill: #1D5CCC; } </style>当点击的时候怎么设置背景颜色
最新发布
11-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大白菜1号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值