前端單獨實現(xiàn)vue動態(tài)路由的示例代碼
Vue 動態(tài)路由權限是指在 Vue 應用程序中,根據(jù)用戶的權限動態(tài)生成和控制路由的行為。這意味著不是所有的路由都在應用啟動時就被硬編碼到路由配置中,而是根據(jù)用戶的權限信息,在運行時動態(tài)地決定哪些路由應該被加載和顯示。
動態(tài)路由的優(yōu)點:
安全性:
- 只有經(jīng)過驗證的用戶才能訪問其權限范圍內(nèi)的頁面。
- 減少了由于硬編碼路由導致的安全漏洞。
靈活性:
- 可以根據(jù)用戶的權限動態(tài)調(diào)整應用的結構,無需重新部署整個應用即可調(diào)整路由。
- 支持按需加載(懶加載),提高應用性能。
用戶體驗:
- 只展示用戶可以訪問的菜單項,避免顯示無用鏈接,提高用戶體驗。
- 用戶界面更加簡潔,只顯示與其角色相關的功能。
可維護性:
- 簡化了路由配置,因為不需要為每個角色單獨編寫路由配置,而是集中管理權限。
- 更容易擴展和修改權限配置,只需更新前端的權限數(shù)據(jù)即可。
開發(fā)效率:
- 開發(fā)者只需要關注業(yè)務邏輯,而不需要關心每個角色的具體路由配置。
- 減少了重復工作,提高了開發(fā)效率。
實現(xiàn)步驟
定義靜態(tài)路由配置:
- 在項目中定義一個包含所有可能路由的靜態(tài)配置文件或對象,每個路由可以附加權限信息(如角色、訪問級別等)。
用戶登錄與鑒權:
- 用戶登錄時,前端存儲用戶的權限信息(如角色、權限列表等)。
動態(tài)生成路由:
- 根據(jù)用戶的權限信息,從前端的靜態(tài)路由配置中篩選出用戶有權訪問的路由。
- 使用遞歸算法或其他邏輯動態(tài)生成路由配置,并添加到 Vue Router 實例中。
動態(tài)渲染菜單:
- 根據(jù)動態(tài)生成的路由表來渲染左側菜單或頂部導航欄,確保只顯示用戶有權訪問的菜單項。
代碼示例
配置路由器
router/index.js
// router/index.js import Vue from 'vue' import VueRouter from 'vue-router' import Layout from '@/Layout/index.vue' Vue.use(VueRouter) // export const roleMap = { // '-1':'運維管理員', // '1':'普通用戶', // '2':'項目經(jīng)理', // '3':'部門管理員', // '4':'綜合部管理員', // '5':'部門領導' // } // 公共路由 export const routes = [ { path: '/', name: 'redirect', component: Layout, hidden: true, // 隱藏菜單 redirect: "/homePage", // 用戶在地址欄輸入 '/' 時會自動重定向到 /homePage 頁面 }, { path: '/homePage', component: Layout, redirect: "/homePage/index", meta: { title: "首頁", }, children: [ { path: 'index', name: 'homePageIndex', meta: { title: "首頁", }, component: () => import('@/views/homePage/index.vue') } ] }, { path: '/login', component: () => import('@/views/login.vue'), hidden: true }, { path: '/404', component: () => import('@/views/error/404.vue'), hidden: true }, { path: '/401', component: () => import('@/views/error/401.vue'), hidden: true }, ] // 動態(tài)權限路由 export const dynamicRoutes = [ { path: '/admin', meta: { title: "系統(tǒng)管理", }, component: Layout, permission: ['-1', '2', '3', '4', '5'], // all 所有角色都可以訪問 1 普通用戶 2 項目經(jīng)理 3 部門管理員 4 綜合部管理員 5 部門領導 -1 項目運維管理員 children: [ { path: 'user', name: 'userIndex', meta: { title: "用戶管理", }, permission: ['-1', '2', '3', '4', '5'], component: () => import('@/views/admin/user/index.vue') }, { path: 'role', name: 'roleIndex', meta: { title: "角色管理", }, permission: ['-1', '2', '3', '4', '5'], component: () => import('@/views/admin/role/index.vue'), children: [ { path: 'add', name: 'addRole', meta: { title: "添加角色", }, permission: ['-1',, '3', '4', '5'], component: () => import('@/views/admin/user/index.vue') }, { path: 'update', name: 'updateRole', meta: { title: "編輯角色", }, permission: ['-1', '2', '3', '4', '5'], component: () => import('@/views/admin/role/index.vue') } ] } ] }, { path: '/tableEcho', meta: { title: "表格管理", }, component: Layout, permission: ['-1', '1', '2'], children: [ { path: 'test', name: 'tableEchoIndex', meta: { title: "表格測試", }, permission: ['-1', '1', '2'], component: () => import('@/views/tableEcho/index.vue'), children: [ { path: 'add', name: 'addTable', hidden: true, meta: { title: "新增測試", }, permission: ['-1', '2'], component: () => import('@/views/tableEcho/add.vue') } ] }, ], }, ] const router = new VueRouter({ base: process.env.BASE_URL, routes }) export default router
上述代碼定義了一個公共路由
routes
和一個動態(tài)權限控制的路由dynamicRoutes
,permission
數(shù)組定義了哪些角色擁有該路由權限, 將用戶分為6個角色級別, 每個角色對應不同的角色級別,分別為
- ‘-1’:‘運維管理員’,
- ‘1’:‘普通用戶’,
- ‘2’:‘項目經(jīng)理’,
- ‘3’:‘部門管理員’,
- ‘4’:‘綜合部管理員’,
- ‘5’:‘部門領導’,
封裝路由守衛(wèi)
permission.js
// permission.js import router from './router' import store from './store' import { Message } from 'element-ui' import { getStore } from '@/utils/store'; const whiteList = ['/login', '/404', '/401']; router.beforeEach((to, from, next) => { let token = getStore('token'); if (token) { /* has token*/ if (to.path === '/login') { next({ path: '/' }); } else { if (store.getters.roles.length === 0) { // 判斷當前用戶是否已拉取完user_info信息 store.dispatch('GetInfo').then((res) => { console.log('--------------', res); router.addRoutes(res) // 動態(tài)添加可訪問路由表 next({ ...to, replace: true }) // hack方法 確保addRoutes已完成 }).catch(err => { store.dispatch('LogOut').then(() => { Message.error(err) next(`/`) }) }) } else { next() } } } else { // 沒有token if (whiteList.indexOf(to.path) !== -1) { // 在免登錄白名單,直接進入 next() } else { next(`/login`) // 否則全部重定向到登錄頁 } } })
上述代碼表示在路由的
beforeEach
函數(shù)里面調(diào)用 vuex 里面actions
里的方法發(fā)送接口請求獲取用戶信息與用戶角色權限, 最后通過router.addRoutes(res)
渲染路由
permission.js 文件需引入到 main.js里面
如果項目 vue-router
版本超過 3.3.0, 需要遍歷路由數(shù)組再使用 router.addRoute()
方法逐個添加路由
res.forEach( route => { router.addRoute(route); })
在 vuex 里獲取用戶所擁有的權限, 過濾該權限不擁有的路由
store/index.js
// store/index.js import Vue from 'vue' import Vuex from 'vuex' import { routes, dynamicRoutes } from "@/router"; import { login, getInfo, logout } from "@/api/user"; import { setStore, clearStore } from '@/utils/store'; Vue.use(Vuex) export default new Vuex.Store({ state: { routes, token: "", roleType: "", roles: [], permissions: [], sidebarRouters: [], }, getters: { token: state => state.token, roles: state => state.roles, permissions: state => state.permissions, sidebarRouters: state => state.sidebarRouters, }, mutations: { SET_TOKEN: (state, token) => { state.token = token; }, SET_USERINFO: (state, user) => { state.userInfo = user; }, SET_ROLETYPE: (state, roleType) => { state.roleType = roleType; }, SET_ROLES: (state, roles) => { state.roles = roles; }, SET_PERMISSIONS: (state, permissions) => { state.permissions = permissions; }, SET_ROUTE: (state, sidebarRouters) => { state.sidebarRouters = sidebarRouters; }, }, actions: { Login({ commit }, userInfo) { return new Promise((resolve, reject) => { login(userInfo).then(res => { setToken(res.data.token); setStore('token', res.data.token); commit('SET_TOKEN', res.data.token); resolve(); }).catch(error => { reject(error); }) }) }, // 獲取用戶信息 GetInfo({ commit }) { return new Promise((resolve, reject) => { getInfo().then(res => { console.log('res::: ', res); if (res.data.code === 0 || 200) { const user = res.data.sysUser; const roleType = res.data.roleType; commit('SET_USERINFO', user); // roleType 用戶所用的權限級別 1 普通用戶 2 項目經(jīng)理 3 部門管理員 4 綜合部管理員 5 部門領導 -1 項目運維管理員 setStore('ROLE_TYPE', roleType); if (res.data.roles) { // 驗證返回的roles是否為真 commit('SET_ROLES', res.data.roles); commit('SET_PERMISSIONS', res.data.permissions); } else { commit('SET_ROLES', ['ROLE_DEFAULT']); } // 過濾路由 let newRouters = filterRouter(roleType, dynamicRoutes); // 連接公共路由 const sidebarRouters = routes.concat([...newRouters]) commit('SET_ROUTE', sidebarRouters); resolve(sidebarRouters); } else { reject(error); } }).catch(error => { reject(error); }) }) }, // 退出系統(tǒng) LogOut({ commit, state }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { commit('SET_TOKEN', '') commit('SET_ROLES', []) commit('SET_PERMISSIONS', []) clearStore('token'); clearStore('userInfo') resolve() }).catch(error => { reject(error) }) }) }, }, modules: { } }) function filterRouter(roleType, routes) { return routes.filter(item => { // filter 方法創(chuàng)建一個新數(shù)組, 其包含通過所提供函數(shù)實現(xiàn)的測試的所有元素,測試未通過的元素會自動剔除 // 如果一級路由的 permission 含有當前角色的 roleType if (item.permission.includes(roleType)) { // 如果該一級路由含有子路由時,遞歸調(diào)用該函數(shù)判斷子路由是否有權限 if (Array.isArray(item.children) && item.children.length > 0) { // 遞歸調(diào)用該函數(shù),最后接受校驗通過后的子路由 let newChildren = filterRouter(roleType, item.children); // 如果子路由有值,則賦值給當前路由的 children,剔除校驗不通過的子路由 if (newChildren.length > 0) { item.children = newChildren; } else if (newChildren.length === 0) { // 如果子路由為空,則刪除該路由的 children 屬性 delete item.children; } } // 最后返回 true, 表示該路由通過權限校驗 return true; } }) }
上述代碼通過
getInfo
接口獲取用戶權限, 通過函數(shù)filterRouter
過濾掉該角色不擁有的路由, 通過concat
方法合并routes
公共路由, 最后通過resolve
返回
文件布局如下
下圖為頁面渲染的菜單(項目經(jīng)理角色)
左側菜單實現(xiàn)參考鏈接: Elemnt-UI + 遞歸組件實現(xiàn)后臺管理系統(tǒng)左側菜單
前端結合后端接口請求實現(xiàn)動態(tài)路由參考連接: 前端 + 接口請求實現(xiàn) vue 動態(tài)路由
總結
在用戶登錄成功后從服務器獲取用戶的權限信息,在 vuex 的異步處理函數(shù)中過濾掉角色權限不存在的路由,使用 concat()
合并公共路由,最后使用 router.addRoutes(res)
動態(tài)添加可訪問的路由。這樣可以確保應用根據(jù)用戶的權限動態(tài)加載相應的路由,增強安全性與靈活性。
相關文章
前端vue?uni-app?cc-countdown倒計時組件使用詳解
cc-countdown是一個倒計時組件,它可以顯示剩余時間、天數(shù)、小時數(shù)、分鐘數(shù)和秒數(shù),在本文中,我們將介紹如何在uni-app中使用cc-countdown組件,需要的朋友可以參考下2023-08-08vue使用websocket實現(xiàn)實時數(shù)據(jù)推送功能
這篇文章主要為大家詳細介紹了vue如何使用websocket實現(xiàn)實時數(shù)據(jù)推送,發(fā)布訂閱重連單點登錄功能,感興趣的小伙伴可以跟隨小編一起學習一下2023-12-12一文詳細聊聊vue3的defineProps、defineEmits和defineExpose
vue3官方文檔defineProps和defineEmits都是只能在scriptsetup中使用的編譯器宏,下面這篇文章主要給大家介紹了關于vue3的defineProps、defineEmits和defineExpose的相關資料,需要的朋友可以參考下2023-02-02解決vue ui報錯Couldn‘t parse bundle asset“C:
這篇文章主要介紹了解決vue ui報錯Couldn‘t parse bundle asset“C:\Users\Administrator\vue_project1\dist\js\about.js“. Analyzer問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04vue使用lodash中debounce(防抖函數(shù))的幾種方法實現(xiàn)
本文主要介紹了vue使用lodash中debounce(防抖函數(shù))的幾種方法實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2025-02-02