基于Vue實(shí)現(xiàn)簡單的權(quán)限控制
Vue+菜單權(quán)限+動(dòng)態(tài)路由
實(shí)現(xiàn)原理:用戶登錄,服務(wù)端返回相關(guān)權(quán)限,進(jìn)行持久化存儲(chǔ),篩選動(dòng)態(tài)路由,同時(shí)菜單欄也需動(dòng)態(tài)渲染
靜態(tài)路由
靜態(tài)路由,也叫常量路由,即所有角色都可以訪問到的路由界面。如: login
、 404
等
const constantRoute = [ { //登錄 path: '/login', component: () => import('@/views/login/index.vue'), name: 'login', meta: { title: '登錄', hidden: true, icon: 'Promotion', }, }, { //登錄成功以后的布局路由 path: '/', component: () => import('@/layout/layout.vue'), name: 'layout', meta: { title: '', hidden: false, icon: '', }, redirect: '/home', children: [ { path: '/home', name: 'home', component: () => import('@/views/home/index.vue'), meta: { title: '首頁', hidden: false, icon: 'House', }, }, ], }, { //404 path: '/404', component: () => import('@/views/404/index.vue'), name: '404', meta: { title: '404', hidden: true, icon: 'DocumentDelete', }, }, ]
對應(yīng)的菜單權(quán)限如圖:
動(dòng)態(tài)路由
即不同角色所擁有的權(quán)限路由
,一般登錄成功后,向后端發(fā)送請求,由服務(wù)器返回對應(yīng)的權(quán)限,然后進(jìn)行篩選過濾。
//返回的用戶信息 [ { "userId": 1, "avatar": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif", "username": "admin", "password": "111111", "desc": "平臺(tái)管理員", "roles": ["平臺(tái)管理員"], "buttons": ["cuser.detail"], "routes": [ "Home", "User", "Role", "Permission", "Trademark", "Product", "Acl" ], "token": "Admin Token" }, ] //所有的權(quán)限路由 const asyncRoute = [ { path: '/acl', component: () => import('@/layout/index.vue'), name: 'Acl', meta: { title: '權(quán)限管理', icon: 'Lock', }, redirect: '/acl/user', children: [ { path: '/acl/user', component: () => import('@/views/acl/user/index.vue'), name: 'User', meta: { title: '用戶管理', icon: 'User', }, }, { path: '/acl/role', component: () => import('@/views/acl/role/index.vue'), name: 'Role', meta: { title: '角色管理', icon: 'UserFilled', }, }, { path: '/acl/permission', component: () => import('@/views/acl/permission/index.vue'), name: 'Permission', meta: { title: '菜單管理', icon: 'Monitor', }, }, ], }, { path: '/product', component: () => import('@/layout/index.vue'), name: 'Product', meta: { title: '商品管理', icon: 'Goods', }, redirect: '/product/trademark', children: [ { path: '/product/trademark', component: () => import('@/views/product/trademark/index.vue'), name: 'Trademark', meta: { title: '品牌管理', icon: 'ShoppingCartFull', }, }, { path: '/product/attr', component: () => import('@/views/product/attr/index.vue'), name: 'Attr', meta: { title: '屬性管理', icon: 'ChromeFilled', }, }, { path: '/product/spu', component: () => import('@/views/product/spu/index.vue'), name: 'Spu', meta: { title: 'SPU管理', icon: 'Calendar', }, }, { path: '/product/sku', component: () => import('@/views/product/sku/index.vue'), name: 'Sku', meta: { title: 'SKU管理', icon: 'Orange', }, }, ], }, ]
菜單權(quán)限
本次demo演示使用的是element-plus的el-menu組件。
在較為簡單的開發(fā)中,菜單我們經(jīng)常寫死,這也就導(dǎo)致了不同的角色所看到的菜單列表是一致的。
所以,一般實(shí)現(xiàn)動(dòng)態(tài)路由,也要二次封裝一個(gè)對應(yīng)的菜單權(quán)限組件
。
實(shí)現(xiàn)步驟
通過pinia或者vuex全局狀態(tài)管理工具,定義一個(gè)全局狀態(tài) menuRoutes
,初始值為對應(yīng)的靜態(tài)路由數(shù)組
二次封裝menu組件,通過 menuRoutes
,遞歸渲染
展示不同的菜單欄
重點(diǎn):需要使用到vue3的遞歸組件,因此需要定義組件名。同時(shí) menuRoutes
需要以父傳子的方式傳遞
<template> <div> <template v-for="(item, index) in props.menuList" :key="item.path"> <!-- 沒有子路由 --> <template v-if="!item.children"> <el-menu-item :index="item.path" v-if="!item.meta.hidden" @click="goRoute" > <template #title> <el-icon> <component :is="item.meta.icon" /> </el-icon> <span>{{ item.meta.title }}</span> </template> </el-menu-item> </template> <!-- 只有一個(gè)子路由 (例如home頁,它是layout的子路由,但是只有一個(gè),直接渲染home) --> <el-menu-item v-if="item.children && item.children.length == 1" :index="item.children[0].path" @click="goRoute" > <template #title> <el-icon> <component :is="item.children[0].meta.icon" /> </el-icon> <span>{{ item.children[0].meta.title }}</span> </template> </el-menu-item> <!-- 有多個(gè)子路由 --> <el-sub-menu :index="item.path" v-if="item.children && item.children.length > 1" > <template #title> <el-icon> <component :is="item.meta.icon"></component> </el-icon> <span>{{ item.meta.title }}</span> </template> <!-- 子路由遞歸動(dòng)態(tài)渲染 --> <Menu :menuList="item.children"></Menu> </el-sub-menu> </template> </div> </template> <script setup lang="ts"> import { ref, reactive, computed, onMounted, watch } from 'vue' import { useRouter } from 'vue-router' const $router = useRouter() //獲取父組件傳遞的路由數(shù)組 interface Iprops { menuList: any[] } const props = withDefaults(defineProps<Iprops>(), { menuList: () => [], }) const goRoute = (vc: any) => { $router.push(vc.index) } </script> <script lang="ts"> export default { name: 'Menu', } </script>
登錄成功后,獲取用戶信息,從而獲取對應(yīng)的權(quán)限列表數(shù)據(jù),傳入所有之前定義好的權(quán)限路由
,進(jìn)行過濾。最后通過addRoute
方法追加動(dòng)態(tài)路由。
import { constantRoute, asyncRoute, anyRoute } from '@/router/routes' //getUserInfo const res = await getUserInfo() let routes = this.filterAsyncRoute( _.cloneDeep(asyncRoute), res.data.checkUser.routes, ) //修改菜單欄顯示 this.menuRoutes = [...constantRoute, ...routes, anyRoute] //通過addRoute追加動(dòng)態(tài)路由 let activeRoutes = [...routes, anyRoute] activeRoutes.forEach((route) => { router.addRoute(route) }) //過濾權(quán)限路由 filterAsyncRoute(asyncRoute: RouteRecordRaw[], routes: RouteRecordName[]) { let result: RouteRecordRaw[] = [] asyncRoute.forEach((item) => { if (routes.includes(item.name!)) { result.push(item) if (item.children) { item.children = this.filterAsyncRoute(item.children, routes) } } }) return result }, },
注意點(diǎn):
1、每次過濾權(quán)限路由的時(shí)候,必須深拷貝一份asyncRoute,懂的都懂(引用類型數(shù)據(jù)是地址)
? 2、pinia中的數(shù)據(jù)是非持久性緩存的,所以一刷新數(shù)據(jù)就會(huì)丟失。解決方案:使用pinia的持久性插件或者路由鑒權(quán)的同時(shí),在路由前置導(dǎo)航守衛(wèi),每次跳轉(zhuǎn)的時(shí)候,判斷pinia中是否存儲(chǔ)了用戶信息,如果沒有,重新調(diào)用getUserInfo方法,獲取用戶信息
? 3、是基于第二點(diǎn),在組件外部通過同步語句獲取倉庫,是獲取不到的,必須通過如下方式獲取
import pinia from '@/store/index' let userStore = useUserStore(pinia)
? 4、至此,我們成功實(shí)現(xiàn)了菜單權(quán)限+動(dòng)態(tài)路由,但還有個(gè)bug
BUG:如果我們在動(dòng)態(tài)路由頁面進(jìn)行刷新,會(huì)導(dǎo)致白屏
原因:刷新頁面的時(shí)候,觸發(fā)了路由前置導(dǎo)航守衛(wèi),獲取用戶信息,如果獲取到了,就放行。但是放行的時(shí)候,動(dòng)態(tài)路由還沒有加載完成! 得確保獲取完用戶信息且全部路由組件渲染完畢
解決辦法:next({...to})
意義:死循環(huán)加載,直至路由組件加載完畢
以上就是基于Vue實(shí)現(xiàn)簡單的權(quán)限控制的詳細(xì)內(nèi)容,更多關(guān)于Vue權(quán)限控制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
unplugin-auto-import與unplugin-vue-components安裝問題解析
這篇文章主要為大家介紹了unplugin-auto-import與unplugin-vue-components問題解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02vue如何轉(zhuǎn)換時(shí)間格式為年月日時(shí)分秒和年月日(補(bǔ)零)
這篇文章主要介紹了vue如何轉(zhuǎn)換時(shí)間格式為年月日時(shí)分秒和年月日(補(bǔ)零),具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04vue中實(shí)現(xiàn)先請求數(shù)據(jù)再渲染dom分享
下面小編就為大家分享一篇vue中實(shí)現(xiàn)先請求數(shù)據(jù)再渲染dom分享,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03Vue.js 實(shí)現(xiàn)tab切換并變色操作講解
這篇文章主要介紹了Vue.js 實(shí)現(xiàn)tab切換并變色操作講解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09Vue Router動(dòng)態(tài)路由使用方法總結(jié)
這篇文章主要介紹了Vue Router動(dòng)態(tài)路由使用方法總結(jié),需要的朋友可以參考下2023-10-10vue響應(yīng)式更新機(jī)制及不使用框架實(shí)現(xiàn)簡單的數(shù)據(jù)雙向綁定問題
vue是一款具有響應(yīng)式更新機(jī)制的框架,既可以實(shí)現(xiàn)單向數(shù)據(jù)流也可以實(shí)現(xiàn)數(shù)據(jù)的雙向綁定。這篇文章主要介紹了vue響應(yīng)式更新機(jī)制及不使用框架實(shí)現(xiàn)簡單的數(shù)據(jù)雙向綁定問題,需要的朋友可以參考下2019-06-06