Vue3中如何为含二级子数组的菜单树设置激活状态?
解决方案
问题根源
navItems是普通常量,非响应式数据,修改active后无法触发视图更新NavBarItem传递的是点击事件对象而非目标item,父组件无法定位到要修改的导航项- 未处理嵌套子项的激活状态重置与设置逻辑
代码修改步骤
1. 更新 NavBar.vue,将导航数据改为响应式并实现激活逻辑
<template> <nav class="mt-2 px-2"> <NavBarItem @click="setActiveLink" :item="item" v-for="(item, _index) in navItems" :key="item.label" /> </nav> </template> <script setup> import { ref } from 'vue' // 将navItems改为响应式ref const navItems = ref([ { href: '#', active: false, label: 'VehicleBuild', icon: vehicleTruckCube, children: [ { href: '/vehicle-packages', active: false, label: 'Paket', icon: CubeIcon, children: [], }, ], } ]) // 递归重置所有导航项的active状态 const resetAllActive = (items) => { items.forEach(item => { item.active = false if (item.children.length) resetAllActive(item.children) }) } // 设置激活项 const setActiveLink = (targetItem) => { // 先重置所有项的激活状态 resetAllActive(navItems.value) // 设置目标项为激活 targetItem.active = true // 可选逻辑:若需要父项随子项激活,可添加递归向上查找父项并设置active的代码 } </script>
2. 更新 NavBarItem.vue,传递点击的目标 item 而非事件对象
<script setup> import { RouterLink } from 'vue-router' import { computed } from 'vue' import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue' import { ChevronDownIcon } from '@heroicons/vue/24/outline' const props = defineProps({ item: Object, }) const hasActiveChild = computed(() => { function hasActiveItem(items) { return items.some(item => item.active || hasActiveItem(item.children)) } return hasActiveItem(props.item.children) }) const emit = defineEmits(['click']) </script> <template> <RouterLink v-if="!item.children.length" :class="[ 'group flex w-full items-center rounded-md py-2 px-3 text-sm', 'hover:bg-gray-100', item.active ? 'text-gray-800 font-semibold' : 'text-gray-600 font-medium', ]" :to="item.href" @click="emit('click', item)" <!-- 传递目标item而非事件对象 --> > <component :class="[ 'w-6 h-6 shrink-0 mr-2 group-hover:text-gray-600', item.active ? 'text-gray-600' : 'text-gray-400', ]" :is="item.icon" v-if="item.icon" ></component> <span>{{ item.label }}</span> </RouterLink> <Disclosure v-else v-slot="{ open }" :default-open="hasActiveChild"> <DisclosureButton :class="[ 'group flex w-full text-left items-center rounded-md py-2 px-3 text-sm', 'hover:bg-gray-100', open ? 'font-semibold text-gray-800' : 'font-medium text-gray-600', ]" > <component :class="[ 'w-6 h-6 shrink-0 mr-2 group-hover:text-gray-600', open ? 'text-gray-600' : 'text-gray-400', ]" :is="item.icon" v-if="item.icon" ></component> <span class="flex-1">{{ item.label }}</span> <ChevronDownIcon :class="['w-4 h-4 shrink-0', open ? '-rotate-180 text-gray-600' : 'text-gray-400']" /> </DisclosureButton> <DisclosurePanel class="ml-4"> <NavBarItem v-for="(child, index) in item.children" :item="child" :key="index" @click="emit('click', $event)" <!-- 直接传递子项触发的事件 --> /> </DisclosurePanel> </Disclosure> </template>
额外说明
- 若需要父项随子项激活自动激活,可在
setActiveLink中添加递归向上查找父项并设置active的逻辑(需给每个item添加parent属性或通过递归遍历记录父项) - 若希望基于当前路由自动设置激活状态,可结合
useRoute()钩子,在组件挂载时根据route.path匹配item.href来设置active
内容的提问来源于stack exchange,提问作者Daniel Öhling
相关产品推荐
相关产品推荐

