Vuetify多级菜单激活异常问题解决方案咨询
问题本质
你遇到的问题是因为v-for循环里所有子菜单共用了同一个激活状态变量/activator绑定,Vue的响应式系统没法区分不同菜单的状态,最后就导致所有子菜单的激活状态都被最后一个父菜单的实例覆盖了。
解决办法
给每个父菜单单独分配激活状态,别用全局变量。两种实现方式任选:
- 给菜单数据里每个父项加个
isActive属性,专门跟踪这个菜单的激活状态 - 用一个对象/数组存每个菜单的激活状态,拿菜单的
name或path当唯一标识
修正后的代码示例
Header.vue
先看你可能写错的版本(反面例子):
<template> <v-app-bar app> <v-menu v-model="menuActive"> <template v-slot:activator="{ on, attrs }"> <v-btn v-bind="attrs" v-on="on">父菜单</v-btn> </template> <v-list> <v-list-item v-for="item in subMenu" :key="item.path" :to="item.path"> {{ item.name }} </v-list-item> </v-list> </v-menu> </v-app-bar> </template> <script> export default { data() { return { menuActive: false, menuItems: [/* 你的父菜单数据 */] } } } </script>
改成这样就没问题了(用每个菜单自己的激活状态):
<template> <v-app-bar app> <!-- 循环渲染所有父菜单 --> <v-menu v-for="(menu, index) in menuItems" :key="menu.name" <!-- 用唯一标识当key,别用index --> v-model="menu.isActive" <!-- 绑定当前菜单自己的isActive --> > <template v-slot:activator="{ on, attrs }"> <v-btn v-bind="attrs" v-on="on"> {{ menu.name }} </v-btn> </template> <v-list> <!-- 渲染当前父菜单的子项 --> <v-list-item v-for="subItem in menu.children" :key="subItem.name" :to="subItem.path" @click="menu.isActive = false" <!-- 点完子菜单就关父菜单 --> > {{ subItem.name }} </v-list-item> </v-list> </v-menu> </v-app-bar> </template> <script> export default { data() { return { // 给每个父菜单加isActive属性,初始都是false menuItems: [ { name: '父菜单1', path: '/parent1', isActive: false, children: [ { name: '子菜单1-1', path: '/parent1/child1' }, { name: '子菜单1-2', path: '/parent1/child2' } ] }, { name: '父菜单2', path: '/parent2', isActive: false, children: [ { name: '子菜单2-1', path: '/parent2/child1' }, { name: '子菜单2-2', path: '/parent2/child2' } ] } ] } } } </script>
如果你的菜单是从路由里动态拿的,就在created钩子给每个父路由加isActive:
created() { // 过滤出有子路由的父项,给每个加isActive this.menuItems = this.$router.options.routes.filter(route => route.children) .map(route => ({ ...route, isActive: false })); }
路由配置(router/index.js)
确保路由结构是父路由包含children的格式:
import Vue from 'vue' import VueRouter from 'vue-router' import Parent1 from '../views/Parent1.vue' import Child1 from '../views/Child1.vue' import Parent2 from '../views/Parent2.vue' import Parent2Child1 from '../views/Parent2Child1.vue' Vue.use(VueRouter) const routes = [ { path: '/parent1', name: 'Parent1', component: Parent1, children: [ { path: 'child1', name: 'Child1', component: Child1 }, { path: 'child2', name: 'Child2', component: () => import('../views/Child2.vue') } ] }, { path: '/parent2', name: 'Parent2', component: Parent2, children: [ { path: 'child1', name: 'Parent2Child1', component: Parent2Child1 } ] } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router
App.vue
正常引入Header和路由视图就行:
<template> <v-app> <Header /> <v-main> <router-view /> </v-main> </v-app> </template> <script> import Header from './components/Header.vue' export default { components: { Header } } </script>
重点提醒
- 每个
v-menu必须绑定独立的v-model,循环里绝对不能用同一个变量 - 循环的
:key一定要用菜单的唯一标识(比如name或path),别用index,不然Vue会复用组件实例,状态还是乱 - 点击子菜单后手动关闭父菜单,用户体验更好
内容的提问来源于stack exchange,提问作者None-sleep
相关产品推荐
相关产品推荐

