vue實(shí)現(xiàn)動(dòng)態(tài)路由詳細(xì)
主流的實(shí)現(xiàn)方式:
簡(jiǎn)單聊一下兩種方式的優(yōu)勢(shì),畢竟如果你從來(lái)沒(méi)做過(guò),說(shuō)再多也看不明白,還是得看代碼
前端控制
- 不用后端幫助,路由表維護(hù)在前端
- 邏輯相對(duì)比較簡(jiǎn)單,比較容易上手
后端控制
- 相對(duì)更安全一點(diǎn)
- 路由表維護(hù)在數(shù)據(jù)庫(kù)
一、前端控制
思路:在路由配置里,通過(guò)meta屬性,擴(kuò)展權(quán)限相關(guān)的字段,在路由守衛(wèi)里通過(guò)判斷這個(gè)權(quán)限標(biāo)識(shí),實(shí)現(xiàn)路由的動(dòng)態(tài)增加,及頁(yè)面跳轉(zhuǎn);如:我們?cè)黾右粋€(gè)role字段來(lái)控制角色
具體方案:
1、根據(jù)登錄用戶(hù)的賬號(hào),返回前端用戶(hù)的角色
2、前端根據(jù)用戶(hù)的角色,跟路由表的meta.role進(jìn)行匹配
3、講匹配到的路由形成可訪問(wèn)路由
核心代碼邏輯
1、在router.js文件(把靜態(tài)路由和動(dòng)態(tài)路由分別寫(xiě)在router.js)
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
import Layout from '@/layout'
// constantRoutes 靜態(tài)路由,主要是登錄頁(yè)、404頁(yè)等不需要?jiǎng)討B(tài)的路由
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path*',
component: () => import('@/views/redirect/index')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/error-page/404'),
hidden: true
},
{
path: '/401',
component: () => import('@/views/error-page/401'),
hidden: true
}
]
// asyncRoutes 動(dòng)態(tài)路由
export const asyncRoutes = [
{
path: '/permission',
component: Layout,
redirect: '/permission/page',
alwaysShow: true,
name: 'Permission',
meta: {
title: 'Permission',
icon: 'lock',
// 核心代碼,可以通過(guò)配的角色來(lái)進(jìn)行遍歷,從而是否展示
// 這個(gè)意思就是admin、editor這兩個(gè)角色,這個(gè)菜單是可以顯示
roles: ['admin', 'editor']
},
children: [
{
path: 'page',
component: () => import('@/views/permission/page'),
name: 'PagePermission',
meta: {
title: 'Page Permission',
// 這個(gè)意思就是只有admin能展示
roles: ['admin']
}
}
]
}
]
const createRouter = () => new Router({
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
// 這個(gè)是重置路由用的,很有用,別看這么幾行代碼
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher
}
export default router
2、store/permission.js(在vuex維護(hù)一個(gè)state,通過(guò)配角色來(lái)控制菜單顯不顯示)
import { asyncRoutes, constantRoutes } from '@/router'
// 這個(gè)方法是用來(lái)把角色和route.meta.role來(lái)進(jìn)行匹配
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
// 這個(gè)方法是通過(guò)遞歸來(lái)遍歷路由,把有權(quán)限的路由給遍歷出來(lái)
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
// 這個(gè)地方維護(hù)了兩個(gè)狀態(tài)一個(gè)是addRouters,一個(gè)是routes
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('admin')) {
accessedRoutes = asyncRoutes || []
} else {
// 核心代碼,把路由和獲取到的角色(后臺(tái)獲取的)傳進(jìn)去進(jìn)行匹配
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
// 把匹配完有權(quán)限的路由給set到vuex里面
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
3、src/permission.js
(新建一個(gè)路由守衛(wèi)函數(shù),可以在main.js,也可以抽離出來(lái)一個(gè)文件)
這里面的代碼主要是控制路由跳轉(zhuǎn)之前,先查一下有哪些可訪問(wèn)的路由,登錄以后跳轉(zhuǎn)的邏輯可以在這個(gè)地方寫(xiě)
// permission.js
router.beforeEach((to, from, next) => {
if (store.getters.token) { // 判斷是否有token
if (to.path === '/login') {
next({ path: '/' });
} else {
// 判斷當(dāng)前用戶(hù)是否已拉取完user_info信息
if (store.getters.roles.length === 0) {
store.dispatch('GetInfo').then(res => { // 拉取info
const roles = res.data.role;
// 把獲取到的role傳進(jìn)去進(jìn)行匹配,生成可以訪問(wèn)的路由
store.dispatch('GenerateRoutes', { roles }).then(() => {
// 動(dòng)態(tài)添加可訪問(wèn)路由表(核心代碼,沒(méi)有它啥也干不了)
router.addRoutes(store.getters.addRouters)
// hack方法 確保addRoutes已完成
next({ ...to, replace: true })
})
}).catch(err => {
console.log(err);
});
} else {
next() //當(dāng)有用戶(hù)權(quán)限的時(shí)候,說(shuō)明所有可訪問(wèn)路由已生成 如訪問(wèn)沒(méi)權(quán)限的全面會(huì)自動(dòng)進(jìn)入404頁(yè)面
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) { // 在免登錄白名單,直接進(jìn)入
next();
} else {
next('/login'); // 否則全部重定向到登錄頁(yè)
}
}
})
4、側(cè)邊欄的可以從vuex里面取數(shù)據(jù)來(lái)進(jìn)行渲染
核心代碼是從router取可以用的路由對(duì)象,來(lái)進(jìn)行側(cè)邊欄的渲染,不管是前端動(dòng)態(tài)加載還是后端動(dòng)態(tài)加載路由,這個(gè)代碼都是一樣的
<!-- layout/components/siderbar.vue -->
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="false"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
mode="vertical"
>
// 把取到的路由進(jìn)行循環(huán)作為參數(shù)傳給子組件
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
</el-menu>
// 獲取有權(quán)限的路由
routes() {
return this.$router.options.routes
}
<!-- layout/components/siderbarItem.vue -->
<template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
props: {
// route object
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
}
前端控制路由,邏輯相對(duì)簡(jiǎn)單,后端只需要存這個(gè)用戶(hù)的角色就可以了,前端拿用戶(hù)的角色進(jìn)行匹配。但是如果新增角色,就會(huì)非常痛苦,每一個(gè)都要加。
二、后端控制路由
后端控制大致思路是:路由配置放在數(shù)據(jù)庫(kù)表里,用戶(hù)登錄成功后,根據(jù)角色權(quán)限,把有權(quán)限的菜單傳給前端,前端格式化成頁(yè)面路由識(shí)別的結(jié)構(gòu),再渲染到頁(yè)面菜單上;
- 用戶(hù)登錄以后,后端根據(jù)該用戶(hù)的角色,直接生成可訪問(wèn)的路由數(shù)據(jù),注意這個(gè)地方是數(shù)據(jù)
- 前端根據(jù)后端返回的路由數(shù)據(jù),轉(zhuǎn)成自己需要的路由結(jié)構(gòu)
具體邏輯:
- router.js里面只放一些靜態(tài)的路由,
login、404之類(lèi) - 整理一份數(shù)據(jù)結(jié)構(gòu),存到表里
- 從后端獲取路由數(shù)據(jù),寫(xiě)一個(gè)數(shù)據(jù)轉(zhuǎn)換的方法,講數(shù)據(jù)轉(zhuǎn)成可訪問(wèn)的路由
- 也是維護(hù)一個(gè)
vuex狀態(tài),將轉(zhuǎn)換好的路由存到vuex里面 - 側(cè)邊欄也是從路由取數(shù)據(jù)進(jìn)行渲染
因?yàn)榍岸慰刂坪秃蠖丝刂?,后面的流程大部分都是一樣的,所以這個(gè)地方只看看前面不一樣的流程:
1、store/permission.js,在vuex里面發(fā)送請(qǐng)求獲取數(shù)據(jù)
GenerateRoutes({ commit }, data) {
return new Promise((resolve, reject) => {
getRoute(data).then(res => {
// 將獲取到的數(shù)據(jù)進(jìn)行一個(gè)轉(zhuǎn)換,然后存到vuex里
const accessedRouters = arrayToMenu(res.data)
accessedRouters.concat([{ path: '*', redirect: '/404', hidden: true }])
commit('SET_ROUTERS', accessedRouters)
resolve()
}).catch(error => {
reject(error)
})
})
}
2、整理一份數(shù)據(jù)結(jié)構(gòu),存到表里
// 頁(yè)面路由格式
{
path: '/form',
component: Layout,
children: [
{
path: 'index',
name: 'Form',
component: () => import('@/views/form/index'),
meta: { title: 'Form', icon: 'form' }
}
]
}
// 整理后的數(shù)據(jù)格式
// 一級(jí)菜單
// parentId為0的就可以當(dāng)做一級(jí)菜單,id最好是可以選4位數(shù),至于為什么等你開(kāi)發(fā)項(xiàng)目的時(shí)候就知道了
{
id: 1300
parentId: 0
title: "菜單管理"
path: "/menu"
hidden: false
component: null
hidden: false
name: "menu"
},
// 二級(jí)菜單
// parentId不為0的,就可以拿parentId跟一級(jí)菜單的id去匹配,匹配上的就push到children里面
{
id: 1307
parentId: 1300
title: "子菜單"
hidden: false
path: "menuItem"
component: "menu/menuItem" // 要跟本地的文件地址匹配上
hidden: false
name: "menuItem"
}
3、寫(xiě)一個(gè)轉(zhuǎn)化方法,把獲取到的數(shù)據(jù)轉(zhuǎn)換成router結(jié)構(gòu)
export function arrayToMenu(array) {
const nodes = []
// 獲取頂級(jí)節(jié)點(diǎn)
for (let i = 0; i < array.length; i++) {
const row = array[i]
// 這個(gè)exists方法就是判斷下有沒(méi)有子級(jí)
if (!exists(array, row.parentId)) {
nodes.push({
path: row.path, // 路由地址
hidden: row.hidden, // 全部true就行,如果后端沒(méi)配
component: Layout, // 一般就是匹配你文件的component
name: row.name, // 路由名稱(chēng)
meta: { title: row.title, icon: row.name }, // title就是顯示的名字
id: row.id, // 路由的id
redirect: 'noredirect'
})
}
}
const toDo = Array.from(nodes)
while (toDo.length) {
const node = toDo.shift()
// 獲取子節(jié)點(diǎn)
for (let i = 0; i < array.length; i++) {
const row = array[i]
// parentId等于哪個(gè)父級(jí)的id,就push到哪個(gè)
if (row.parentId === node.id) {
const child = {
path: row.path,
name: row.name,
hidden: row.hidden,
// 核心代碼,因?yàn)槎?jí)路由的component是需要匹配頁(yè)面的
component: require('@/views/' + row.component + '/index.vue'),
meta: { title: row.title, icon: row.name },
id: row.id
}
if (node.children) {
node.children.push(child)
} else {
node.children = [child]
}
toDo.push(child)
}
}
}
return nodes
}
// 看下有沒(méi)有子級(jí)
function exists(rows, parentId) {
for (let i = 0; i < rows.length; i++) {
if (rows[i].id === parentId) return true
}
return false
}
到此這篇關(guān)于vue實(shí)現(xiàn)動(dòng)態(tài)路由詳細(xì)的文章就介紹到這了,更多相關(guān)vue實(shí)現(xiàn)動(dòng)態(tài)路由內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue draggable resizable gorkys與v-chart使用與總結(jié)
這篇文章主要介紹了vue draggable resizable gorkys與v-chart使用與總結(jié),本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09
利用vue組件實(shí)現(xiàn)圖片的拖拽和縮放功能
這篇文章主要給大家介紹了關(guān)于利用vue組件實(shí)現(xiàn)圖片的拖拽和縮放功能的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01
Vue3?基礎(chǔ)概念與環(huán)境搭建過(guò)程詳解
本文介紹了Vue3的基礎(chǔ)概念,包括響應(yīng)式系統(tǒng)、組合式API和更好的TypeScript支持,同時(shí),文章手把手教你如何搭建Vue3開(kāi)發(fā)環(huán)境,使用Vite創(chuàng)建項(xiàng)目,并解析了項(xiàng)目的結(jié)構(gòu),通過(guò)這些內(nèi)容,讀者可以快速上手Vue3,并為后續(xù)的學(xué)習(xí)打下堅(jiān)實(shí)的基礎(chǔ),感興趣的朋友一起看看吧2025-02-02
vue上傳文件formData入?yún)榭?接口請(qǐng)求500的解決
這篇文章主要介紹了vue上傳文件formData入?yún)榭?接口請(qǐng)求500的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
基于vuejs實(shí)現(xiàn)一個(gè)todolist項(xiàng)目
這篇文章主要為大家詳細(xì)介紹了基于vuejs實(shí)現(xiàn)一個(gè)todolist項(xiàng)目,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
vue簡(jiǎn)單的二維數(shù)組循環(huán)嵌套方式
這篇文章主要介紹了vue簡(jiǎn)單的二維數(shù)組循環(huán)嵌套方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04

