导航菜单往往是管理后台必不可少的一个功能组件。菜单组件的编写重要的还是逻辑,逻辑清晰无论是什么UI库做起来都不难,大同小异罢了。
首先处理好路由数据
// route/index.ts import { createRouter, createWebHistory } from 'vue-router'; import Layout from '@/layout/index.vue'; const routes: Array<RouteRecordRaw> = [ { path: '/', name: 'layout', component: Layout, redirect: '/home', children: [ { path: '/home', name: 'home', meta: { title: 'home', icon: 'home' }, component: () => import('@/views/home/index.vue'), }, { path: '/form', name: 'form', meta: { title: 'form', icon: 'form' }, component: () => import('@/views/form/index.vue'), }, { path: '/table', name: 'table', meta: { title: 'table', icon: 'table' }, component: () => import('@/views/table/index.vue'), redirect: '/table/first', children: [ { path: '/table/first', name: 'tableFirst', meta: { title: 'table', subTitle: 1 }, component: () => import('@/views/table/first.vue'), }, { path: '/table/secound', name: 'tableSecound', meta: { title: 'table', subTitle: 2, isHidden: true }, component: () => import('@/views/table/secound.vue'), }, ], }, ], }, { path: '/:pathMatch(.*)', name: '404', component: () => import('@/views/404.vue'), }, { path: '/login', name: 'login', component: () => import('@/views/login/index.vue'), }, ]; const router = createRouter({ history: createWebHistory(), routes, }); export default router;
复制
在上面的路由ts文件中,我们需要用来做菜单的只有path为 ‘/’,name为’layout’的路由项中children字段的内容,所以在渲染之前我们要先进行过滤(动态路由的思路也差不多)
// sideBar.vue <template> <!-- 这里的class="h-100% w-250px" 为Unocss写法,具体可以自行百度了解下原子化css --> <a-menu v-model:openKeys="menuState.openKeys" v-model:selectedKeys="menuState.selectedKeys" class="h-100% w-250px" mode="inline" @click="handleClickMenu" > <MenuItem :routes="routes" v-if="routes" /> </a-menu> </template> <script setup lang="ts"> import { MenuProps } from 'ant-design-vue/es'; import MenuItem from './menuItem.vue'; const route = useRoute(); const router = useRouter(); // 过滤出需要添加到菜单栏中的路由 const routes = computed(() => { // 取出meta中isHidden不为true的路由(isHidden为true时不在导航菜单中渲染) function _noHidden(_routes: RouteRecordRaw[]) { const filterRoute: RouteRecordRaw[] = []; _routes.forEach((_route) => { if (!_route?.meta?.isHidden) { if (!_route.children || _route.children.length === 0) { filterRoute.push(_route); } else { filterRoute.push({ ..._route, children: _noHidden(_route.children)! || [], }); } } }); return filterRoute; } return _noHidden( // 这里先取出name为 layout 的children中路由数据 router.getRoutes().find((item) => item.name === 'layout')!.children ); }); const menuState = reactive<{ selectedKeys: string[]; openKeys: string[] }>({ selectedKeys: [], openKeys: [], }); const handleClickMenu: MenuProps['onClick'] = (menuInfo) => { router.push({ path: menuInfo.key as string }); }; watchEffect(() => { // 路由菜单改变时 更新 selectedKeys 跟 openKeys 的值 实现菜单同步展开/高亮 menuState.selectedKeys = [route.path]; const keyList: any = route.path.slice(1).split('/'); if (keyList.length === 1) { menuState.openKeys = ['']; return; } for (let index = 0; index < route.path.length; index++) { if (route.path[index] === '/') { menuState.openKeys.push(route.path.substr(0, index)); } } menuState.openKeys.shift(); }); </script> <style lang="scss" scoped></style>
复制
// menuItem.vue <template> <template v-for="item in routes" :key="item.path"> <a-menu-item :key="item.path" v-if="!item.children || item.children.length === 0" > <template #icon> <!-- svg-icon 组件为动态使用svg图标封装的组件。可注释,具体请查看项目源码 --> <svg-icon class="text-14px mr-4px" v-if="item.meta && item.meta.icon" :name="item.meta.icon as string" /> </template> <span>{{ item.meta?.title }}</span> </a-menu-item> <template v-else> <a-sub-menu :key="item.path"> <template #icon> <svg-icon class="text-14px mr-4px" v-if="item.meta && item.meta.icon" :name="item.meta.icon as string" /> </template> <template #title> <span>{{ item.meta?.title }}</span> </template> <menuItem :routes="item.children" /> </a-sub-menu> </template> </template> </template> <script setup lang="ts"> // defineOptions为vue3.3新增特性 用法见vue官网。在这里主要是为了给递归组件命名 defineOptions({ name: 'menuItem', }); defineProps<{ routes: Array<RouteRecordRaw> }>(); </script>
复制
最后贴上源码地址,有用的话记得给个star哦~ 项目源码在线地址