前端+接口請(qǐng)求實(shí)現(xiàn)vue動(dòng)態(tài)路由
前端 + 接口請(qǐng)求實(shí)現(xiàn) vue 動(dòng)態(tài)路由
在 Vue 應(yīng)用中,通過前端結(jié)合后端接口請(qǐng)求來實(shí)現(xiàn)動(dòng)態(tài)路由是一種常見且有效的權(quán)限控制方案。這種方法允許前端根據(jù)用戶的角色和權(quán)限,動(dòng)態(tài)生成和加載路由,而不是在應(yīng)用啟動(dòng)時(shí)就固定所有的路由配置。
實(shí)現(xiàn)原理
定義靜態(tài)路由配置:
- 在項(xiàng)目的初始階段,定義一套完整的路由配置,這些配置包含了所有可能的路由路徑和相關(guān)的權(quán)限信息。
用戶登錄與鑒權(quán):
- 用戶登錄時(shí),前端向后端發(fā)送請(qǐng)求驗(yàn)證用戶的身份。
- 服務(wù)器驗(yàn)證成功后,返回一個(gè)包含用戶信息和權(quán)限的數(shù)據(jù)對(duì)象。
獲取用戶權(quán)限信息:
- 前端根據(jù)登錄時(shí)獲得的令牌(如 JWT),再次向后端請(qǐng)求獲取當(dāng)前用戶的權(quán)限信息。
- 權(quán)限信息可能包括用戶的角色、能夠訪問的資源等。
動(dòng)態(tài)生成路由:
- 前端根據(jù)從后端獲取的權(quán)限信息,動(dòng)態(tài)生成符合用戶權(quán)限的路由表。
- 這個(gè)過程可以通過遞歸算法處理路由配置樹,根據(jù)用戶的權(quán)限過濾掉無權(quán)訪問的路由。
動(dòng)態(tài)添加路由:
- 使用 Vue Router 的 router.addRoutes(routes) 方法將生成的路由動(dòng)態(tài)添加到路由實(shí)例中。
- 這樣只有經(jīng)過權(quán)限驗(yàn)證的路由才會(huì)被添加,從而實(shí)現(xiàn)了權(quán)限控制。
動(dòng)態(tài)渲染菜單:
- 左側(cè)菜單通常是基于生成的路由表來渲染的,因此只有用戶有權(quán)訪問的路由才會(huì)在菜單中顯示。
優(yōu)點(diǎn)
安全性:
- 只有經(jīng)過驗(yàn)證的用戶才能訪問其權(quán)限范圍內(nèi)的頁面。
- 減少了由于硬編碼路由導(dǎo)致的安全漏洞。
靈活性:
- 可以根據(jù)用戶的權(quán)限動(dòng)態(tài)調(diào)整應(yīng)用的結(jié)構(gòu),無需重新部署整個(gè)應(yīng)用即可調(diào)整路由。
- 支持按需加載(懶加載),提高應(yīng)用性能。
用戶體驗(yàn):
- 只展示用戶可以訪問的菜單項(xiàng),避免顯示無用鏈接,提高用戶體驗(yàn)。
- 用戶界面更加簡(jiǎn)潔,只顯示與其角色相關(guān)的功能。
可維護(hù)性:
- 簡(jiǎn)化了路由配置,因?yàn)椴恍枰獮槊總€(gè)角色單獨(dú)編寫路由配置,而是集中管理權(quán)限。
- 更容易擴(kuò)展和修改權(quán)限配置,只需更新后端的權(quán)限數(shù)據(jù)即可。
開發(fā)效率:
- 開發(fā)者只需要關(guān)注業(yè)務(wù)邏輯,而不需要關(guān)心每個(gè)角色的具體路由配置。
- 減少了重復(fù)工作,提高了開發(fā)效率。
示例
在前導(dǎo)航路由鉤子 beforeEach 函數(shù)里發(fā)送接口請(qǐng)求獲取路由信息
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) {
// 判斷當(dāng)前用戶是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
// 獲取路由信息
store.dispatch('GenerateRoutes').then((res) => {
console.log('--------------', res);
// 根據(jù)roles權(quán)限生成可訪問的路由表
router.addRoutes(res) // 動(dòng)態(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) {
// 在免登錄白名單,直接進(jìn)入
next()
} else {
next(`/login`) // 否則全部重定向到登錄頁
}
}
})
permission.js 文件需引入到 main.js 里
如果項(xiàng)目 vue-router 版本超過 3.3.0, 需要遍歷路由數(shù)組再使用 router.addRoute() 方法逐個(gè)添加路由
res.forEach( route => {
router.addRoute(route);
})
假設(shè)后端接口返回的路由權(quán)限如下
[
{
path: '/admin',
meta: {
title: "系統(tǒng)管理",
},
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: "表格測(cè)試",
},
component: '/tableEcho/index.vue',
children: [
{
path: 'add',
name: 'addTable',
hidden: true,
meta: {
title: "新增測(cè)試",
},
component: '/tableEcho/add.vue'
}
]
},
],
},
]
vuex 處理數(shù)據(jù)
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 用戶所用的權(quán)限級(jí)別 1 普通用戶 2 項(xiàng)目經(jīng)理 3 部門管理員 4 綜合部管理員 5 部門領(lǐng)導(dǎo) -1 項(xiàng)目運(yùn)維管理員
setStore('ROLE_TYPE', roleType);
if (res.data.roles) { // 驗(yàn)證返回的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) => {
// 向后端請(qǐng)求路由數(shù)據(jù)
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);
})
})
},
// 退出系統(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: {
}
})
const loadView = (view) => {
// 路由懶加載
return () => import(`@/views${view}`);
};
function filterAsyncRouter(routes) {
return routes.filter((route) => {
if (Array.isArray(route.children) && route.children.length > 0) {
// 如果該路由含有子路由時(shí),遞歸調(diào)用該函數(shù)
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是字符串, 需手動(dòng)封裝函數(shù)轉(zhuǎn)換成組件
公共路由如下
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", // 用戶在地址欄輸入 '/' 時(shí)會(huì)自動(dòng)重定向到 /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
文件結(jié)構(gòu)如下

頁面菜單渲染

左側(cè)菜單實(shí)現(xiàn)參考鏈接: Elemnt-UI + 遞歸組件實(shí)現(xiàn)后臺(tái)管理系統(tǒng)左側(cè)菜單
前端單獨(dú)實(shí)現(xiàn)動(dòng)態(tài)路由參考連接: 前端單獨(dú)實(shí)現(xiàn) vue 動(dòng)態(tài)路由
總結(jié)
通過以上步驟,你可以實(shí)現(xiàn)一個(gè)完整的動(dòng)態(tài)路由權(quán)限管理系統(tǒng):
- 后端接口返回路由配置:獲取用戶的權(quán)限信息及路由信息。
- 動(dòng)態(tài)加載組件:使用異步組件方式加載指定路徑的組件。
- 動(dòng)態(tài)添加路由:根據(jù)權(quán)限信息動(dòng)態(tài)添加路由。
- 路由守衛(wèi):使用
router.addRoutes()或router.addRoute()添加路由。
這樣可以確保應(yīng)用根據(jù)用戶的權(quán)限動(dòng)態(tài)加載相應(yīng)的路由,增強(qiáng)安全性與靈活性。
到此這篇關(guān)于前端+接口請(qǐng)求實(shí)現(xiàn)vue動(dòng)態(tài)路由的文章就介紹到這了,更多相關(guān)vue 動(dòng)態(tài)路由內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue3動(dòng)態(tài)路由(響應(yīng)式帶參數(shù)的路由)變更頁面不刷新的問題解決辦法
- 在Vue3中實(shí)現(xiàn)動(dòng)態(tài)路由的示例代碼
- vue3動(dòng)態(tài)路由+菜單欄的實(shí)現(xiàn)示例
- vue3實(shí)現(xiàn)動(dòng)態(tài)路由及菜單
- Vue實(shí)現(xiàn)動(dòng)態(tài)路由的示例詳解
- vue實(shí)現(xiàn)動(dòng)態(tài)路由的詳細(xì)代碼示例
- vue3+element-plus動(dòng)態(tài)路由菜單示例代碼
- vue3動(dòng)態(tài)路由刷新出現(xiàn)空白頁的原因與最優(yōu)解
相關(guān)文章
Vue2?中自定義圖片懶加載指令?v-lazy實(shí)例詳解
這篇文章主要為大家介紹了Vue2?中自定義圖片懶加載指令?v-lazy實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
Vue+SpringBoot實(shí)現(xiàn)支付寶沙箱支付的示例代碼
本文主要介紹了Vue+SpringBoot實(shí)現(xiàn)支付寶沙箱支付的示例代碼,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-06-06
解決ElementUI組件中el-upload上傳圖片不顯示問題
這篇文章主要介紹了解決ElementUI組件中el-upload上傳圖片不顯示問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
vue路由跳轉(zhuǎn)攜帶參數(shù)的方式總結(jié)
我們知道在vue中每個(gè)頁面都需要在路由中聲明,下面這篇文章主要給大家介紹了關(guān)于vue路由跳轉(zhuǎn)攜帶參數(shù)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10
一次用vue3簡(jiǎn)單封裝table組件的實(shí)戰(zhàn)過程
之所以封裝全局組件是為了省事,所有的目的,全都是為了偷懶,下面這篇文章主要給大家介紹了關(guān)于用vue3簡(jiǎn)單封裝table組件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12
vue實(shí)現(xiàn)動(dòng)態(tài)綁定行內(nèi)樣式style的backgroundImage
這篇文章主要介紹了vue實(shí)現(xiàn)動(dòng)態(tài)綁定行內(nèi)樣式style的backgroundImage方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07

