第四節:菜單權限、動態路由注冊、菜單搭建、面包屑、按鈕權限 剖析


一. 菜單權限-動態路由注冊

 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 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM