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