動態路由前端控制或是後端控制
技術標籤:vuejsvuejavascript
為什麼設定動態路由
我們在開發的過程中,會有不同的人來作業系統,有admin(管理員)、superAdmin(超管),還會有各種操作員、普通管理員。為了區別這些人員,我們會給不同的人分配不一樣的角色,從而來展示不同的選單,這個就必須要通過動態路由來實現。
前端控制
1、不用後端幫助,路由表維護在前端
2、邏輯相對比較簡單,比較容易上手
後端控制
1、相對更安全一點
2、路由表維護在資料庫
一、前端控制
通過路由的meta屬性role來控制載入路由
具體方法是:
1、根據登入使用者的賬號,返回前端使用者的角色 2、前端根據使用者的角色,跟路由表的meta.role進行匹配 3、講匹配到的路由形成可訪問路由
1、把靜態路由和動態路由分別寫在router.js
2、在vuex維護一個state,通過配角色來控制選單顯不顯示
3、新建一個路由守衛函式,可以在main.js,也可以抽離出來一個檔案
4、側邊欄的可以從vuex裡面取資料來進行渲染
核心程式碼:
1、在router.js檔案(把靜態路由和動態路由分別寫在router.js)
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) import Layout from '@/layout' // constantRoutes 靜態路由,主要是登入頁、404頁等不需要動態的路由 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 動態路由 export const asyncRoutes = [ { path: '/permission', component: Layout, redirect: '/permission/page', alwaysShow: true, name: 'Permission', meta: { title: 'Permission', icon: 'lock', // 核心程式碼,可以通過配的角色來進行遍歷,從而是否展示 // 這個意思就是admin、editor這兩個角色,這個選單是可以顯示 roles: ['admin', 'editor'] }, children: [ { path: 'page', component: () => import('@/views/permission/page'), name: 'PagePermission', meta: { title: 'Page Permission', // 這個意思就是隻有admin能展示 roles: ['admin'] } } ] } ] const createRouter = () => new Router({ scrollBehavior: () => ({ y: 0 }), routes: constantRoutes }) const router = createRouter() // 這個是重置路由用的,很有用,別看這麼幾行程式碼 export function resetRouter() { const newRouter = createRouter() router.matcher = newRouter.matcher } export default router
2、store/permission.js(在vuex維護一個state,通過配角色來控制選單顯不顯示)
import { asyncRoutes, constantRoutes } from '@/router' // 這個方法是用來把角色和route.meta.role來進行匹配 function hasPermission(roles, route) { if (route.meta && route.meta.roles) { return roles.some(role => route.meta.roles.includes(role)) } else { return true } } // 這個方法是通過遞迴來遍歷路由,把有許可權的路由給遍歷出來 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) => { // 這個地方維護了兩個狀態一個是addRouters,一個是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 { // 核心程式碼,把路由和獲取到的角色(後臺獲取的)傳進去進行匹配 accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) } // 把匹配完有許可權的路由給set到vuex裡面 commit('SET_ROUTES', accessedRoutes) resolve(accessedRoutes) }) } } export default { namespaced: true, state, mutations, actions }
3、src/permission.js(新建一個路由守衛函式,可以在main.js,也可以抽離出來一個檔案)
這裡面的程式碼主要是控制路由跳轉之前,先查一下有哪些可訪問的路由,登入以後跳轉的邏輯可以在這個地方寫
// permission.js
router.beforeEach((to, from, next) => {
if (store.getters.token) { // 判斷是否有token
if (to.path === '/login') {
next({ path: '/' });
} else {
// 判斷當前使用者是否已拉取完user_info資訊
if (store.getters.roles.length === 0) {
store.dispatch('GetInfo').then(res => { // 拉取info
const roles = res.data.role;
// 把獲取到的role傳進去進行匹配,生成可以訪問的路由
store.dispatch('GenerateRoutes', { roles }).then(() => {
// 動態新增可訪問路由表(核心程式碼,沒有它啥也幹不了)
router.addRoutes(store.getters.addRouters)
// hack方法 確保addRoutes已完成
next({ ...to, replace: true })
})
}).catch(err => {
console.log(err);
});
} else {
next() //當有使用者許可權的時候,說明所有可訪問路由已生成 如訪問沒許可權的全面會自動進入404頁面
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) { // 在免登入白名單,直接進入
next();
} else {
next('/login'); // 否則全部重定向到登入頁
}
}
})
4、側邊欄的可以從vuex裡面取資料來進行渲染
核心程式碼是從router取可以用的路由物件,來進行側邊欄的渲染,不管是前端動態載入還是後端動態載入路由,這個程式碼都是一樣的
layout/components/siderbar/index.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"
>
// 把取到的路由進行迴圈作為引數傳給子元件
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
</el-menu>
// 獲取有許可權的路由
routes() {
return this.$router.options.routes
}
layout/components/siderbar/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: ''
}
}
前端控制路由,邏輯相對簡單,後端只需要存這個使用者的角色就可以了,前端拿使用者的角色進行匹配。但是如果新增角色,就會非常痛苦,每一個都要加。
二、後端控制路由
具體方法是:
1、使用者登入以後,後端根據該使用者的角色,直接生成可訪問的路由資料,注意這個地方是資料
2、前端根據後端返回的路由資料,轉成自己需要的路由結構
具體的程式碼邏輯:
1、router.js裡面只放一些靜態的路由,login、404之類
2、整理一份資料結構,存到表裡
3、從後端獲取路由資料,寫一個數據轉換的方法,講資料轉成可訪問的路由
4、也是維護一個vuex狀態,將轉換好的路由存到vuex裡面
5、側邊欄也是從路由取資料進行渲染
因為前段控制和後端控制,後面的流程大部分都是一樣的,所以這個地方只看看前面不一樣的流程
1、store/permission.js,在vuex裡面傳送請求獲取資料
GenerateRoutes({ commit }, data) {
return new Promise((resolve, reject) => {
getRoute(data).then(res => {
// 將獲取到的資料進行一個轉換,然後存到vuex裡
const accessedRouters = arrayToMenu(res.data)
accessedRouters.concat([{ path: '*', redirect: '/404', hidden: true }])
commit('SET_ROUTERS', accessedRouters)
resolve()
}).catch(error => {
reject(error)
})
})
}
2、整理一份資料結構,存到表裡
我們知道vue的router規定的資料結構是這樣的:
{
path: '/form',
component: Layout,
children: [
{
path: 'index',
name: 'Form',
component: () => import('@/views/form/index'),
meta: { title: 'Form', icon: 'form' }
}
]
}
所以,一級選單有幾個引數必須要有:id、path、name、component、title,二級選單children是一個數組,是子父級的關係,所以可以給加一個fid或者parentId,來進行匹配,後面寫轉換方法的時候會詳細解釋,資料格式大概就是這樣:
// 一級選單
// parentId為0的就可以當做一級選單,id最好是可以選4位數,至於為什麼等你開發專案的時候就知道了
{
id: 1300
parentId: 0
title: "企業管理"
path: "/enterprise"
hidden: false
component: null
hidden: false
name: "enterprise"
},
// 二級選單
// parentId不為0的,就可以拿parentId跟一級選單的id去匹配,匹配上的就push到children裡面
{
id: 1307
parentId: 1300
title: "商戶資訊"
hidden: false
path: "merchantInfo"
component: "enterprise/merchantInfo" // 要跟本地的檔案地址匹配上
hidden: false
name: "merchantInfo"
}
3、寫一個轉化方法,把獲取到的資料轉換成router結構
剛才獲取到的資料無法直接轉成router進行渲染,需要一個arrayToMenu的方法,剛才也說了一些思路,下面就一起分析下這個方法:
export function arrayToMenu(array) {
const nodes = []
// 獲取頂級節點
for (let i = 0; i < array.length; i++) {
const row = array[i]
// 這個exists方法就是判斷下有沒有子級
if (!exists(array, row.parentId)) {
nodes.push({
path: row.path, // 路由地址
hidden: row.hidden, // 全部攜程true就行,如果後端沒配
component: Layout, // 一般就是匹配你檔案的component
name: row.name, // 路由名稱
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()
// 獲取子節點
for (let i = 0; i < array.length; i++) {
const row = array[i]
// parentId等於哪個父級的id,就push到哪個
if (row.parentId === node.id) {
const child = {
path: row.path,
name: row.name,
hidden: row.hidden,
// 核心程式碼,因為二級路由的component是需要匹配頁面的
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
}
// 看下有沒有子級
function exists(rows, parentId) {
for (let i = 0; i < rows.length; i++) {
if (rows[i].id === parentId) return true
}
return false
}
側邊欄的程式碼跟靜態的程式碼是一樣的