灏天阁

vue3.2/Vite/Ts25: 递归组件生成动态菜单

· Yin灏

新建 menu 组件

  • src / layout / menu / index.vue
<template>
  <template v-for="(item, index) in menuList" :key="item.path">
    <!-- 没有子路由 -->
    <template v-if="!item.children">
      <el-menu-item v-if="!item.meta.hidden" :index="item.path">
        <template #title>
          <span>{{ item.meta.title }}</span>
        </template>
      </el-menu-item>
    </template>
    <!-- 只有一个子路由 -->
    <template v-if="item?.children?.length === 1">
      <el-menu-item
        v-if="!item.children[0].meta.hidden"
        :index="item.children[0].path"
      >
        <template #title>
          <span>{{ item.children[0].meta.title }}</span>
        </template>
      </el-menu-item>
    </template>
    <!-- 有超过1个的子路由 -->
    <el-sub-menu v-if="item?.children?.length > 1" :index="item.path">
      <template #title>
        <span>{{ item.meta.title }}</span>
      </template>
      <!-- 重点:递归组件 -->
      <menu :menuList="item.children"></menu>
    </el-sub-menu>
  </template>
</template>
<script setup lang="ts">
  defineProps(["menuList"]);
</script>

<!-- 给当前组件取一个名字,不然递归组件用不了 -->
<script lang="ts">
  export default {
    name: "Menu",
  };
</script>

处理路由文件

  • src / router / routes.ts
  • 同时给每一个路由添加路由元信息
export const contantRoute = [
  {
    path: "/login",
    component: () => import("@/views/login/index.vue"),
    name: "login",
    meta: {
      title: "登录", // 菜单标题
      hidden: true, // 此路由在导航中是否隐藏
    },
  },
  //...
  {
    path: "/",
    component: () => import("@/layout/index.vue"),
    name: "layout",
    meta: {
      title: "layout",
      hidden: false,
    },
    children: [
      {
        path: "/home",
        component: () => import("@/views/home/index.vue"),
        meta: {
          title: "首页",
          hidden: false,
        },
      },
    ],
  },
  {
    path: "/404",
    component: () => import("@/views/404/index.vue"),
    name: "404",
    meta: {
      title: "404",
      hidden: true,
    },
  },
  {
    path: "/:pathMatch(.*)*",
    redirect: "/404",
    name: "Any",
    meta: {
      title: "任意路由",
      hidden: true,
    },
  },
  //...
];

将路由挂载在用户小仓库

  • src / store / modules / user.ts
//...
import { contantRoute } from "@/router/routes";
//...
let useUserStore = defineStore("User", {
  state: (): UserState => {
    return {
      //...
      // 将路由对象挂载到用户小仓库
      menuRoutes: contantRoute,
    };
  },
});

修改 UserState 类型值,用来满足 menuRoutes 的 ts 类型检测

  • src / store / modules / types / type.ts
import type { RouteRecordRaw } from "vue-router";
export interface UserState {
  token: string | null;
  menuRoutes: RouteRecordRaw[];
}

使用新建 menu 组件

  • src / layout / index.vue
<template>
  <div class="layout_container">
    <!-- 左侧菜单 -->
    <div class="layout_slider">
      <Logo></Logo>
      <!-- 展示菜单 -->
      <el-scrollbar class="scrollbar">
        <el-menu background-color="#001529" text-color="white">
          <menu :menuList="userStore.menuRoutes"></menu>
        </el-menu>
      </el-scrollbar>
    </div>
    <!-- 顶部导航 -->
    <div class="layout_tabbar"></div>
    <!-- 内容展示区域 -->
    <div class="layout_main"></div>
  </div>
</template>
<script setup lang="ts">
  import Logo from "./logo/index.vue";
  import Menu from "./menu/index.vue";
  // 获取用户先关小仓库
  import useUserStore from "@/store/modules/user";
  const userStore = useUserStore();
</script>
<style scoped lang="scss">
  .scrollbar {
    width: 100%;
    height: calc(100vh - $base-menu-logo-height);
  }
</style>

- Book Lists -