主要思路,从后台取到菜单数据,前端根据数据生成路由配置以及菜单。
后台根据前端的用户信息返回对应权限的菜单数据,省的在前端写好路由再配置权限了。
本功能使用vuex进行统一集中式的管理,这里不过多解释。
主要步骤如下:
1.从后台取对应用户权限的菜单数据
2.遍历数据,生成父路由及其子路由组件,添加到路由表中。
3.同时菜单数据也要保存到sessionStorage中,避免每次都从后台取,而且避免URL刷新空白,后面会提到。
我后台返回的菜单数据格式如下

为了添加路由时导入组件方便,我的vue组件目录结构是和菜单URL完全对应的

首先放一下我的路由配置,里面这个constantRouterMap 是固定的路由表,在没有从接口取数据生成路由时也可访问。
import Vue from 'vue'
import Router from 'vue-router'
import Layout from '../views/layout/Layout'
const _import = require('./_import_' + process.env.NODE_ENV)
Vue.use(Router)
export const constantRouterMap = [
{path: '/login', component: _import('admin/user/login'), hidden: true},
{path: '/404', component: _import('404'), hidden: true},
{
path: '/',
component: Layout,
redirect: '/control',
name: '首页',
hidden: true,
children: [{
path: '/control', url: '/control', component: _import('admin/control/index'), name: 'control',
meta: {title: '首页', isTabView: true}
},
{
name: 'viewList',
path: "/admin/viewlist/index/:id",
meta: {title: 'title', isTabView: true},
component: _import("admin/viewlist/index")
}]
},
]
export default new Router({
//mode: 'history', //后端支持可开
scrollBehavior: () => ({y: 0}),
routes: constantRouterMap
})
首先定义一个action用于从接口获取菜单数据,前面说过每次获取菜单数据时都要在sessionStorage中保存一下,避免每次都要从接口请求数据
// 获取菜单
GetMenu(state) {
return new Promise(resolve => {
let storageMenus = sessionStorage.menus
if (!utils.isNullOrEmpty(storageMenus)) {
store.commit("SetMenuAndRouter", JSON.parse(storageMenus))
resolve()
} else {
userApi.getMenu().then(res => {
let menus = res.data
store.commit("SetMenuAndRouter", menus)
resolve()
})
}
})
}
然后还需要一个mutations是用于设置菜单及路由
//设置菜单和路由
SetMenuAndRouter(state, menus) {
//保存用户菜单
state.menus = menus
sessionStorage.menus = JSON.stringify(menus)
//生成菜单URL与标题键值对
store.commit('SetMenuTitle', menus)
//菜单转换为路由
let accessedRouters = []
accessedRouters = menuToRouter(menus)
//执行设置路由的方法
store.commit('SET_ROUTERS', accessedRouters)
router.addRoutes(store.getters.addRouters)
}
接下来是上面代码中调用的这个mutations:SetRouters用于生成路由
SET_ROUTERS: (state, routers) => {
state.addRouters = routers
state.routers = constantRouterMap.concat(routers) //将固定路由和新增路由进行合并, 成为本用户最终的全部路由信息
}
menuToRouter用于将接口返回的数据转换为路由组件,代码如下
export function menuToRouter(menus) {
const accessedRouters = []
for (let i in menus) {
let parentMenu = menus[i]
//特殊情况 viewlist
let parentRoute = {
//父级路由 去掉末尾的index 默认跳转到子路由index
path: parentMenu.Url,
children: [],
redirect: '',
component: Layout,
meta: {title: parentMenu.Name},
name: parentMenu.Id
}
let childRoute = []
for (let j in parentMenu.Children) {
let childMenu = parentMenu.Children[j]
childMenu.Url = childMenu.Url.toLowerCase()
//去掉开头的斜杠
let childModulePath = childMenu.Url.substr(1, childMenu.Url.length).toLowerCase()
let isTabView = utils.isNullOrEmpty(childMenu.IsTab) ? false : childMenu.IsTab.toString() == "1"
//如果是ViewList的URL 不需要设置对应路由了 已经在固定路由中设置过了
if (childMenu.Url.indexOf('viewlist') != -1) {
continue
} else {
childRoute.push({
name: childMenu.Id,
path: childMenu.Url,
meta: {title: childMenu.Name, isTabView: isTabView},
component: _import(childModulePath)
})
}
}
parentRoute.children = childRoute
accessedRouters.push(parentRoute)
}
accessedRouters.push({path: '*', redirect: '/404', url: '/404', hidden: true})
return accessedRouters
}
SetMenuTitle是因为我Tab选项卡那里的需求,所以写了一个这
//保存URL与菜单标题键值对
SetMenuTitle(state, menus) {
var menuTitle = {}
for (var pKey in menus) {
var pMenu = menus[pKey]
menuTitle[pMenu.Url] = pMenu.Name
for (var cKey in pMenu.Children) {
var cMenu = pMenu.Children[cKey]
menuTitle[cMenu.Url.toLowerCase()] = cMenu.Name
}
}
menuTitle['/control'] = '首页'
state.menuTitle = menuTitle
}
还有是注意添加路由是需要加载组件的,路由中的component属性,我这里是根据菜单URL导入对应的组件,主要代码是
const _import = require('../../router/_import_' + process.env.NODE_ENV)
在router文件夹下有两个文件,分别为_import_development.js以及_import_production.js
_import_development.js
module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+
_import_production.js
module.exports = file => () => import('@/views/' + file + '.vue')
还有一个最重要的,使用动态路由时,当你在首页(加载路由数据的页面)以外,刷新一下会直接变空白,这是由于子路由是动态添加的,界面刷新的时候,其实我们路由里面并没有该子路由的配置,在页面刷新时候,在router.beforeEach里面去判断,如果是页面刷新且无子路由配置,就调用获取菜单并生成路由的方法,判断条件==3是因为我的固定路由表里默认是3个
import router, {constantRouterMap} from './router'
import store from './store'
import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css' // Progress 进度条样式
import * as utils from "@/utils"
router.beforeEach((to, from, next) => {
NProgress.start()
if (from.name === null || to.name === null) { //页面刷新
if ((utils.isNullOrEmpty(store.getters.permission_routers) || store.getters.permission_routers.length == 3)) {
store.dispatch("GetMenu").then(() => {
next({...to, replace: true})
})
} else {
next()
}
} else {
if (to.matched.length === 0) { //如果未匹配到路由
from.path ? next({path: from.path}) : next('/') //如果上级也未匹配到路由则跳转主页面,如果上级能匹配到则转上级路由
} else {
next() //如果匹配到正确跳转
}
}
})
最后放下完整代码
import {constantRouterMap} from '@/router/index'
import Layout from '@/views/layout/Layout'
import * as utils from '@/utils'
import store from "../index"
import router from "../../router"
import * as userApi from '@/api/user'
const _import = require('../../router/_import_' + process.env.NODE_ENV)
export function menuToRouter(menus) {
const accessedRouters = []
for (let i in menus) {
let parentMenu = menus[i]
//特殊情况 viewlist
let parentRoute = {
//父级路由 去掉末尾的index 默认跳转到子路由index
path: parentMenu.Url,
children: [],
redirect: '',
component: Layout,
meta: {title: parentMenu.Name},
name: parentMenu.Id
}
let childRoute = []
for (let j in parentMenu.Children) {
let childMenu = parentMenu.Children[j]
childMenu.Url = childMenu.Url.toLowerCase()
//去掉开头的斜杠
let childModulePath = childMenu.Url.substr(1, childMenu.Url.length).toLowerCase()
let isTabView = utils.isNullOrEmpty(childMenu.IsTab) ? false : childMenu.IsTab.toString() == "1"
//如果是ViewList的URL 不需要设置对应路由了 已经在固定路由中设置过了
if (childMenu.Url.indexOf('viewlist') != -1) {
continue
} else {
childRoute.push({
name: childMenu.Id,
path: childMenu.Url,
meta: {title: childMenu.Name, isTabView: isTabView},
component: _import(childModulePath)
})
}
}
parentRoute.children = childRoute
accessedRouters.push(parentRoute)
}
accessedRouters.push({path: '*', redirect: '/404', url: '/404', hidden: true})
return accessedRouters
}
const permission = {
state: {
routers: constantRouterMap, //本用户所有的路由,包括了固定的路由和下面的addRouters
addRouters: [], //本用户的角色赋予的新增的动态路由
menus: [],
menuTitle: {},//菜单URL与标题键值对 主要为Tab标题所使用
},
mutations: {
SetRouters: (state, routers) => {
state.addRouters = routers
state.routers = constantRouterMap.concat(routers) //将固定路由和新增路由进行合并, 成为本用户最终的全部路由信息
},
//设置菜单和路由
SetMenuAndRouter(state, menus) {
//保存用户菜单
state.menus = menus
sessionStorage.menus = JSON.stringify(menus)
//生成菜单URL与标题键值对
store.commit('SetMenuTitle', menus)
//菜单转换为路由
let accessedRouters = []
accessedRouters = menuToRouter(menus)
//执行设置路由的方法
store.commit('SetRouters', accessedRouters)
router.addRoutes(store.getters.addRouters)
},
//保存URL与菜单标题键值对
SetMenuTitle(state, menus) {
var menuTitle = {}
for (var pKey in menus) {
var pMenu = menus[pKey]
menuTitle[pMenu.Url] = pMenu.Name
for (var cKey in pMenu.Children) {
var cMenu = pMenu.Children[cKey]
menuTitle[cMenu.Url.toLowerCase()] = cMenu.Name
}
}
menuTitle['/control'] = '首页'
state.menuTitle = menuTitle
},
},
actions: {
// 获取菜单
GetMenu(state) {
return new Promise(resolve => {
let storageMenus = sessionStorage.menus
if (!utils.isNullOrEmpty(storageMenus)) {
store.commit("SetMenuAndRouter", JSON.parse(storageMenus))
resolve()
} else {
userApi.getMenu().then(res => {
let menus = res.data
store.commit("SetMenuAndRouter", menus)
resolve()
})
}
})
}
}
}
export default permission
还有getter.js中代码
const getters = {
menus: state => state.permission.menus,
menuTitle: state => state.permission.menuTitle,
permission_routers: state => state.permission.routers,
addRouters: state => state.permission.addRouters,
}
export default getters
相关代码打包
Comments | 20 条评论
博主 谢谢分享
谢谢分享
博主 LH
谢谢分享。。
博主 abc
感谢分享
博主 keevens
下载代码
博主 df
感谢分享
博主 HM
想把代码下载下来
博主 HM
谢谢楼主分享
博主 杨超越的小可爱
感谢
博主 zy
谢谢楼主分享
博主 zy
想把代码下载下来试一试
博主 叶落
谢谢分享
博主 zzz
谢谢楼主分享
博主 alan
谢谢楼主分享
博主 undefined
应该有帮助
博主 爷新
感谢大佬
博主 473646743@qq.com
谢谢分享
博主 娜娜
大佬 ,如何下载呀, 一直失败呢
博主 loneking
@娜娜 没问题啊,你那点get it能弹出新标签打开下载地址吗?
博主 影子
大佬 能给个源码研究一下吗 现在项目做到这个不太会
博主 loneking
@影子 主要代码都在这里了其实,其他代码只有菜单栏那,只是把菜单数据绑定到菜单栏,其他没什么了,
把vue的路由以及vuex弄清楚,不会有问题的。