首页 前端知识 vue3 ant-design-vue typescript实现导航菜单组件

vue3 ant-design-vue typescript实现导航菜单组件

2024-05-20 14:05:19 前端知识 前端哥 662 341 我要收藏

导航菜单往往是管理后台必不可少的一个功能组件。菜单组件的编写重要的还是逻辑,逻辑清晰无论是什么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哦~ 项目源码在线地址

转载请注明出处或者链接地址:https://www.qianduange.cn//article/8977.html
标签
评论
会员中心 联系我 留言建议 回顶部
复制成功!