You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js中v-menu下拉项点击无法唤起自定义确认组件问题

问题解决:v-menu下拉项无法唤起确认对话框

问题现象

页面头部普通按钮点击能正常弹出自定义确认组件,但点击v-menu下拉菜单里的选项时,确认组件无法正常打开。

问题原因

  1. v-menu事件拦截:v-menu的列表项点击后会触发菜单自动关闭,事件冒泡会干扰确认对话框的渲染流程。
  2. 组件状态不同步:Confirmation组件内部的dialogVisible没有和父组件传入的modelValue绑定,父组件状态变更时,组件内部状态未同步更新。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 19:44:55