前端+接口請求實現vue動態(tài)路由
前端 + 接口請求實現 vue 動態(tài)路由
在 Vue 應用中,通過前端結合后端接口請求來實現動態(tài)路由是一種常見且有效的權限控制方案。這種方法允許前端根據用戶的角色和權限,動態(tài)生成和加載路由,而不是在應用啟動時就固定所有的路由配置。
實現原理
定義靜態(tài)路由配置:
- 在項目的初始階段,定義一套完整的路由配置,這些配置包含了所有可能的路由路徑和相關的權限信息。
用戶登錄與鑒權:
- 用戶登錄時,前端向后端發(fā)送請求驗證用戶的身份。
- 服務器驗證成功后,返回一個包含用戶信息和權限的數據對象。
獲取用戶權限信息:
- 前端根據登錄時獲得的令牌(如 JWT),再次向后端請求獲取當前用戶的權限信息。
- 權限信息可能包括用戶的角色、能夠訪問的資源等。
動態(tài)生成路由:
- 前端根據從后端獲取的權限信息,動態(tài)生成符合用戶權限的路由表。
- 這個過程可以通過遞歸算法處理路由配置樹,根據用戶的權限過濾掉無權訪問的路由。
動態(tài)添加路由:
- 使用 Vue Router 的 router.addRoutes(routes) 方法將生成的路由動態(tài)添加到路由實例中。
- 這樣只有經過權限驗證的路由才會被添加,從而實現了權限控制。
動態(tài)渲染菜單:
- 左側菜單通常是基于生成的路由表來渲染的,因此只有用戶有權訪問的路由才會在菜單中顯示。
優(yōu)點
安全性:
- 只有經過驗證的用戶才能訪問其權限范圍內的頁面。
- 減少了由于硬編碼路由導致的安全漏洞。
靈活性:
- 可以根據用戶的權限動態(tài)調整應用的結構,無需重新部署整個應用即可調整路由。
- 支持按需加載(懶加載),提高應用性能。
用戶體驗:
- 只展示用戶可以訪問的菜單項,避免顯示無用鏈接,提高用戶體驗。
- 用戶界面更加簡潔,只顯示與其角色相關的功能。
可維護性:
- 簡化了路由配置,因為不需要為每個角色單獨編寫路由配置,而是集中管理權限。
- 更容易擴展和修改權限配置,只需更新后端的權限數據即可。
開發(fā)效率:
- 開發(fā)者只需要關注業(yè)務邏輯,而不需要關心每個角色的具體路由配置。
- 減少了重復工作,提高了開發(fā)效率。
示例
在前導航路由鉤子 beforeEach
函數里發(fā)送接口請求獲取路由信息
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(() => { // 獲取路由信息 store.dispatch('GenerateRoutes').then((res) => { console.log('--------------', res); // 根據roles權限生成可訪問的路由表 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`) // 否則全部重定向到登錄頁 } } })
permission.js 文件需引入到 main.js 里
如果項目 vue-router
版本超過 3.3.0, 需要遍歷路由數組再使用 router.addRoute()
方法逐個添加路由
res.forEach( route => { router.addRoute(route); })
假設后端接口返回的路由權限如下
[ { path: '/admin', meta: { title: "系統管理", }, component: 'Layout', children: [ { path: 'user', name: 'userIndex', meta: { title: "用戶管理", }, component: '/admin/user/index.vue' }, { path: 'role', name: 'roleIndex', meta: { title: "角色管理", }, component: '/admin/role/index.vue', children: [ { path: 'add', name: 'addRole', meta: { title: "添加角色", }, component: '/admin/user/index.vue' }, { path: 'update', name: 'updateRole', meta: { title: "編輯角色", }, component: '/admin/role/index.vue' } ] } ] }, { path: '/tableEcho', meta: { title: "表格管理", }, component: 'Layout', children: [ { path: 'test', name: 'tableEchoIndex', meta: { title: "表格測試", }, component: '/tableEcho/index.vue', children: [ { path: 'add', name: 'addTable', hidden: true, meta: { title: "新增測試", }, component: '/tableEcho/add.vue' } ] }, ], }, ]
vuex 處理數據
store/index.vue
// store/index.vue import Vue from 'vue' import Vuex from 'vuex' import { routes, dynamicRoutes } from "@/router"; import { login, getInfo, logout, getRouters } from "@/api/user"; import { setStore, clearStore } from '@/utils/store'; import Layout from '@/Layout/index.vue' 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 項目經理 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']); } resolve(); } else { reject(error); } }).catch(error => { reject(error); }) }) }, GenerateRoutes({ commit }) { return new Promise((resolve, reject) => { // 向后端請求路由數據 getRouters().then(res => { if (res.data.code === 0 || 200) { const sdata = JSON.parse(JSON.stringify(res.data.routes)); console.log('sdata::: ', sdata); let newRouters = filterAsyncRouter(sdata); // 連接公共路由 const sidebarRoutes = routes.concat([...newRouters]); commit('SET_ROUTE', sidebarRoutes); resolve(sidebarRoutes); } else { reject(error); } }).catch(error => { reject(error); }) }) }, // 退出系統 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: { } }) const loadView = (view) => { // 路由懶加載 return () => import(`@/views${view}`); }; function filterAsyncRouter(routes) { return routes.filter((route) => { if (Array.isArray(route.children) && route.children.length > 0) { // 如果該路由含有子路由時,遞歸調用該函數 route.children = filterAsyncRouter(route.children); } if (route.component) { // Layout ParentView 組件特殊處理 if (route.component === "Layout") { route.component = Layout; } else { // 路由組件懶加載 route.component = loadView(route.component); } } return true; }) }
由于接口返回的
component
是字符串, 需手動封裝函數轉換成組件
公共路由如下
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 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 }, ] const router = new VueRouter({ base: process.env.BASE_URL, routes }) export default router
文件結構如下
頁面菜單渲染
左側菜單實現參考鏈接: Elemnt-UI + 遞歸組件實現后臺管理系統左側菜單
前端單獨實現動態(tài)路由參考連接: 前端單獨實現 vue 動態(tài)路由
總結
通過以上步驟,你可以實現一個完整的動態(tài)路由權限管理系統:
- 后端接口返回路由配置:獲取用戶的權限信息及路由信息。
- 動態(tài)加載組件:使用異步組件方式加載指定路徑的組件。
- 動態(tài)添加路由:根據權限信息動態(tài)添加路由。
- 路由守衛(wèi):使用
router.addRoutes()
或router.addRoute()
添加路由。
這樣可以確保應用根據用戶的權限動態(tài)加載相應的路由,增強安全性與靈活性。
到此這篇關于前端+接口請求實現vue動態(tài)路由的文章就介紹到這了,更多相關vue 動態(tài)路由內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決ElementUI組件中el-upload上傳圖片不顯示問題
這篇文章主要介紹了解決ElementUI組件中el-upload上傳圖片不顯示問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10vue實現動態(tài)綁定行內樣式style的backgroundImage
這篇文章主要介紹了vue實現動態(tài)綁定行內樣式style的backgroundImage方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07