主要思路,从后台取到菜单数据,前端根据数据生成路由配置以及菜单。
后台根据前端的用户信息返回对应权限的菜单数据,省的在前端写好路由再配置权限了。
本功能使用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
相关代码打包
谢谢分享
谢谢分享。。
感谢分享
下载代码
感谢分享
想把代码下载下来
谢谢楼主分享
感谢
谢谢楼主分享
想把代码下载下来试一试
谢谢分享
谢谢楼主分享
谢谢楼主分享
应该有帮助
感谢大佬
谢谢分享
大佬 ,如何下载呀, 一直失败呢
没问题啊,你那点get it能弹出新标签打开下载地址吗?
大佬 能给个源码研究一下吗 现在项目做到这个不太会
主要代码都在这里了其实,其他代码只有菜单栏那,只是把菜单数据绑定到菜单栏,其他没什么了,
把vue的路由以及vuex弄清楚,不会有问题的。