第四节:菜单权限、动态路由注册、菜单搭建、面包屑、按钮权限 剖析
一. 菜单权限-动态路由注册
1. 核心思路
(1). 首先我们知道不同账号登录,会拥有不同的菜单权限,在前端仅仅需要把有权限的路由注册进去即可,而不是把所有路由都注册了,这就是所谓的动态路由注册。
(2). 前端先把所有的路由都建立出来,然后接口中返回有权限的菜单,这里接口中返回的菜单数据 需要 能和前端建立出来的路由进行关联比对,我们采用路径的方式,即接口中返回一个路径(如:/main/system/user),这个路径恰好是前端路由中的path字段。
(3). 最终把接口中返回的菜单,且这些菜单在和前端构建的所有路由通过path相等比对,即能匹配上的路由,进行注册,添加到vue-router中。
补充:
上述步骤只是为了将路由动态注册到vue-router里,与菜单显示无关,菜单显示需要借助menu组件,通过递归数据进行显示,详见下面的(二. 菜单搭建 )
2. 实现步骤
(1). 准备 基础路由 和 前端所有的菜单路由。
A. 基础路由代码
const routes: Array<RouteRecordRaw> = [ { path: '/', redirect: '/login', }, { path: '/login', name: 'login', component: () => import('@/views/login/login.vue'), }, { path: '/main', name: 'main', component: () => import('@/views/main/main.vue'), }, { path: '/:pathMatch(.*)*', name: 'notFound', component: () => import('@/views/not-found/not-found.vue'), }, ]; const router = createRouter({ history: createWebHistory(), routes, });
B. 所有的菜单路由代码,一个菜单对应一个路由文件,如下图:(未来这些菜单路由都是添加为 main 的子路由 )
user.ts代码如下:
const user = () => import('@/views/main/system/user/user.vue'); export default { path: '/main/system/user', name: 'user', component: user, children: [], };
(2). 请求接口获得具有权限的菜单数据,存入vuex中
接口数据如下:
[{ "id": 38, "name": "系统总览", "type": 1, "url": "/main/analysis", "icon": "el-icon-monitor", "sort": 1, "children": [{ "id": 39, "url": "/main/analysis/overview", "name": "核心技术", "sort": 106, "type": 2, "children": null, "parentId": 38 }, { "id": 40, "url": "/main/analysis/dashboard", "name": "商品统计", "sort": 107, "type": 2, "children": null, "parentId": 38 }] }, { "id": 1, "name": "系统管理", "type": 1, "url": "/main/system", "icon": "el-icon-setting", "sort": 2, "children": [{ "id": 2, "url": "/main/system/user", "name": "用户管理", "sort": 100, "type": 2, "children": [{ "id": 5, "url": null, "name": "创建用户", "sort": null, "type": 3, "parentId": 2, "permission": "system:users:create" }, { "id": 6, "url": null, "name": "删除用户", "sort": null, "type": 3, "parentId": 2, "permission": "system:users:delete" }, { "id": 7, "url": null, "name": "修改用户", "sort": null, "type": 3, "parentId": 2, "permission": "system:users:update" }, { "id": 8, "url": null, "name": "查询用户", "sort": null, "type": 3, "parentId": 2, "permission": "system:users:query" }], "parentId": 1 }, { "id": 3, "url": "/main/system/department", "name": "部门管理", "sort": 101, "type": 2, "children": [{ "id": 17, "url": null, "name": "创建部门", "sort": null, "type": 3, "parentId": 3, "permission": "system:department:create" }, { "id": 18, "url": null, "name": "删除部门", "sort": null, "type": 3, "parentId": 3, "permission": "system:department:delete" }, { "id": 19, "url": null, "name": "修改部门", "sort": null, "type": 3, "parentId": 3, "permission": "system:department:update" }, { "id": 20, "url": null, "name": "查询部门", "sort": null, "type": 3, "parentId": 3, "permission": "system:department:query" }], "parentId": 1 }, { "id": 4, "url": "/main/system/menu", "name": "菜单管理", "sort": 103, "type": 2, "children": [{ "id": 21, "url": null, "name": "创建菜单", "sort": null, "type": 3, "parentId": 4, "permission": "system:menu:create" }, { "id": 22, "url": null, "name": "删除菜单", "sort": null, "type": 3, "parentId": 4, "permission": "system:menu:delete" }, { "id": 23, "url": null, "name": "修改菜单", "sort": null, "type": 3, "parentId": 4, "permission": "system:menu:update" }, { "id": 24, "url": null, "name": "查询菜单", "sort": null, "type": 3, "parentId": 4, "permission": "system:menu:query" }], "parentId": 1 }, { "id": 25, "url": "/main/system/role", "name": "角色管理", "sort": 102, "type": 2, "children": [{ "id": 26, "url": null, "name": "创建角色", "sort": null, "type": 3, "parentId": 25, "permission": "system:role:create" }, { "id": 27, "url": null, "name": "删除角色", "sort": null, "type": 3, "parentId": 25, "permission": "system:role:delete" }, { "id": 28, "url": null, "name": "修改角色", "sort": null, "type": 3, "parentId": 25, "permission": "system:role:update" }, { "id": 29, "url": null, "name": "查询角色", "sort": null, "type": 3, "parentId": 25, "permission": "system:role:query" }], "parentId": 1 }] }, { "id": 9, "name": "商品中心", "type": 1, "url": "/main/product", "icon": "el-icon-goods", "sort": 3, "children": [{ "id": 15, "url": "/main/product/category", "name": "商品类别", "sort": 104, "type": 2, "children": [{ "id": 30, "url": null, "name": "创建类别", "sort": null, "type": 3, "parentId": 15, "permission": "system:category:create" }, { "id": 31, "url": null, "name": "删除类别", "sort": null, "type": 3, "parentId": 15, "permission": "system:category:delete" }, { "id": 32, "url": null, "name": "修改类别", "sort": null, "type": 3, "parentId": 15, "permission": "system:category:update" }, { "id": 33, "url": null, "name": "查询类别", "sort": null, "type": 3, "parentId": 15, "permission": "system:category:query" }], "parentId": 9 }, { "id": 16, "url": "/main/product/goods", "name": "商品信息", "sort": 105, "type": 2, "children": [{ "id": 34, "url": null, "name": "创建商品", "sort": null, "type": 3, "parentId": 16, "permission": "system:goods:create" }, { "id": 35, "url": null, "name": "删除商品", "sort": null, "type": 3, "parentId": 16, "permission": "system:goods:delete" }, { "id": 36, "url": null, "name": "修改商品", "sort": null, "type": 3, "parentId": 16, "permission": "system:goods:update" }, { "id": 37, "url": null, "name": "查询商品", "sort": null, "type": 3, "parentId": 16, "permission": "system:goods:query" }], "parentId": 9 }] }, { "id": 41, "name": "随便聊聊", "type": 1, "url": "/main/story", "icon": "el-icon-chat-line-round", "sort": 4, "children": [{ "id": 42, "url": "/main/story/chat", "name": "你的故事", "sort": 108, "type": 2, "children": null, "parentId": 41 }, { "id": 43, "url": "/main/story/list", "name": "故事列表", "sort": 109, "type": 2, "children": [], "parentId": 41 }] }]
(3). 封装方法 mapMenusToRoutes,递归获取具有用户权限的路由信息。
/** * 获取用户权限的路由对象 * @param userMenus 菜单数据 * @returns 用户权限的路由对象 */ export function mapMenusToRoutes(userMenus: RouteRecordRaw[]) { // 具有权限的routes const routes: RouteRecordRaw[] = []; // 1.先去加载本地默认所有的routes const allRoutes: RouteRecordRaw[] = []; // 1.1 获取所有的路由文件,【 require.context,是webpack的函数(true表示递归)】 const routeFiles = require.context('../router/main', true, /\.ts/); // 1.2 获取路由对象 // 默认获取的是当前main目录下,./analysis/dashboard/dashboard.ts, 所以要拼接切割一下(因为该文件存放在utils文件夹下) routeFiles.keys().forEach((key) => { // require获取的是一个ESModule对象 const route = require('../router/main' + key.split('.')[1]); // xx.default获取的是的路由对象 allRoutes.push(route.default); }); // 2. 根据菜单获取需要添加的routes(需要递归!! 支持多级菜单) // type === 1 -> children -> type === 1 (表示有子菜单) // type === 2 -> url -> route const _recurseGetRoute = (menus: any[]) => { for (const menu of menus) { if (menu.type === 2) { const route = allRoutes.find((item) => item.path === menu.url); if (route) { routes.push(route); } } else { _recurseGetRoute(menu.children); } } }; // 调用 _recurseGetRoute(userMenus); return routes; }
(4). 动态注册路由
// 2. 注册动态路由,添加映射关系,用户后面的点击跳转 const routes = mapMenusToRoutes(userMenus); // 添加到main下作为二级路由 routes.forEach((itemRoute) => { // main是一级路由的name值 router.addRoute('main', itemRoute); });
二. 菜单搭建
1. 封装nav-menu菜单组件
(1). 剖析
A. 这里仅仅封装两级菜单,即:①一级菜单直接跳转 ②一级菜单展开后,二级菜单可以跳转。
B. 最外层是<el-menu>,一级菜单直接跳转是<el-menu-item>标签;一级菜单展开后,二级可以跳转是<el-sub-menu>包裹<el-menu-item>
C. default-active属性表示默认选中,对应的值是标签上的index属性
D. collapse 控制展开和折叠
(2). 核心代码
<template> <div class="nav-menu"> <div class="logo"> <img class="img" src="~@/assets/img/logo.svg" alt="logo" /> <span v-show="!collapse" class="title">Vue3+TS</span> </div> <!-- <el-menu default-active="1-1" class="el-menu-vertical-demo"> <el-sub-menu index="1"> <template #title> <el-icon><location /></el-icon> <span>Navigator One</span> </template> <el-menu-item index="1-1">item one</el-menu-item> <el-menu-item index="1-2">item two</el-menu-item> </el-sub-menu> <el-sub-menu index="2"> <template #title> <el-icon><location /></el-icon> <span>Navigator Two</span> </template> <el-menu-item index="2-1">item Three</el-menu-item> <el-menu-item index="2-2">item Four</el-menu-item> </el-sub-menu> <el-menu-item index="3"> <el-icon><location /></el-icon> <span>Navigator Three</span> </el-menu-item> <el-menu-item index="4"> <el-icon><location /></el-icon> <span>Navigator Four</span> </el-menu-item> </el-menu> --> <el-menu class="el-menu-vertical" background-color="#0c2135" text-color="#b7bdc3" active-text-color="#0a60bd" unique-opened :default-active="defaultValue + ''" :collapse="collapse" > <template v-for="item in userMenus" :key="item.id"> <!-- type=1,表示含 二级菜单 --> <template v-if="item.type === 1"> <!-- 二级菜单的可以展开的标题 --> <el-sub-menu :index="item.id + ''"> <template #title> <el-icon :size="15"><location /></el-icon> <span>{{ item.name }}</span> </template> <!-- 遍历里面的item --> <template v-for="subitem in item.children" :key="subitem.id"> <el-menu-item :index="subitem.id + ''" @click="HandleMenuItemClick(subitem)"> <el-icon :size="15"><setting /></el-icon> <span>{{ subitem.name }}</span> </el-menu-item> </template> </el-sub-menu> </template> <!-- type=2 表示不含二级菜单,存放在最外层直接点击 --> <template v-else-if="item.type === 2"> <el-menu-item :index="item.id + ''" @click="HandleMenuItemClick(item)"> <el-icon :size="15"><location /></el-icon> <span>{{ item.name }}</span> </el-menu-item> </template> </template> </el-menu> </div> </template> <script lang="ts"> import { defineComponent, computed, ref } from 'vue'; import { useStore } from '@/store'; import { Location, Setting } from '@element-plus/icons'; import { useRouter, useRoute } from 'vue-router'; import { pathMapToMenu } from '@/utils/map-menus'; export default defineComponent({ components: { Location, Setting, }, props: { collapse: { type: Boolean, default: false, }, }, setup() { // 这里的useStore是自己改造的,加上了返回值的类型声明,便于后面state.能点出来 const store = useStore(); // 获取vuex中的菜单信息,放到computed中,支持响应式 const userMenus = computed(() => store.state.login.userMenus); // console.log(userMenus.value); const router = useRouter(); const route = useRoute(); const currentPath = route.path; console.log('---------currentPath-----------------', currentPath); // 根据路由的path去userMenus集合中找到对应的menu const menu = pathMapToMenu(userMenus.value, currentPath); const defaultValue = ref(menu?.id ?? 15); // 菜单点击跳转事件 const HandleMenuItemClick = (item: any) => { // console.log(item.url); router.push({ path: item.url ?? '/not-found', }); }; return { userMenus, HandleMenuItemClick, defaultValue, }; }, }); </script>
2. 如何实现点击变色和打开页面?
点击某个菜单,菜单选中后变色,是menu组件默认实现的功能。
打开页面:需要给每个可点击的菜单绑定一个事件,事件中 push 跳转到对应的url处。
3.首次进入默认选中和打开页面如何匹配?
首先登录成功后,肯定会有一个push,比如push("/system/user"),
那么default-active属性就需要设置成 “/system/user”标签上的index值。
4. 刷新页面后仍然记住当前页面的两套方案
方案一:(推荐!)
index属性里存放的是跳转的路径(如:/system/user),每次点击的时候,获取点击的url,先给deault-active属性赋值,然后再存放到缓存里。页面加载的时候先从缓存中获取值,赋给deault-active,如果没有的话,再赋一个默认值。
代码参考:
<template> <el-container class="home-container"> <!-- 头部区域 --> <el-header> <div> <img src="../assets/heima.png" alt=""> <span>后台管理系统</span> </div> <el-button type="info" @click="logout" size="small">退出</el-button> </el-header> <!-- 页面主体区域 --> <el-container> <!-- 侧边栏 --> <el-aside :width="isCollapse ? '64px' : '200px'"> <div class="toggle-button" @click="toggleCollapse">|||</div> <!-- 侧边栏菜单区域 :default-openeds="['103']"表示默认登录展开项,103对应权限管理的index值 有bug--> <!-- :default-active="activePath" 用来处理点击后记录选中的子菜单项 --> <el-menu background-color="#333744" text-color="#fff" active-text-color="#409EFF" :collapse="isCollapse" :collapse-transition="false" :default-active="activePath" router unique-opened> <!-- 一级菜单 --> <el-submenu :index="item.id + ''" v-for="item in menulist" :key="item.id"> <!-- 一级菜单的模板区域 --> <template slot="title"> <!-- 图标 --> <i :class="iconsObj[item.id]"></i> <!-- 文本 --> <span>{{item.authName}}</span> </template> <!-- 二级菜单 --> <el-menu-item :index="'/' + subItem.path" v-for="subItem in item.children" :key="subItem.id" @click="saveNavState('/' + subItem.path)"> <template slot="title"> <!-- 图标 --> <i class="el-icon-menu"></i> <!-- 文本 --> <span>{{subItem.authName}}</span> </template> </el-menu-item> </el-submenu> </el-menu> </el-aside> <!-- 右侧内容主体 --> <el-main> <!-- 路由占位符 --> <router-view></router-view> </el-main> </el-container> </el-container> </template> <script> export default { data() { return { // 左侧菜单数据 menulist: [], // 图标 iconsObj: { 125: 'iconfont icon-user', 103: 'iconfont icon-tijikongjian', 101: 'iconfont icon-shangpin', 102: 'iconfont icon-danju', 145: 'iconfont icon-baobiao' }, // 是否折叠 isCollapse: false, // 保存当前选中状态 activePath: '', } }, // 实例创建之后执行 created() { // 1.获取左侧所有菜单 this.getMenuList() this.activePath = window.sessionStorage.getItem('activePath') }, methods: { // 1.退出 logout() { window.sessionStorage.clear() this.$router.push('/login') }, // 2.获取左侧所有菜单 async getMenuList() { const { data: res } = await this.$http.get('menus') if (res.meta.status !== 200) return this.$message.error(res.meta.msg) this.menulist = res.data console.log(res) }, // 3. 切换左侧菜单的折叠与展开 toggleCollapse() { this.isCollapse = !this.isCollapse }, // 4.保存当前页面的选中状态 // (比如刷新了一下浏览器,还是进入到刷新前的页面,而不是默认打开页面) saveNavState(activePath) { window.sessionStorage.setItem('activePath', activePath) this.activePath = activePath } } } </script> <style lang="less" scoped> .home-container { height: 100%; } .el-header { background-color: #373d41; display: flex; justify-content: space-between; padding-left: 0; align-items: center; color: #fff; font-size: 20px; >div { display: flex; align-items: center; span { margin-left: 15px; } } } .el-aside { background-color: #333744; .el-menu { border-right: none; } } .el-main { background-color: #eaedf1; } .iconfont { margin-right: 10px; } .toggle-button { background-color: #4a5064; font-size: 10px; line-height: 24px; color: #fff; text-align: center; letter-spacing: 0.2em; cursor: pointer; } </style>
方案二:
利用route.path可以获取上一次点击的路由跳转路径,结合上述方案一,就可以不用缓存了。【推荐】
下面分享的是通过id来匹配,拿到path后,根据path去菜单中找到对应的menuItem,然后找到id,赋值给default-active.
代码参考详见上述1
pathMapToMenu方法如下:
/** *根据path递归找对应的menu * @param userMenus 所有菜单 * @param currentPath 当前path * @returns 对应menu */ export function pathMapToMenu(userMenus: any[], currentPath: string): any { for (const menu of userMenus) { if (menu.type === 1) { const findMenu = pathMapToMenu(menu.children ?? [], currentPath); if (findMenu) { return findMenu; } } else if (menu.type === 2 && menu.url === currentPath) { return menu; } } }
三. 菜单-面包屑
1. 封装Ypf-breadcrumb
使用el-breadcrumb组件封装即可。
核心代码如下
<template> <!-- <el-breadcrumb :separator-icon="ArrowRight"> <el-breadcrumb-item :to="{ path: '/' }">商品中心</el-breadcrumb-item> <el-breadcrumb-item :to="{ path: '/' }">商品类别</el-breadcrumb-item> </el-breadcrumb> --> <el-breadcrumb :separator-icon="ArrowRight"> <template v-for="item in breadcrumbs" :key="item.name"> <!-- <el-breadcrumb-item :to="{ path: item.path }">{{ item.name }}</el-breadcrumb-item> --> <el-breadcrumb-item>{{ item.name }}</el-breadcrumb-item> </template> </el-breadcrumb> </template> <script lang="ts"> import { defineComponent, PropType } from 'vue'; import { IBreadcrumb } from '../types'; import { ArrowRight } from '@element-plus/icons'; export default defineComponent({ components: { ArrowRight, }, props: { breadcrumbs: { type: Array as PropType<IBreadcrumb[]>, default: () => [], }, }, // 下面这种数组写法,不存在类型验证,同样可以使用 // props: ['breadcrumbs'], setup() { return { ArrowRight, }; }, }); </script>
2. 如何获取对应的面包屑数据?【重点】
(ps: 上述面包屑数据的封装很容易,关键是如何传入对应的值)
A 先从vuex中获取useMenus菜单数据
B 然后从route中获取当前的path (灵魂的地方,很关键)
C 然后再封装个方法,根据userMenus和当前path获取面包屑需要的数据。
核心代码
//2. 面包屑相关数据[{name: , path: }] const store = useStore(); const myBreadcrumbs = computed(() => { // 2.1 从vuex中获取当前菜单数据 const userMenus = store.state.login.userMenus; const route = useRoute(); // 2.2 获取当前path,然后根据当前path获取需要面包屑数组 const currentPath = route.path; return pathMapBreadcrumbs(userMenus, currentPath); });
封装方法
/** *根据path递归找对应的menu * @param userMenus 所有菜单 * @param currentPath 当前path * @returns 对应menu */ export function pathMapToMenu(userMenus: any[], currentPath: string): any { for (const menu of userMenus) { if (menu.type === 1) { const findMenu = pathMapToMenu(menu.children ?? [], currentPath); if (findMenu) { return findMenu; } } else if (menu.type === 2 && menu.url === currentPath) { return menu; } } } /** * 根据当前的path去用户menus中获取需要的面包屑数组 * @param userMenus 用户菜单数据 * @param currentPath 当前路径 * @returns 返回格式:[{name: , path: },{name: , path: }] */ export function pathMapBreadcrumbs(userMenus: any[], currentPath: string) { const breadcrumbs: IBreadcrumb[] = []; for (const menu of userMenus) { if (menu.type === 1) { const findMenu = pathMapToMenu(menu.children ?? [], currentPath); if (findMenu) { breadcrumbs.push({ name: menu.name, path: menu.url }); breadcrumbs.push({ name: findMenu.name, path: findMenu.url }); } } else if (menu.type === 2 && menu.url === currentPath) { return menu; } } return breadcrumbs; }
四. 按钮权限
1. 核心思路
(1) .从接口中拿到所有按钮权限,存放到vuex里state属性中的一个数组里。 【这里需要约定一个规则 ,如:system:users:create,system:users:delete,最后一项对应按钮的名称】
(2). 封装一个方法,判断页面上的某个按钮是否在这个数组里,返回一个标记,为 :false or true。
(3). 每个按钮都添加v-if (或者 v-show)属性,通过上面的flag来判断是否显示。
2. 实现步骤
(1). 请求接口,拿到数据 → 封装一个方法mapMenusToPermissions,处理数据,返回按钮权限数组 → 存放到vuex里
接口公用菜单权限接口,数据如下:
[{ "id": 38, "name": "系统总览", "type": 1, "url": "/main/analysis", "icon": "el-icon-monitor", "sort": 1, "children": [{ "id": 39, "url": "/main/analysis/overview", "name": "核心技术", "sort": 106, "type": 2, "children": null, "parentId": 38 }, { "id": 40, "url": "/main/analysis/dashboard", "name": "商品统计", "sort": 107, "type": 2, "children": null, "parentId": 38 }] }, { "id": 1, "name": "系统管理", "type": 1, "url": "/main/system", "icon": "el-icon-setting", "sort": 2, "children": [{ "id": 2, "url": "/main/system/user", "name": "用户管理", "sort": 100, "type": 2, "children": [{ "id": 5, "url": null, "name": "创建用户", "sort": null, "type": 3, "parentId": 2, "permission": "system:users:create" }, { "id": 6, "url": null, "name": "删除用户", "sort": null, "type": 3, "parentId": 2, "permission": "system:users:delete" }, { "id": 7, "url": null, "name": "修改用户", "sort": null, "type": 3, "parentId": 2, "permission": "system:users:update" }, { "id": 8, "url": null, "name": "查询用户", "sort": null, "type": 3, "parentId": 2, "permission": "system:users:query" }], "parentId": 1 }, { "id": 3, "url": "/main/system/department", "name": "部门管理", "sort": 101, "type": 2, "children": [{ "id": 17, "url": null, "name": "创建部门", "sort": null, "type": 3, "parentId": 3, "permission": "system:department:create" }, { "id": 18, "url": null, "name": "删除部门", "sort": null, "type": 3, "parentId": 3, "permission": "system:department:delete" }, { "id": 19, "url": null, "name": "修改部门", "sort": null, "type": 3, "parentId": 3, "permission": "system:department:update" }, { "id": 20, "url": null, "name": "查询部门", "sort": null, "type": 3, "parentId": 3, "permission": "system:department:query" }], "parentId": 1 }, { "id": 4, "url": "/main/system/menu", "name": "菜单管理", "sort": 103, "type": 2, "children": [{ "id": 21, "url": null, "name": "创建菜单", "sort": null, "type": 3, "parentId": 4, "permission": "system:menu:create" }, { "id": 22, "url": null, "name": "删除菜单", "sort": null, "type": 3, "parentId": 4, "permission": "system:menu:delete" }, { "id": 23, "url": null, "name": "修改菜单", "sort": null, "type": 3, "parentId": 4, "permission": "system:menu:update" }, { "id": 24, "url": null, "name": "查询菜单", "sort": null, "type": 3, "parentId": 4, "permission": "system:menu:query" }], "parentId": 1 }, { "id": 25, "url": "/main/system/role", "name": "角色管理", "sort": 102, "type": 2, "children": [{ "id": 26, "url": null, "name": "创建角色", "sort": null, "type": 3, "parentId": 25, "permission": "system:role:create" }, { "id": 27, "url": null, "name": "删除角色", "sort": null, "type": 3, "parentId": 25, "permission": "system:role:delete" }, { "id": 28, "url": null, "name": "修改角色", "sort": null, "type": 3, "parentId": 25, "permission": "system:role:update" }, { "id": 29, "url": null, "name": "查询角色", "sort": null, "type": 3, "parentId": 25, "permission": "system:role:query" }], "parentId": 1 }] }, { "id": 9, "name": "商品中心", "type": 1, "url": "/main/product", "icon": "el-icon-goods", "sort": 3, "children": [{ "id": 15, "url": "/main/product/category", "name": "商品类别", "sort": 104, "type": 2, "children": [{ "id": 30, "url": null, "name": "创建类别", "sort": null, "type": 3, "parentId": 15, "permission": "system:category:create" }, { "id": 31, "url": null, "name": "删除类别", "sort": null, "type": 3, "parentId": 15, "permission": "system:category:delete" }, { "id": 32, "url": null, "name": "修改类别", "sort": null, "type": 3, "parentId": 15, "permission": "system:category:update" }, { "id": 33, "url": null, "name": "查询类别", "sort": null, "type": 3, "parentId": 15, "permission": "system:category:query" }], "parentId": 9 }, { "id": 16, "url": "/main/product/goods", "name": "商品信息", "sort": 105, "type": 2, "children": [{ "id": 34, "url": null, "name": "创建商品", "sort": null, "type": 3, "parentId": 16, "permission": "system:goods:create" }, { "id": 35, "url": null, "name": "删除商品", "sort": null, "type": 3, "parentId": 16, "permission": "system:goods:delete" }, { "id": 36, "url": null, "name": "修改商品", "sort": null, "type": 3, "parentId": 16, "permission": "system:goods:update" }, { "id": 37, "url": null, "name": "查询商品", "sort": null, "type": 3, "parentId": 16, "permission": "system:goods:query" }], "parentId": 9 }] }, { "id": 41, "name": "随便聊聊", "type": 1, "url": "/main/story", "icon": "el-icon-chat-line-round", "sort": 4, "children": [{ "id": 42, "url": "/main/story/chat", "name": "你的故事", "sort": 108, "type": 2, "children": null, "parentId": 41 }, { "id": 43, "url": "/main/story/list", "name": "故事列表", "sort": 109, "type": 2, "children": [], "parentId": 41 }] }]
mapMenusToPermissions递归方法如下:
// 获取按钮权限 // type=3,表示是按钮权限,字段为permission,内容的格式如:"system:users:create" // type=1,2 表示还有下一级别菜单 export function mapMenusToPermissions(userMenus: any[]) { const permissonList: string[] = []; // 递归函数 const _getPermissions = (menus: any[]) => { for (const item of menus) { if (item.type === 2 || item.type === 1) { _getPermissions(item.children ?? []); } else if (item.type === 3) { permissonList.push(item.permission); } } }; // 调用 _getPermissions(userMenus); return permissonList; }
(2). 封装方法 usePermission,利用find来判断是否存在该权限。
import { useStore } from '@/store'; /** * 判断是否有这个按钮权限 * @param pageName 页面名称 * @param handleName 按钮名称 * @returns true or false */ export function usePermission(pageName: string, handleName: string) { const store = useStore(); const permissonList = store.state.login.permissions; const myPer = `system:${pageName}:${handleName}`; // !name=>false // !!name=>true return !!permissonList.find((item: any) => item === myPer); }
(3). 调用usePermission方法,获取结果,然后通过v-if给按钮 或者 查询 赋予权限。
const isCreate = usePermission(props.pageName as string, 'create'); const isUpdate = usePermission(props.pageName as string, 'update'); const isDelete = usePermission(props.pageName as string, 'delete'); const isQuery = usePermission(props.pageName as string, 'query');
页面代码:
<div class="handle-btns"> <el-button v-if="isUpdate" :icon="Edit" size="mini" type="text" @click="editHandle(myScope.row1)"> 编辑 </el-button> <el-button v-if="isDelete" :icon="Delete" size="mini" type="text" @click="deleteHandle(myScope.row1)"> 删除 </el-button> </div>
查询方法代码:
const getPageData = (queryInfo: any = {}) => { if (!isQuery) return; store.dispatch('system/getPageListAction', { pageName: props.pageName, queryInfo: { offset: (pageInfo.value.currentPage - 1) * pageInfo.value.pageSize, size: pageInfo.value.pageSize, ...queryInfo, }, }); };
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。