在菜单权限管理开发中,通常需要根据后端返回的菜单列表递归渲染左侧菜单栏以及动态加载路由,这样可以确保用户无法访问没有权限的菜单。为了实现这个功能,我们需要进行以下步骤:
-
获取菜单列表数据:调用菜单接口
/menu
获取菜单列表数据。 -
渲染左侧菜单栏:使用递归的方式根据菜单列表数据格式来渲染左侧菜单栏(sidebar)。递归可以遍历菜单数据,根据数据的嵌套结构来递归渲染菜单的子菜单。
-
将菜单列表转换为 Vue 路由格式:在渲染菜单的过程中,需要将菜单列表数据转换为符合 Vue 路由的格式。Vue 路由需要包含路径(path)和组件(component)等信息。根据菜单列表的数据结构,进行适当的转换来生成 Vue 路由格式的数据。
-
动态添加路由:将菜单列表转换为 Vue 路由格式的数据后,可以使用 router.addRoute 方法动态添加路由。
本篇文章将介绍动态渲染左侧菜单列表部分,接下来我们看下具体实现方式。
布局结构设计
布局结构可以简单分为三部分,顶部导航栏(navbar)+左侧菜单栏(sidebar)+主要内容(main),如下图
在 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
则是能点击跳转的菜单。来看一下后端返回的菜单数据结构
由此可知,如果一条数据有children
则说明它是目录,否则则是菜单,这样就好办了,我们可以判断每条数据有没有children
有的话渲染el-sub-menu
,没有则渲染el-menu-item
。这里可以再创建一个sidebaritem
组件接收一个item
属性,如果item
有children
,则再次调用自身组件把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>
启动项目,登录之后便可看到如下页面
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
我们发现图标加载出来了,这里菜单数据库中配置的图标都是menu
菜单折叠
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 {
isCollapse: false,
menuList: [],
};
},
actions: {
async GenerateRoutes() {
const { data } = await getMenuList({});
this.menuList = data;
return data;
},
},
});
然后在sidebar
中使用
同时在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>
这里会有个问题,菜单折叠后,图标也跟着折叠了
但是我们想将图标留下,该怎么做呢。其实很简单,只需要将el-sub-menu
设置为grid
就行了
这时就会发现图标留下来了
到这里菜单列表的渲染就已经完成了,下一篇文章将介绍如何动态添加路由,欢迎点赞收藏+关注!!
原文链接:https://juejin.cn/post/7259356504759140413 作者:东方小月