在菜单权限管理开发中,通常需要根据后端返回的菜单列表递归渲染左侧菜单栏以及动态加载路由,这样可以确保用户无法访问没有权限的菜单。为了实现这个功能,我们需要进行以下步骤:

  • 获取菜单列表数据:调用菜单接口/menu获取菜单列表数据。

  • 渲染左侧菜单栏:使用递归的方式根据菜单列表数据格式来渲染左侧菜单栏(sidebar)。递归可以遍历菜单数据,根据数据的嵌套结构来递归渲染菜单的子菜单。

  • 将菜单列表转换为 Vue 路由格式:在渲染菜单的过程中,需要将菜单列表数据转换为符合 Vue 路由的格式。Vue 路由需要包含路径(path)和组件(component)等信息。根据菜单列表的数据结构,进行适当的转换来生成 Vue 路由格式的数据。

  • 动态添加路由:将菜单列表转换为 Vue 路由格式的数据后,可以使用 router.addRoute 方法动态添加路由。

本篇文章将介绍动态渲染左侧菜单列表部分,接下来我们看下具体实现方式。

布局结构设计

布局结构可以简单分为三部分,顶部导航栏(navbar)+左侧菜单栏(sidebar)+主要内容(main),如下图

图片
1690191053986.png

在 src 下新建layout目录来存放这些布局,目录结构如下

-- layout
   -- components
     -- appmain.vue
     -- sidebar.vue
     -- navbar.vue
   -- index.vue

并在index.vue中分别引入它们

<template>
  <div class='flex'>
    <div class='h-screen'>
      <sidebar />
    </div>
    <div class='flex-1'>
      <navbar />
      <appmain />
    </div>
  </div>

</template>

<script lang='ts' setup>
import sidebar from './
components/sidebar.vue';
import navbar from '
./components/navbar.vue';
import appmain from '
./components/appmain.vue';
</script>

左侧菜单栏渲染

菜单栏的渲染我们使用element plus中提供的Menu组件开发,其中el-menu中的el-sub-menu代表目录,el-menu-item则是能点击跳转的菜单。来看一下后端返回的菜单数据结构

图片
image.png

由此可知,如果一条数据有children则说明它是目录,否则则是菜单,这样就好办了,我们可以判断每条数据有没有children有的话渲染el-sub-menu,没有则渲染el-menu-item。这里可以再创建一个sidebaritem组件接收一个item属性,如果itemchildren,则再次调用自身组件把children当作item传入

<template>
  <el-sub-menu v-if='props.item.children?.length' :index='props.item.path'>
    <template #title>
      <span>{{ props.item.name }}</span>
    </template>
    <sidebaritem
      v-for='i in props.item.children'
      :key='i.id'
      :item='i'
    >
</sidebaritem>
  </el-sub-menu>

  <el-menu-item v-else :index='props.item.path'>
    <span>{{ props.item.name }}</span>
  </el-menu-item>

</template>

<script lang='ts' setup>
type Props = {
  item: any;
};
const props = defineProps<Props>();
</script>

然后在sidebar中引入,这里的菜单列表取自pinia中的menuList,是在动态添加路由那里获取的,下篇文章会介绍

<template>
  <div class='h-[100%] bg-[#545c64]'>
    <div class='h-[50px]  text-white flex items-center justify-center'>
      <span v-if='!homeStore.isCollapse'>权限管理系统</span>
    </div>
    <el-scrollbar class='wrap-scroll'>
      <el-menu
        :unique-opened='true'
        :collapse='homeStore.isCollapse'
        active-text-color='#ffd04b'
        @select='getPath'
        background-color='#545c64'
        class='el-menu-vertical-demo w-[223px] !border-r-0'
        default-active='2'
        text-color='#fff'
      >

        <sidebaritem
          v-for='item in homeStore.menuList'
          :key='item.id'
          :item='item'
        />

      </el-menu>
    </el-scrollbar>
  </div>

</template>

<script lang='ts' setup>
import sidebaritem from './
sidebaritem.vue';
import home from '
@/store';
import { useRouter } from '
vue-router';
const router = useRouter();
const homeStore = home();
</script>
<style>
.wrap-scroll {
  height: calc(100% - 50px);
}
</style>

启动项目,登录之后便可看到如下页面

图片
image.png

Icon 加载

菜单接口返回结果中还有一个 icon 字段,我们可以根据这个字段来渲染菜单的 Icon,同样的我们使用elementplus中的Icon组件

首先在 main.ts 中全局引入@element-plus/icons-vue

import * as ElementPlusIconsVue from '@element-plus/icons-vue';

const app = createApp(App);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component);
}

sidebaritem中动态渲染 Icon

图片
image.png

我们发现图标加载出来了,这里菜单数据库中配置的图标都是menu

图片
image.png

菜单折叠

Menu 组件中有一个collapse属性,我们可以根据它控制菜单折叠,可以将其定义在store

//store/index.ts
import { defineStore } from 'pinia';
import { getMenuList } from '@/http/menu/index';
import router from '@/router';
import { MenuVo } from '@/http/menu/types/menu.vo';
type StoreState = {
  isCollapse: boolean,
  menuList: MenuVo[],
};
export default defineStore('home', {
  state: (): StoreState => {
    return {
      isCollapsefalse,
      menuList: [],
    };
  },
  actions: {
    async GenerateRoutes() {
      const { data } = await getMenuList({});
      this.menuList = data;
      return data;
    },
  },
});

然后在sidebar中使用

图片
image.png

同时在navbar中控制是否折叠

//layout/components/navbar
<template>
  <div class='p-2  shadow-sm'>
    <div @click='handleFold'>
      <Fold v-if='!homeStore.isCollapse' class='w-6 cursor-pointer' />
      <Expand v-else class='w-6 cursor-pointer' />
    </div>
  </div>

  <div class='p-2'></div>
</template>

<script lang='ts' setup>
import { Fold, Expand } from '@element-plus/icons';
import home from '
@/store';
const homeStore = home();
const handleFold = () => {
  homeStore.$patch({
    isCollapse: !homeStore.isCollapse,
  });
};
</script>

这里会有个问题,菜单折叠后,图标也跟着折叠了

图片
image.png

但是我们想将图标留下,该怎么做呢。其实很简单,只需要将el-sub-menu设置为grid就行了

图片
image.png

这时就会发现图标留下来了

图片
image.png

到这里菜单列表的渲染就已经完成了,下一篇文章将介绍如何动态添加路由,欢迎点赞收藏+关注!!

想要源码的同学可以留言获取~