基于Vue實(shí)現(xiàn)簡單的權(quán)限控制
Vue+菜單權(quán)限+動態(tài)路由
實(shí)現(xiàn)原理:用戶登錄,服務(wù)端返回相關(guān)權(quán)限,進(jìn)行持久化存儲,篩選動態(tài)路由,同時(shí)菜單欄也需動態(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)限如圖:

動態(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": "平臺管理員",
"roles": ["平臺管理員"],
"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)動態(tài)路由,也要二次封裝一個對應(yīng)的菜單權(quán)限組件。
實(shí)現(xiàn)步驟
通過pinia或者vuex全局狀態(tài)管理工具,定義一個全局狀態(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>
<!-- 只有一個子路由 (例如home頁,它是layout的子路由,但是只有一個,直接渲染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>
<!-- 有多個子路由 -->
<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>
<!-- 子路由遞歸動態(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方法追加動態(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追加動態(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ù)就會丟失。解決方案:使用pinia的持久性插件或者路由鑒權(quán)的同時(shí),在路由前置導(dǎo)航守衛(wèi),每次跳轉(zhuǎn)的時(shí)候,判斷pinia中是否存儲了用戶信息,如果沒有,重新調(diào)用getUserInfo方法,獲取用戶信息
? 3、是基于第二點(diǎn),在組件外部通過同步語句獲取倉庫,是獲取不到的,必須通過如下方式獲取
import pinia from '@/store/index' let userStore = useUserStore(pinia)
? 4、至此,我們成功實(shí)現(xiàn)了菜單權(quán)限+動態(tài)路由,但還有個bug
BUG:如果我們在動態(tài)路由頁面進(jìn)行刷新,會導(dǎo)致白屏
原因:刷新頁面的時(shí)候,觸發(fā)了路由前置導(dǎo)航守衛(wèi),獲取用戶信息,如果獲取到了,就放行。但是放行的時(shí)候,動態(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-02
vue如何轉(zhuǎn)換時(shí)間格式為年月日時(shí)分秒和年月日(補(bǔ)零)
這篇文章主要介紹了vue如何轉(zhuǎn)換時(shí)間格式為年月日時(shí)分秒和年月日(補(bǔ)零),具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04
vue中實(shí)現(xiàn)先請求數(shù)據(jù)再渲染dom分享
下面小編就為大家分享一篇vue中實(shí)現(xiàn)先請求數(shù)據(jù)再渲染dom分享,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03
Vue.js 實(shí)現(xiàn)tab切換并變色操作講解
這篇文章主要介紹了Vue.js 實(shí)現(xiàn)tab切換并變色操作講解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
Vue Router動態(tài)路由使用方法總結(jié)
這篇文章主要介紹了Vue Router動態(tài)路由使用方法總結(jié),需要的朋友可以參考下2023-10-10
vue響應(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

