vue項(xiàng)目實(shí)現(xiàn)動(dòng)態(tài)路由的方式大體可分為兩種:
1.簡(jiǎn)單的角色路由設(shè)置:比如只涉及到管理員和普通用戶的權(quán)限。通常直接在前端進(jìn)行簡(jiǎn)單的角色權(quán)限設(shè)置
前端這邊把路由寫(xiě)好,登錄的時(shí)候根據(jù)用戶的角色權(quán)限來(lái)動(dòng)態(tài)展示路由,(前端控制路由)
詳情可參閱花褲衩大佬的項(xiàng)目手把手...
2.復(fù)雜的路由權(quán)限設(shè)置:比如OA系統(tǒng)、多種角色的權(quán)限配置。通常需要后端返回路由列表,前端渲染使用
后臺(tái)傳來(lái)當(dāng)前用戶對(duì)應(yīng)權(quán)限的路由表,前端通過(guò)調(diào)接口拿到后處理(后端處理路由)
這兩種方法各有優(yōu)點(diǎn),效果都能實(shí)現(xiàn),我們公司是通過(guò)第二中種方法實(shí)現(xiàn)的,原因就是公司項(xiàng)目里有一個(gè)專(zhuān)門(mén)的用戶中心,里邊邏輯很復(fù)雜,不好返給前端用戶權(quán)限,擔(dān)心路由放到前端不安全(以上的話是公司的后臺(tái)同學(xué)講的),那好吧,抱著都試試、鍛煉下自己能力的態(tài)度,我們搞了第二種方法。
Vue 動(dòng)態(tài)路由的實(shí)現(xiàn)(后臺(tái)傳遞路由,前端拿到并生成側(cè)邊欄)
思路整理
大體步驟:攔截路由->后臺(tái)取到路由->保存路由到localStorage(用戶登錄進(jìn)來(lái)只會(huì)從后臺(tái)取一次,其余都從本地取,所以用戶,只有退出在登錄路由才會(huì)更新)
后端管理創(chuàng)建菜單需要
菜單名字menName;
菜單路徑menPath;
菜單指向的資源menuUrl(也就是組件地址, 一般從views層級(jí)開(kāi)始寫(xiě))
前端登錄后通過(guò)接口請(qǐng)求拿到菜單數(shù)據(jù)后,
將菜單數(shù)據(jù)轉(zhuǎn)換格式 轉(zhuǎn)成前端需要的路由表
menName ---> name
menPath ---> path
menuUrl ---->components文件
轉(zhuǎn)換時(shí),用到這個(gè)方法找組件資源 把 menuUrl 可以變?yōu)閏omponents的格式,轉(zhuǎn)為組件文件
module.exports = file => require('@/views' + file + '.vue').default // vue-loader at least v13.0.0+
生成路由表
可以再過(guò)濾一遍生成的路由表
下面這個(gè)方法找到views底下所有的組件資源
require.context('@/views', true, /\.vue$/).keys().forEach(v => map.set(v.replace(/^\.(.*)\.vue$/,'$1'), true))
export default map
路由表里路由的組件在所有組件資源里沒(méi)找到時(shí),將該路由的path變?yōu)?404
過(guò)濾后,再動(dòng)態(tài)添加
getRouter.push({ path: '*', redirect: '/404', hidden: true });
router.addRoutes(getRouter); //動(dòng)態(tài)添加路由
代碼
前置工作:配置項(xiàng)目路由文件,該文件中沒(méi)有路由,或者存在一部分公共路由,即沒(méi)有權(quán)限的路由
import Vue from 'vue'
import Router from 'vue-router'
import Layout from '@/layout';
Vue.use(Router)
// 配置項(xiàng)目中沒(méi)有涉及權(quán)限的公共路由
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
]
const createRouter = () => new Router({
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher
}
export default router
1.登錄后通過(guò)接口拿到后端返回的路由數(shù)據(jù)
每個(gè)路由都使用到組件Layout,這個(gè)組件是整體的頁(yè)面布局:左側(cè)菜單列,右側(cè)頁(yè)面,所以children下邊的第一級(jí)路由就是你自己的開(kāi)發(fā)的頁(yè)面,meta里包含著路由的名字,以及路由對(duì)應(yīng)的icon;
因?yàn)榭赡軙?huì)有多級(jí)菜單,所以會(huì)出現(xiàn)children下邊嵌套children的情況;
路由是數(shù)組格式
后端返回的路由格式
"data": {
"router": [
{
"path": "",
"component": "Layout",
"redirect": "dashboard",
"children": [
{
"path": "dashboard",
"component": "dashboard/index",
"meta": {
"title": "首頁(yè)",
"icon": "dashboard"
}
}
]
},
{
"path": "/example",
"component": "Layout",
"redirect": "/example/table",
"name": "Example",
"meta": {
"title": "案例",
"icon": "example"
},
"children": [
{
"path": "table",
"name": "Table",
"component": "table/index",
"meta": {
"title": "表格",
"icon": "table"
}
},
{
"path": "tree",
"name": "Tree",
"component": "tree/index",
"meta": {
"title": "樹(shù)形菜單",
"icon": "tree"
}
}
]
},
{
"path": "/form",
"component": "Layout",
"children": [
{
"path": "index",
"name": "Form",
"component": "form/index",
"meta": {
"title": "表單",
"icon": "form"
}
}
]
},
{
"path": "*",
"redirect": "/404",
"hidden": true
}
]
}
實(shí)際前端需要的 component是 component: () => import('@/views/content/classify'),
2.將后端傳回的"component": "Layout", 轉(zhuǎn)為 "component": Layout組件對(duì)象
因?yàn)橛卸嗉?jí)路由的出現(xiàn),所以要寫(xiě)成遍歷遞歸方法,確保把每個(gè)component轉(zhuǎn)成組件對(duì)象
因?yàn)楹笈_(tái)傳回的是字符串,所以要把加載組件的過(guò)程 封裝成一個(gè)方法,用這個(gè)方法在遍歷中使用;詳情查看項(xiàng)目里的router文件夾下的 _import_development.js和_import_production.js文件
Layout我放的目錄跟其他文件的目錄不一樣,所以我在遍歷里單獨(dú)處理,各位小伙伴可自己調(diào)整哈
router 文件夾下的 _import_development.js
module.exports = file => require('@/views' + file + '.vue').default // vue-loader at least v13.0.0+
filterAsyncRouter方法 將后端返回的路由改為加載組件的過(guò)程
const _import = require('./router/_import_' + process.env.NODE_ENV) // 獲取組件的方法
import LayoutPage from '@/views/layout' // Layout 是架構(gòu)組件,不在后臺(tái)返回,在文件里單獨(dú)引入
function filterAsyncRouter(asyncRouterMap) { // 遍歷后臺(tái)傳來(lái)的路由字符串,轉(zhuǎn)換為組件對(duì)象
const accessedRouters = asyncRouterMap.filter(route => {
if (route.component) {
if (route.component === 'Layout') { // Layout組件特殊處理
route.component = LayoutPage
} else {
route.component = _import(route.component)
}
}
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children)
}
return true
})
return accessedRouters
}
3.使用beforeEach、addRoutes、localStorage來(lái)配合實(shí)現(xiàn)拿到數(shù)據(jù)再轉(zhuǎn)換成路由
beforeEach路由攔截,進(jìn)入判斷,如果發(fā)現(xiàn)本地沒(méi)有路由數(shù)據(jù),那就利用axios后臺(tái)取一次,取完以后,利用localStorage存儲(chǔ)起來(lái),利用addRoutes動(dòng)態(tài)添加路由,
ps:beforeEach好壞啊,一步小心就進(jìn)入到了他的死循環(huán),瀏覽器都tm崩了,得在一開(kāi)始就加判斷,拿到路由了,就直接next(),嚶嚶嚶
global.antRouter是為了傳遞數(shù)據(jù)給左側(cè)菜單組件進(jìn)行渲染
import axios from 'axios'
var getRouter // 用來(lái)獲取后臺(tái)拿到的路由
router.beforeEach((to, from, next) => {
if (!getRouter) { // 不加這個(gè)判斷,路由會(huì)陷入死循環(huán) (如果沒(méi)有路由)
if (!getObjArr('router')) { // 緩存里沒(méi)有路由,axios重新獲取
axios.get('https://www.easy-mock.com/mock/5a5da330d9b48c260cb42ca8/example/antrouter').then(res => {
getRouter = res.data.data.router//后臺(tái)拿到路由
saveObjArr('router', getRouter) //存儲(chǔ)路由到localStorage
routerGo(to, next) // 添加路由 執(zhí)行路由跳轉(zhuǎn)方法
})
} else { // 從localStorage拿到了路由
getRouter = getObjArr('router')//拿到路由
routerGo(to, next) // // 添加路由 執(zhí)行路由跳轉(zhuǎn)方法
}
} else {
next()
}
})
// routerGo
function routerGo(to, next) {
// 這里后端返回的數(shù)據(jù)getRouter 可能不是我們需要的結(jié)構(gòu),那么需要先有一步轉(zhuǎn)換的步驟
// 直到轉(zhuǎn)換成前面第一步那種vue標(biāo)準(zhǔn)路由表的格式
getRouter = filterAsyncRouter(getRouter) // 過(guò)濾路由 轉(zhuǎn)換路由的component
// filterAsyncRoute方法是把后端返回的路由數(shù)據(jù) 轉(zhuǎn)換為前端要的路由結(jié)構(gòu)
getRouter.push({ path: '*', redirect: '/404', hidden: true });
router.addRoutes(getRouter) //動(dòng)態(tài)添加路由
global.antRouter = getRouter //將路由數(shù)據(jù)傳遞給全局變量,做側(cè)邊欄菜單渲染工作
next({ ...to, replace: true })
}
function saveObjArr(name, data) { //localStorage 存儲(chǔ)數(shù)組對(duì)象的方法
localStorage.setItem(name, JSON.stringify(data))
}
function getObjArr(name) { //localStorage 獲取數(shù)組對(duì)象的方法
return JSON.parse(window.localStorage.getItem(name));
}
4.拿到遍歷好的路由,進(jìn)行左側(cè)菜單渲染
上邊第三步會(huì)給 global.antRouter賦值,這是一個(gè)全局變量(可以用vuex替代),菜單那邊拿到路由,進(jìn)行渲染