vue項目實現(xiàn)動態(tài)路由的方式大體可分為兩種:
1.簡單的角色路由設(shè)置:比如只涉及到管理員和普通用戶的權(quán)限像屋。通常直接在前端進(jìn)行簡單的角色權(quán)限設(shè)置
前端這邊把路由寫好餐胀,登錄的時候根據(jù)用戶的角色權(quán)限來動態(tài)展示路由授艰,(前端控制路由)
詳情可參閱花褲衩大佬的項目手把手...
2.復(fù)雜的路由權(quán)限設(shè)置:比如OA系統(tǒng)胶哲、多種角色的權(quán)限配置体啰。通常需要后端返回路由列表,前端渲染使用
后臺傳來當(dāng)前用戶對應(yīng)權(quán)限的路由表卦方,前端通過調(diào)接口拿到后處理(后端處理路由)
這兩種方法各有優(yōu)點羊瘩,效果都能實現(xiàn)泰佳,我們公司是通過第二中種方法實現(xiàn)的盼砍,原因就是公司項目里有一個專門的用戶中心,里邊邏輯很復(fù)雜逝她,不好返給前端用戶權(quán)限浇坐,擔(dān)心路由放到前端不安全(以上的話是公司的后臺同學(xué)講的),那好吧黔宛,抱著都試試近刘、鍛煉下自己能力的態(tài)度,我們搞了第二種方法臀晃。
Vue 動態(tài)路由的實現(xiàn)(后臺傳遞路由觉渴,前端拿到并生成側(cè)邊欄)
思路整理
大體步驟:攔截路由->后臺取到路由->保存路由到localStorage(用戶登錄進(jìn)來只會從后臺取一次,其余都從本地取,所以用戶徽惋,只有退出在登錄路由才會更新)
后端管理創(chuàng)建菜單需要
菜單名字menName案淋;
菜單路徑menPath;
菜單指向的資源menuUrl(也就是組件地址, 一般從views層級開始寫)
前端登錄后通過接口請求拿到菜單數(shù)據(jù)后险绘,
將菜單數(shù)據(jù)轉(zhuǎn)換格式 轉(zhuǎn)成前端需要的路由表
menName ---> name
menPath ---> path
menuUrl ---->components文件
轉(zhuǎn)換時,用到這個方法找組件資源 把 menuUrl 可以變?yōu)閏omponents的格式踢京,轉(zhuǎn)為組件文件
module.exports = file => require('@/views' + file + '.vue').default // vue-loader at least v13.0.0+
生成路由表
可以再過濾一遍生成的路由表
下面這個方法找到views底下所有的組件資源
require.context('@/views', true, /\.vue$/).keys().forEach(v => map.set(v.replace(/^\.(.*)\.vue$/,'$1'), true))
export default map
路由表里路由的組件在所有組件資源里沒找到時,將該路由的path變?yōu)?404
過濾后宦棺,再動態(tài)添加
getRouter.push({ path: '*', redirect: '/404', hidden: true });
router.addRoutes(getRouter); //動態(tài)添加路由
代碼
前置工作:配置項目路由文件瓣距,該文件中沒有路由,或者存在一部分公共路由代咸,即沒有權(quán)限的路由
import Vue from 'vue'
import Router from 'vue-router'
import Layout from '@/layout';
Vue.use(Router)
// 配置項目中沒有涉及權(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.登錄后通過接口拿到后端返回的路由數(shù)據(jù)
每個路由都使用到組件Layout,這個組件是整體的頁面布局:左側(cè)菜單列蹈丸,右側(cè)頁面,所以children下邊的第一級路由就是你自己的開發(fā)的頁面,meta里包含著路由的名字,以及路由對應(yīng)的icon;
因為可能會有多級菜單呐芥,所以會出現(xiàn)children下邊嵌套children的情況;
路由是數(shù)組格式
后端返回的路由格式
"data": {
"router": [
{
"path": "",
"component": "Layout",
"redirect": "dashboard",
"children": [
{
"path": "dashboard",
"component": "dashboard/index",
"meta": {
"title": "首頁",
"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": "樹形菜單",
"icon": "tree"
}
}
]
},
{
"path": "/form",
"component": "Layout",
"children": [
{
"path": "index",
"name": "Form",
"component": "form/index",
"meta": {
"title": "表單",
"icon": "form"
}
}
]
},
{
"path": "*",
"redirect": "/404",
"hidden": true
}
]
}
實際前端需要的 component是 component: () => import('@/views/content/classify'),
2.將后端傳回的"component": "Layout", 轉(zhuǎn)為 "component": Layout組件對象
因為有多級路由的出現(xiàn)白华,所以要寫成遍歷遞歸方法,確保把每個component轉(zhuǎn)成組件對象
因為后臺傳回的是字符串贩耐,所以要把加載組件的過程 封裝成一個方法弧腥,用這個方法在遍歷中使用;詳情查看項目里的router文件夾下的 _import_development.js和_import_production.js文件
Layout我放的目錄跟其他文件的目錄不一樣,所以我在遍歷里單獨處理潮太,各位小伙伴可自己調(diào)整哈
router 文件夾下的 _import_development.js
module.exports = file => require('@/views' + file + '.vue').default // vue-loader at least v13.0.0+
filterAsyncRouter方法 將后端返回的路由改為加載組件的過程
const _import = require('./router/_import_' + process.env.NODE_ENV) // 獲取組件的方法
import LayoutPage from '@/views/layout' // Layout 是架構(gòu)組件管搪,不在后臺返回虾攻,在文件里單獨引入
function filterAsyncRouter(asyncRouterMap) { // 遍歷后臺傳來的路由字符串,轉(zhuǎn)換為組件對象
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來配合實現(xiàn)拿到數(shù)據(jù)再轉(zhuǎn)換成路由
beforeEach路由攔截,進(jìn)入判斷澡为,如果發(fā)現(xiàn)本地沒有路由數(shù)據(jù)漂坏,那就利用axios后臺取一次,取完以后媒至,利用localStorage存儲起來顶别,利用addRoutes動態(tài)添加路由,
ps:beforeEach好壞啊拒啰,一步小心就進(jìn)入到了他的死循環(huán)驯绎,瀏覽器都tm崩了,得在一開始就加判斷谋旦,拿到路由了剩失,就直接next(),嚶嚶嚶
global.antRouter是為了傳遞數(shù)據(jù)給左側(cè)菜單組件進(jìn)行渲染
import axios from 'axios'
var getRouter // 用來獲取后臺拿到的路由
router.beforeEach((to, from, next) => {
if (!getRouter) { // 不加這個判斷册着,路由會陷入死循環(huán) (如果沒有路由)
if (!getObjArr('router')) { // 緩存里沒有路由拴孤,axios重新獲取
axios.get('https://www.easy-mock.com/mock/5a5da330d9b48c260cb42ca8/example/antrouter').then(res => {
getRouter = res.data.data.router//后臺拿到路由
saveObjArr('router', getRouter) //存儲路由到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) // 過濾路由 轉(zhuǎn)換路由的component
// filterAsyncRoute方法是把后端返回的路由數(shù)據(jù) 轉(zhuǎn)換為前端要的路由結(jié)構(gòu)
getRouter.push({ path: '*', redirect: '/404', hidden: true });
router.addRoutes(getRouter) //動態(tài)添加路由
global.antRouter = getRouter //將路由數(shù)據(jù)傳遞給全局變量甲捏,做側(cè)邊欄菜單渲染工作
next({ ...to, replace: true })
}
function saveObjArr(name, data) { //localStorage 存儲數(shù)組對象的方法
localStorage.setItem(name, JSON.stringify(data))
}
function getObjArr(name) { //localStorage 獲取數(shù)組對象的方法
return JSON.parse(window.localStorage.getItem(name));
}
4.拿到遍歷好的路由演熟,進(jìn)行左側(cè)菜單渲染
上邊第三步會給 global.antRouter賦值,這是一個全局變量(可以用vuex替代)摊鸡,菜單那邊拿到路由绽媒,進(jìn)行渲染