vue項目實現動態路由有倆種方式html
一.前端在routers中寫好--全部--路由表 <前端控制路由>,登陸時根據用戶的角色權限來動態的顯示菜單路由前端
二.前端經過調用接口請求拿到當前用戶--對應權限的--路由表 <後端處理路由返回>,以動態的顯示菜單路由 vue
介紹第二種 (參考資料 segmentfault-大師兄)node
左側菜單可經過 ①本地mock假數據 ②easymock假數據 ③從後臺請求返回的數據 方式之一請求而來ios
介紹方式①本地mock假數據git
1.iview-admin的src->mock->data目錄下新增菜單路由數據menus-data.js (字段可參照src->router->routers.js中設置)github
menus-data.jsjson
export const mockMenuData = [ { 'path': '/multilevel', 'name': 'multilevel', 'meta': { 'icon': 'md-menu', 'title': '多級菜單' }, 'component': 'Main', 'children': [ { 'path': '/level_2_1', 'name': 'level_2_1', 'meta': { 'icon': 'md-funnel', 'title': '二級-1' }, 'component': 'multilevel/level-2-1' }, { 'path': '/level_2_2', 'name': 'level_2_2', 'meta': { 'icon': 'md-funnel', 'showAlways': true, 'title': '二級-2' }, 'component': 'parentView', 'children': [ { 'path': '/level_2_2_1', 'name': 'level_2_2_1', 'meta': { 'icon': 'md-funnel', 'title': '三級' }, 'component': 'multilevel/level-2-2/level-2-2-1' }, { 'path': '/level_2_2_2', 'name': 'level_2_2_2', 'meta': { 'icon': 'md-funnel', 'title': '三級' }, 'component': 'multilevel/level-2-2/level-2-2-2' } ] }, { 'path': '/level_2_3', 'name': 'level_2_3', 'meta': { 'icon': 'md-funnel', 'title': '二級-3' }, 'component': 'multilevel/level-2-3' } ] }, { 'path': '/auth', 'name': 'auth', 'meta': { 'icon': 'md-menu', 'title': '權限設置', 'access': ['super_admin'] }, 'component': 'Main', 'children': [ { 'path': '/role', 'name': 'role', 'meta': { 'icon': 'ios-paper-outline', 'title': '角色' }, 'component': 'auth/role', 'permission': ['add', 'edit'] }, { 'path': '/cmenu', 'name': 'cmenu', 'meta': { 'icon': 'ios-paper-outline', 'title': '菜單' }, 'component': 'auth/cmenu', 'permission': ['add', 'del'] }, { 'path': '/account', 'name': 'account', 'meta': { 'icon': 'ios-paper-outline', 'title': '帳號' }, 'component': 'auth/account' } ] }, { 'path': '/lesmessage', 'name': 'lesmessage', 'meta': { 'icon': 'ios-paper', 'title': '留言管理' }, 'component': 'Main', 'children': [ { 'path': '/list', 'name': 'list', 'meta': { 'icon': 'ios-paper', 'title': '數據列表' }, 'component': 'lesmessage/list' } ] } ]
2/3/4步驟官方文檔axios
2.src->api->data.js中添加menus-data接口定義(參照iview-admin原有的mock數據接口封裝寫法)segmentfault
export const getMockMenuData = () => { return axios.request({ url: 'get_mock_menu_data', method: 'post' }) }
3.src->mock->data.js中添加menus-data請求函數(參照iview-admin原有的mock數據請求函數封裝寫法)
export const getMockMenuData = req => { return mockMenuData }
4.src->mock->index.js中添加menus-data的Mock導出封裝
import { ... , getMockMenuData } from './data'
.
.
.
Mock.mock(/\/get_mock_menu_data/, getMockMenuData)
以上,mock數據定義好了請求接口get_mock_menu_data
5.src->libs目錄下新增router-util.js , 用於轉化請求的menus-data數據爲能被vue識別的路由格式
router-util.js
/** * ①添 * @@新增 定義初始化菜單 */ import store from '@/store' import { getToken, localSave, localRead } from '@/libs/util' // import config from '@/config' import { lazyLoadingCop } from '@/libs/tools' import { getMockMenuData } from '@/api/data' import Main from '@/components/main' // Main 是架構組件,不在後臺返回,在文件裏單獨引入 import parentView from '@/components/parent-view' // parentView 是二級架構組件,不在後臺返回,在文件裏單獨引入 const _import = require('@/router/_import_' + process.env.NODE_ENV)// 獲取組件的方法 var gotRouter // 初始化路由 export const initRouter = (vm) => { if (!getToken()) { return } var routerData console.log(gotRouter,!gotRouter, vm,store, 'initRouter') if (!gotRouter) { getMockMenuData().then(res => { routerData = res.data // 後臺拿到路由 localSave('dynamicRouter', JSON.stringify(routerData)) // 存儲路由到localStorage gotRouter = filterAsyncRouter(routerData) // 過濾路由,路由組件轉換 console.log(gotRouter, 'filterAsyncRouter') store.commit('updateMenuList', gotRouter); dynamicRouterAdd() }) } else { gotRouter = dynamicRouterAdd() } return gotRouter } // 加載路由菜單,從localStorage拿到路由,在建立路由時使用 export const dynamicRouterAdd = () => { let dynamicRouter = [] let data = localRead('dynamicRouter') if (!data) { return dynamicRouter } dynamicRouter = filterAsyncRouter(JSON.parse(data)) return dynamicRouter } // @函數: 遍歷後臺傳來的路由字符串,轉換爲組件對象 export const filterAsyncRouter = (asyncRouterMap) => { const accessedRouters = asyncRouterMap.filter(route => { if (route.component) { if (route.component === 'Main') { // Main組件特殊處理 route.component = Main } else if (route.component === 'parentView') { // parentView組件特殊處理 route.component = parentView } else { // route.component = _import(route.component) route.component = lazyLoadingCop(route.component) } } if (route.children && route.children.length) { route.children = filterAsyncRouter(route.children) } return true }) return accessedRouters }
附: src->libs->toos.js中添加 引入.vue組件的封裝函數 (不分環境可用)
// @函數: 引入組件 export const lazyLoadingCop = file => require('@/view/' + file + '.vue').default
附:src-->router中新增_import_development.js和_import_production.js爲引入.vue組件的封裝 (倆種環境)
_import_development.js
module.default = file => require('@/view/' + file + '.vue').default // vue-loader at least v13.0.0+
_import_production.js
module.exports = file => () => import('@/view/' + file + '.vue')
vux部分updateMenuList更新菜單數據
附:src->store->module->app.js中的mutations添加 updateMenuList 操做 state中的 menuList:[] (添加) 、 getters中menuList修改的獲取方式
updateMenuList (state, routes) { // ①添 接受前臺數組,刷新菜單 router.addRoutes(routes); // 動態添加路由 state.menuList = routes; console.log('①updateMenuList添menuList', this); }
getters: { // menuList: (state, getters, rootState) => getMenuByRouter(routers, rootState.user.access), // menuList: (state, getters, rootState) => getMenuByRouter(dynamicRouterAdd(), rootState.user.access), // ①改 經過路由列表獲得菜單列表 menuList: (state, getters, rootState) => getMenuByRouter(state.menuList, rootState.user.access), // ①改 經過路由列表獲得菜單列表 ... },
src->router->routers.js 主要是左側菜單的加入
import Main from '@/components/main' import { dynamicRouterAdd } from '@/libs/router-util' // ①添 引入加載菜單 /** * iview-admin中meta除了原生參數外可配置的參數: * meta: { * title: { String|Number|Function } * 顯示在側邊欄、麪包屑和標籤欄的文字 * 使用'{{ 多語言字段 }}'形式結合多語言使用,例子看多語言的路由配置; * 能夠傳入一個回調函數,參數是當前路由對象,例子看動態路由和帶參路由 * hideInBread: (false) 設爲true後此級路由將不會出如今麪包屑中,示例看QQ羣路由配置 * hideInMenu: (false) 設爲true後在左側菜單不會顯示該頁面選項 * notCache: (false) 設爲true後頁面在切換標籤後不會緩存,若是須要緩存,無需設置這個字段,並且須要設置頁面組件name屬性和路由配置的name一致 * access: (null) 可訪問該頁面的權限數組,當前路由設置的權限會影響子路由 * icon: (-) 該頁面在左側菜單、麪包屑和標籤導航處顯示的圖標,若是是自定義圖標,須要在圖標名稱前加下劃線'_' * beforeCloseName: (-) 設置該字段,則在關閉當前tab頁時會去'@/router/before-close.js'裏尋找該字段名對應的方法,做爲關閉前的鉤子函數 * } */ // 不做爲Main組件的子頁面展現的頁面單獨寫 export const otherRouter = [{ path: '/login', name: 'login', meta: { title: 'Login - 登陸', hideInMenu: true }, component: () => import('@/view/login/login.vue') }, { path: '/401', name: 'error_401', meta: { hideInMenu: true }, component: () => import('@/view/error-page/401.vue') }, { path: '/500', meta: { title: '500-服務端錯誤' }, name: 'error_500', component: () => import('@/view/error-page/500.vue') } ]; // 做爲Main組件的子頁面展現可是不在左側菜單顯示的路由寫在mainRouter裏 export const mainRouter = [{ path: '/', name: '_home', redirect: '/home', component: Main, meta: { hideInMenu: true, notCache: true }, children: [ { path: '/home', name: 'home', meta: { hideInMenu: true, title: '首頁', notCache: true, icon: 'md-home' }, component: () => import('@/view/single-page/home') } ] }, { path: '/message', name: 'message', component: Main, meta: { hideInBread: true, hideInMenu: true }, children: [ { path: 'message_page', name: 'message_page', meta: { icon: 'md-notifications', title: '消息中心' }, component: () => import('@/view/single-page/message/index.vue') } ] }]; // 做爲Main組件的子頁面展現而且在左側菜單顯示的路由寫在appRouter裏 export const appRouter = [...dynamicRouterAdd()]; export const routes = [ ...otherRouter, ...mainRouter, ...appRouter ] // 全部上面定義的路由都要寫在下面輸出 export default routes
src->main.js 實例化對象 添加掛載時的動態路由調用
. . . import { initRouter } from '@/libs/router-util' // ①新增 引入動態菜單渲染 . . . new Vue({ . . . mounted() { initRouter(this); // ①新增 調用方法,動態生成路由 }, . })
登陸菜單未渲染,可在路由跳轉前執行一次initRouter (此方案捨去) src->router->routers.js
import { initRouter } from '@/libs/router-util' . . . const turnTo = (to, access, next) => { initRouter(); ... };
登陸菜單未渲染,可在路由跳轉前執行一次initRouter(此方案體驗更佳,解決附加6的問題) src->router->routers.js
import { InitRouter } from '@/libs/router-util' ... router.beforeEach((to, from, next) => { . if (!token && to.name !== LOGIN_PAGE_NAME) { // 未登陸且要跳轉的頁面不是登陸頁 . } else if (!token && to.name === LOGIN_PAGE_NAME) { // 未登錄且要跳轉的頁面是登陸頁 InitRouter() // 登陸頁刷新從新獲取,確保路由跳轉前即beforeEach獲取到動態路由表 next() // 跳轉 } else if (token && to.name === LOGIN_PAGE_NAME) { // 已登陸且要跳轉的頁面是登陸頁 . } else { . } })
以上。動態載入請求本地mock菜單路由結束。
介紹方式 ②easymock假數據
1.使用easy-mock,建立項目,複製項目Base URL
2.配置proxy (在vue.config.js中)
devServer: { proxy: { '/mock': { // easymock跨域請求配置 target: 'easy-mock項目的Base URL', changeOrigin: true, pathRewrite: {'^/mock': '/'} } } }
3.配置package.json腳本
"easy-mock": "node build/dev-server.js mock",
4.配置api請求基礎路徑baseUrl(在src->config->index.js中)
/** * @description api請求基礎路徑 */ baseUrl: { dev: '步驟2中的easymock項目地址Base URL', pro: 'https://produce.com' },
以後就是接口封裝和使用了。(可參照iview-admin本來寫mock假數據的封裝寫法及使用)
5.附加 easy-mock工具使用
6.附加 vue 解決addRoutes動態添加路由後,刷新失效問題
備註:這篇文章屬於一邊摸索一邊寫下的,有出現問題會修改一些步驟,基本原理已經表現。
在此文章基礎上加寫一篇,iView-admin2.0動態菜單路由【版2】,解決這篇文章中忽略的/沒講清的點。