Vue.js中v-menu下拉项点击无法唤起自定义确认组件问题
问题现象
页面头部普通按钮点击能正常弹出自定义确认组件,但点击v-menu下拉菜单里的选项时,确认组件无法正常打开。
问题原因
- v-menu事件拦截:v-menu的列表项点击后会触发菜单自动关闭,事件冒泡会干扰确认对话框的渲染流程。
- 组件状态不同步:Confirmation组件内部的
dialogVisible没有和父组件传入的modelValue绑定,父组件状态变更时,组件内部状态未同步更新。 - v-if导致组件销毁:父组件用
v-if控制Confirmation组件的显示/隐藏,组件频繁销毁重建可能和v-menu的关闭动画冲突,导致渲染异常。
修复方案
1. 阻止v-menu列表项的事件冒泡
给v-list-item的click事件添加.stop修饰符,避免菜单关闭逻辑干扰对话框显示:
<v-list-item v-for="(item, index) in userOptions" :key="index" class="dropdown" @click.stop="opendialog()" >
2. 同步Confirmation组件的状态
在组件内部监听modelValue的变化,自动同步dialogVisible状态:
// 在Confirmation.vue的setup中添加watch import { ref, watch } from 'vue' setup(props, { emit }) { const dialogVisible = ref(props.modelValue) watch(() => props.modelValue, (newVal) => { dialogVisible.value = newVal }) // ... 其他代码 }
3. 替换v-if为v-show(可选)
用v-show替代v-if控制组件显示,避免组件频繁销毁重建:
<Confirmation v-model="isConfirmDialogVisible" v-show="isConfirmDialogVisible" action-button="submit" action-button-left="cancle" action-button-right="confirm" >
修改后的完整代码
App.vue
<template> {{isConfirmDialogVisible}} <div class="header"> <div class="left-menus"> <div v-for="(item, index) in items" :key="index" class="menu" style="background-color: red; gap: 10px; padding: 10px" @click="opendialog()" > {{ item.title }} </div> </div> <v-menu open-on-hover transition="slide-y-transition"> <template #activator="{ props }"> <div style="cursor: pointer"> <v-icon v-bind="props">mdi-menu-down</v-icon> </div> </template> <v-list> <v-list-item v-for="(item, index) in userOptions" :key="index" class="dropdown" @click.stop="opendialog()" > <v-list-item-title>{{ item.title }}</v-list-item-title> </v-list-item> </v-list> </v-menu> <Confirmation v-model="isConfirmDialogVisible" v-show="isConfirmDialogVisible" action-button="submit" action-button-left="cancle" action-button-right="confirm" > <div style="font-size: 20px">Are you sure?</div> </Confirmation> </div> </template> <script> import { ref } from 'vue' import Confirmation from './Confirmation.vue' const isConfirmDialogVisible = ref(false) const userOptions = ref([{ title: 'Logout' }, { title: 'Setting' }]) const opendialog = () => { isConfirmDialogVisible.value = true } export default { components: { Confirmation, }, data: () => ({ items: [ { title: 'Click Me' }, { title: 'Click Me' }, { title: 'Click Me' }, { title: 'Click Me 2' }, ], opendialog, isConfirmDialogVisible, userOptions, }), } </script> <style> .header .left-menus { display: flex; align-items: center; gap: 16px; } </style>
Confirmation.vue
<template> <v-dialog v-model="dialogVisible" persistent width="auto"> <div class="dialog_container"> <v-card> <div class="card-components"> <div class="warning_image"></div> <v-card-text> <slot></slot> </v-card-text> <v-card-actions> <v-btn class="back" @click="closeDialog" >{{ actionButtonLeft }}</v-btn > <v-btn @click="confirmSelection" class="confirm-submit"> {{ actionButtonRight}} </v-btn> </v-card-actions> </div> </v-card> </div> </v-dialog> </template> <script> import { ref, watch } from 'vue' export default { props: { modelValue: Boolean, actionButton: { type: String, required: true, }, actionButtonRight: { type: String, required: true, }, actionButtonLeft: { type: String, required: false, default: 'cancel', }, }, emits: ['update:modelValue', 'confirmAction'], setup(props, { emit }) { const dialogVisible = ref(props.modelValue) // 监听父组件状态变化,同步对话框显示 watch(() => props.modelValue, (newVal) => { dialogVisible.value = newVal }) const closeDialog = () => { dialogVisible.value = false emit('update:modelValue', false) } const confirmSelection = () => { closeDialog() emit('confirmAction') } const returnBack = () => { closeDialog() } return { dialogVisible, closeDialog, returnBack, confirmSelection, } }, } </script> <style scoped> .card-components { display: flex; flex-direction: column; gap: 24px; } .dialog_container { height: 225px; width: 648px; } .v-card { padding: 16px 24px 24px 24px; } .v-card-text { padding: 0; } .warning_image { display: flex; width: 100%; justify-content: center; } .v-card-actions { min-height: 0; padding: 0; display: flex; justify-content: flex-end; } .v-card-actions .v-btn { border: 1px solid #9ca3af; color: #9ca3af; width: 140px; justify-content: center; align-items: center; border-radius: 4px; } .v-card-actions .confirm-submit { color: white; border: 1px solid #41a1e0; background: #41a1e0; } </style>
内容的提问来源于stack exchange,提问作者soumatech
相关产品推荐
相关产品推荐

