第四节:菜单权限、动态路由注册、菜单搭建、面包屑、按钮权限 剖析

一. 菜单权限-动态路由注册

 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,
});
View Code

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
    }]
}]
View Code

(3). 封装方法 mapMenusToRoutes,递归获取具有用户权限的路由信息。

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;
}
View Code

(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>
View Code

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>
View Code

方案二:

  利用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>
View Code

 

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;
}
View Code

 

 

四. 按钮权限

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
    }]
}]
View Code

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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

 

 

posted @ 2021-12-04 09:14  Yaopengfei  阅读(1102)  评论(1编辑  收藏  举报