【vue】iView-admin2.0动态菜单路由【版1】
vue项目实现动态路由有俩种方式
一.前端在routers中写好--所有--路由表 <前端控制路由>,登录时根据用户的角色权限来动态的显示菜单路由
二.前端通过调用接口请求拿到当前用户--对应权限的--路由表 <后端处理路由返回>,以动态的显示菜单路由
介绍第二种 (参考资料 segmentfault-大师兄)
左侧菜单可通过 ①本地mock假数据 ②easymock假数据 ③从后台请求返回的数据 方式之一请求而来
介绍方式①本地mock假数据
1.iview-admin的src->mock->data目录下新增菜单路由数据menus-data.js (字段可参照src->router->routers.js中设置)
menus-data.js
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.src->api->data.js中添加menus-data接口定义(参照iview-admin原有的mock数据接口封装写法)
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】,解决这篇文章中忽略的/没讲清的点。