前端配合后端實現(xiàn)Vue路由權限的方法實例
前言
在開發(fā)管理后臺時,都會存在多個角色登錄,登錄成功后,不同的角色會展示不同的菜單路由。這就是我們通常所說的動態(tài)路由權限,實現(xiàn)路由權限的方案有多種,比較常用的是由前端使用addRoutes(V3版本改成了addRoute)動態(tài)掛載路由和服務端返回可訪問的路由菜單這兩種。上一篇文章講了純前端實現(xiàn)路由權限,沒看過的可以點擊文章鏈接純前端實現(xiàn)Vue路由權限。今天主要是基于后端返回路由菜單的基礎上,實現(xiàn)路由權限功能。
實現(xiàn)思路
后端返回路由菜單主要是在我們登錄之后,后端接口會直接返回當前用戶可訪問的完整路由菜單,相當于前端基于RBAC模型篩選出了前端可訪問的路由列表。
需要注意的是,后端返回的路由菜單是不包括login、404等頁面的。前端這邊還是需要寫一份完整的路由列表,基于后端返回的可訪問路由菜單去篩選出需要掛載在router上的路由列表。
代碼實現(xiàn)
登錄
首先是登錄,登錄成功后,服務端會返回登錄用戶可訪問的路由菜單userMenus,我們一般會將這些信息保存到Vuex里。
登錄方法:
const login = () => {
ruleFormRef.value?.validate((valid: boolean) => {
if (valid) {
store.dispatch('userModule/login', { ...accountForm })
} else {
console.log('error submit!')
}
})
}Vuex對應異步操作:
async login({ commit }, payload: IRequest) {
// 登錄獲取token
const { data } = await accountLogin(payload)
commit('SET_TOKEN', data.token)
localCache.setCache('token', data.token)
// 獲取用戶信息
const userInfo = await getUserInfo(data.id)
commit('SET_USERINFO', userInfo.data)
localCache.setCache('userInfo', userInfo.data)
// 獲取菜單
const userMenu = await getUserMenu(userInfo.data.role.id)
commit('SET_USERMENU', userMenu.data)
localCache.setCache('userMenu', userMenu.data)
router.replace('/main/analysis/dashboard')
},接口返回的路由菜單信息:

路由菜單
可以看到,返回的userMenus是一個數(shù)組,包含了圖標icon、路由名稱name、路由地址、子路由children、路由type等重要信息。前面這些信息主要是用于遍歷生成頁面左側的菜單列表,路由type則是用于后面篩選出需要掛載在router上的路由列表。
本地路由列表
前端這邊還是需要寫一份完整的路由列表,我這里打算在router/index.ts里面寫入接口不返回的菜單,如login、404等頁面。將接口可能返回的菜單單獨放在router/main下面。
router/index.ts:
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/main'
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue'),
meta: {
title: '登錄'
}
},
{
path: '/main',
name: 'main',
redirect: '/main/analysis/dashboard',
component: () => import('@/views/main/index.vue'),
meta: {
title: '核心技術'
}
},
{
path: '/:pathMatch(.*)*',
name: 'notFound',
component: () => import('@/views/404.vue'),
meta: {
title: '頁面找不到~'
}
}
]router/main下面就是寫入所有菜單列表:
單個菜單內(nèi)容,如dashboard.ts:
const dashboard = () => import('@/views/main/analysis/dashboard/dashboard.vue')
export default {
path: '/main/analysis/dashboard',
name: 'dashboard',
component: dashboard,
meta: {
title: '商品統(tǒng)計'
},
children: []
}整個router目錄:

router目錄
接下來,我們就需要根據(jù)userMenus去過濾我們寫好的router/main下面的路由,也就是接口返回的菜單列表對應一份路由列表,然后將路由列表掛載在router上,這樣就能訪問路由了。
生成路由
現(xiàn)在我們需要根據(jù)userMenus生成對應的路由。
首先我們需要去加載所有的路由,也就是router/main下面的路由文件內(nèi)容。這里我使用的是require.context方法來加載所有的路由。
這里簡單介紹下require.context這個api:
require.context 是webpack的一個api,通過執(zhí)行require.context()函數(shù),來獲取指定的文件夾內(nèi)的特定文件,在需要多次從同一個文件夾內(nèi)導入的模塊,使用這個函數(shù)可以自動導入,不用每個都顯示的寫import來引入。
require.context(directory,useSubdirectories,regExp) 需要的參數(shù):
- directory:要搜索文件的相對路徑
- useSubdirectories:是否查詢其子目錄
- regExp:匹配基礎組件文件名的正則表達式
我們就通過這個api來加載router/main下面的路由。
const routeFiles = require.context('../router/main', true, /.ts/)我們對routeFiles進行打印:

routeFiles
得到了一個對象,我們需要對這個對象進行遍歷拿到文件內(nèi)容:
routeFiles.keys().forEach((key) => {
const route = require('../router/main' + key.split('.')[1]).default
console.log(route)
allRoutes.push(route)
})打印得到route

route
這樣我們就得到了所有的路由,放在allRoutes里面。
接下來我們需要根據(jù)userMenus獲取需要添加的routes。
開始我們提到過路由type,這個字段主要是區(qū)分菜單下是否還有子菜單,1表示有子菜單,2表示沒有子菜單。

接口返回的菜單
我們將allRoutes進行遍歷,然后根據(jù)path與接口返回的菜單列表userMenus里的path進行比較,如果相同就是匹配到了,那我們就需要這條路由,否則就將這條路由過濾掉。由于allRoutes下的每一項都還可能存在子路由,所以這里我們也需要進行遞歸篩選。具體的方法如下:
const _recurseGetRoute = (menus: any[]) => {
for (const menu of menus) {
if (menu.type === 2) {
const route = allRoutes.find((route) => route.path === menu.url)
if (route) routes.push(route)
} else {
_recurseGetRoute(menu.children)
}
}
}最終,routes就是我們得到的userMenus所對應的路由列表。
將生成對應的路由的邏輯整理如下:
import { RouteRecordRaw } from 'vue-router'
export function generateRoutes(userMenus: any[]): RouteRecordRaw[] {
const routes: RouteRecordRaw[] = []
// 1.先去加載默認所有的routes
const allRoutes: RouteRecordRaw[] = []
const routeFiles = require.context('../router/main', true, /.ts/)
routeFiles.keys().forEach((key) => {
const route = require('../router/main' + key.split('.')[1]).default
console.log(route)
allRoutes.push(route)
})
// 2.根據(jù)菜單獲取需要添加的routes
// userMenus:
// 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((route) => route.path === menu.url)
if (route) routes.push(route)
} else {
_recurseGetRoute(menu.children)
}
}
}
_recurseGetRoute(userMenus)
return routes
}掛載路由
最后,需要將我們得到的routes掛載er上面。
還是將掛載路由的時機放在全局路由守衛(wèi)這里,我們在router文件夾下創(chuàng)建一個permission.ts,用于寫全局路由守衛(wèi)相關邏輯:
import router from '@/router'
import { RouteLocationNormalized } from 'vue-router'
import localCache from '@/utils/cache'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import store from '@/store'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login']
const userMenu = store.state.userModule.userMenu
router.beforeEach(
async (
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: any
) => {
document.title = to.meta.title as string
const token: string = localCache.getCache('token')
NProgress.start()
// 判斷該用戶是否登錄
if (token) {
if (to.path === '/login') {
// 如果登錄,并準備進入 login 頁面,則重定向到主頁
next({ path: '/' })
NProgress.done()
} else {
store.dispatch('routesModule/generateRoutes', { userMenu })
// 確保添加路由已完成
// 設置 replace: true, 因此導航將不會留下歷史記錄
next({ ...to, replace: true })
}
} else {
// 如果沒有 token
if (whiteList.includes(to.path)) {
// 如果在免登錄的白名單中,則直接進入
next()
} else {
// 其他沒有訪問權限的頁面將被重定向到登錄頁面
next('/login')
NProgress.done()
}
}
}
)
router.afterEach(() => {
NProgress.done()
})routesModule文件下的代碼:
// 引入generateRoutes
import { generateRoutes } from '@/utils/generateRoutes'
actions: {
generateRoutes({ commit }, { userMenu }) {
const routes = generateRoutes(userMenu)
// 將routes => router.main.children
routes.forEach((route) => {
router.addRoute('main', route)
})
}
}這樣,完整的路由權限功能就完成了。我們可以看一下頁面:

系統(tǒng)界面
總結
相比純前端實現(xiàn)路由權限,這種基于后端返回路由菜單的方式會顯得簡單一些。我們不需要經(jīng)過RBAC去過濾出用戶可以訪問的路由,而是接口直接返回給了我們。我們只需要將路由菜單對應生成一份路由,然后將路由進行掛載。
到此這篇關于前端配合后端實現(xiàn)Vue路由權限的文章就介紹到這了,更多相關后端實現(xiàn)Vue路由權限內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
element?UI中el-dialog實現(xiàn)拖拽功能示例代碼
我們在開發(fā)中常會遇見拖拽的功能,下面這篇文章主要給大家介紹了關于element?UI中el-dialog實現(xiàn)拖拽功能的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-12-12
Vue3中如何修改父組件傳遞到子組件中的值(全網(wǎng)少有!)
大家都知道,vue是具有單向數(shù)據(jù)流的傳遞特性,下面這篇文章主要給大家介紹了關于Vue3中如何修改父組件傳遞到子組件中值的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-04-04

