vue动态菜单最全代码
这段时间公司用vue+elementUI做了个后台管理系统,管理系统都会有角色菜单权限管理,所以研究了一下动态菜单的加载,遇到了很多坑和大家分享一下。
耐心跟着步骤往下看,希望能帮助到你
element-admin的登录逻辑
1、先看登录方法里写什么:
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true;
//调用user模块红的login
console.log("点击登陆按钮")
this.$store.dispatch('user/login', this.loginForm).then(() => {
console.log("登录成功");
this.$router.push({ path: this.redirect || '/' });
this.loading = false;
}).catch(() => {
this.loading = false;
})
} else {
console.log('error submit!!');
return false;
}
})
}
点击过登录按钮后,调用了$store里的一个方法,名叫login 。
this.$store.dispatch(‘user/login’, this.loginForm)
2、下面来看看$store里的这个login方法:
import { login, logout, getInfo,self} from '@/api/user'
const actions = {
// user login
login({ commit }, userInfo) {
const { username, password } = userInfo;
return new Promise((resolve, reject) => {
console.log("vuex中的请求")
login({ username: username.trim(), password: password }).then(response => {
console.log('vuex中')
console.log(response);
const { data } = response;
commit('SET_TOKEN', data.token);//存在vueX中
setToken(data.token);//存在cookie中
resolve();
}).catch(error => {
console.log(error);
reject(error);
})
})
},
怎么两个login,第一个login是$store中的方法,第二个login方法是,api里的login方法,用来调用接口的
3、再来看看api中的login方法:
import request from '@/utils/request'
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
}
上面是api中的login方法,它调用了request.js,request.js是封装了axios,是用来请求后台接口的,如果这个接口请求成功了,就回到了第一步中的.then()方法中
代码是,路由跳转到首页,进入正式页面!!!
重点来了!!!
之所以是称之为权限,也就是必须满足一定的条件才能够访问到正常页面,那么如果不满足呢?如果我没有token,让不让我进正常页面呢??
肯定是不让的,那我没有token,该去哪?答案是还待在登录页,哪都去不了,那么这些处理应该在哪写呢?答案是,permission.js模块
这个js在main.js引入,其实就是个路由拦截器:来看看它的代码:
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // 是否有转圈效果
const whiteList = ['/login', '/auth-redirect'] // 没有重定向白名单
router.beforeEach(async(to, from, next) => {
// 开始进度条
NProgress.start()
// 设置页面标题
document.title = getPageTitle(to.meta.title)
// 确定用户是否已登录
const hasToken = getToken()
// console.log('to', to)
// console.log('from', from)
// console.log('next', next)
// console.log("hasToken",hasToken)
if (hasToken) {
if (to.path === '/login') {
// 如果已登录,则重定向到主页
next({ path: '/' })
NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
} else {
// determine whether the user has obtained his permission roles through getInfo
const hasRoles = store.getters.roles && store.getters.roles.length > 0
// console.log('hasRoles',hasRoles);
if (hasRoles) {
next()
} else {
try {
// 获得用户信息
const { roles } = await store.dispatch('user/getInfo')
// generate accessible routes map based on roles
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
console.log('accessRoutes', accessRoutes)
// dynamically add accessible routes
router.addRoutes(accessRoutes)
// router.options.routes=store.getters.routers;
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// 在免费登录白名单,直接去
next()
} else {
// 没有访问权限的其他页面被重定向到登录页面。
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => {
// 完成进度条
NProgress.done()
})
从上面代码看,每次路由跳转,都要从cookie中取token,
那么可以分两种情况,有token和无token
有token:再看看是不是去登录页的,登录页肯定不能拦截的,如果是登录页就直接放行。如果不是登录页,就要看看本地有没有用户信息,看看cookie中有没有用户信息(不一定是token,也可能是localstorage)。如果有用户信息,放行。如果没有用户信息,就调用接口去获取登录信息,然后后面还有一点代码,涉及到了动态添加路由(这里先说到这,后面具体说动态添加权限路由的事)。获取到用户信息后放行。如果在获取用户信息的过程中报错,则回到登录页
无token:先看看用户要进入的页面是不是在白名单内,一般登录、注册、忘记密码都是在白名单内的,这些页面,在无token的情况下也是直接放行。如果不在白名单内,滚回登录页。
以上就是element-admin的登录逻辑了。
下面来说一下,element-admin的动态权限路由,显示侧边栏是什么逻辑
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
/* Router Modules */
// import componentsRouter from './modules/components'
// import chartsRouter from './modules/charts'
/* import tableRouter from './modules/table'*/
// import goodsInfo from './modules/goods_info'
// import rptManage from './modules/rpt_manage'
import paramnter from './modules/parameter'
import accountsystem from './modules/account_system'
import busmanage from './modules/bus_manage'
/**
*
* 没有权限要求的基页
* 可以访问所有角色
*/
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/auth-redirect',
component: () => import('@/views/login/auth-redirect'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/error-page/404'),
hidden: true
},
{
path: '/401',
component: () => import('@/views/error-page/401'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
name: 'Dashboard',
meta: { title: '班车预约', icon: 'dashboard', affix: true }
}
]
},
]
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher
}
export const asyncRoutes = [
]
export default router
注意以上代码中(constantRoutes 和 asyncRoutes),这个routes中分两块路由配置,
constantRoutes:是固定的,无权限的路由配置,也就是不管是管理员身份,还是超级管理员身份,都会显示的路由配置。
asyncRoutes:是带权限的路由配置,动态路由会在store/modules下面的permission.js中获取赋值。
注意: 固定路由配置中千万不要加:{ path: ‘*’, redirect: ‘/404’, hidden: true }, 否则刷新动态路由的时候会跳转404页面
这就用到登录时的拦截器了,上面遇到过,就在哪执行,来看看那里都是写了一些什么代码:
拿到这看看:
// 获得用户信息
const { roles } = await store.dispatch('user/getInfo')
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
router.addRoutes(accessRoutes)
next({ ...to, replace: true })
roles其实就是上面的两个权限组成的数组,然后传入了GenerateRoutes方法内
接下来比较关键了,获取动态菜单的js
import { asyncRoutes, constantRoutes } from '@/router'
import { getUserMenu} from '@/api/paramnter/admininfolist'
import { get} from '@/api/user'
import Layout from '@/layout'
/**
* Use meta.role to determine if the current user has permission
* @param roles
* @param route
*/
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
/**
* Filter asynchronous routing tables by recursion
* @param routes asyncRoutes
* @param roles
*/
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
async generateRoutes({ commit }, roles) {
// 获取动态菜单
let res = await get({
url:"/sys/role/getManageMenuList"
})
return new Promise(resolve => {
// 把查询出的菜单变成 路由样式
let roledata = res.data;
let rs = []
roledata.forEach(item => {
const menu = {
path: '/'+item.imgurl,
component: Layout,
name: item.imgurl,
meta: { title: item.menuname },
children: []
}
item.children.forEach(chld => {
var view = chld.menuurl;
const ren = {
path: chld.imgurl,
// 路径必须有至少一层静态的, 不然访问不到页面。 正确写法 require(['@/views/datalook' + view], resolve),错误写法 require(['@' + view], resolve)
component: (resolve) => require(['@/views/datalook' + view], resolve),
name: chld.imgurl,
meta: { title: chld.menuname },
children: []
}
menu.children.push(ren);
})
rs.push(menu)
})
// 解决刷新页面跳转404的问题
const error404 = { path: '*', redirect: '/404', hidden: true };
rs.push(error404);
// console.log('rs', rs)
const accessedRoutes = filterAsyncRoutes(asyncRoutes.concat(rs), roles)
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
首先调用后端接口获取动态树形菜单,然后把动态树形菜单变成路由样式菜单。 循环遍历之后,一定要把 { path: ‘*’, redirect: ‘/404’, hidden: true }; 追加到路由菜单后面,不然点击菜单后刷新页面404。
GenerateRoutes方法办了一件事,就是把动态路由存到了vuex中,然后调用了这一句代码:
router.addRoutes(accessRoutes)
// router.options.routes=store.getters.routers;
router.addRoutes()方法是,动态添加路由配置,参数是符合路由配置的数组,然后将路由元信息,变成合并后的路由元信息,因为渲染侧边栏需要用到。
如果侧边栏显示不出来动态添加的路由,就把上面注释的这句代码加上router.options.routes=store.getters.routers;
上面用到的 menuurl、imgurl 仅供参考,具体根据后端返回的菜单取值
下面是我们后端返回的树形菜单格式:
[{
"id": 1,
"moduleid": 1,
"parentid": 0,
"plevel": "1",
"menuname": "父级001",
"menuurl": null,
"imgurl": "parameter",
"status": "1",
"organcode": "0",
"remark": null,
"subcrip": "1",
"sort": 1,
"children": [{
"id": 103,
"moduleid": 1,
"parentid": 1,
"plevel": "0",
"menuname": "子集001_01",
"menuurl": "/paramnter/sysmodelist",
"imgurl": "sysmodelist",
"status": "1",
"organcode": "0001",
"remark": "1",
"subcrip": "1",
"sort": 6,
"children": null
}, {
"id": 104,
"moduleid": 1,
"parentid": 1,
"plevel": "0",
"menuname": "子集001_02",
"menuurl": "/paramnter/querysysroledefine",
"imgurl": "querysysroledefine",
"status": "1",
"organcode": "0001",
"remark": "1",
"subcrip": "1",
"sort": 7,
"children": null
}]
}, {
"id": 2,
"moduleid": 1,
"parentid": 0,
"plevel": "1",
"menuname": "父级002",
"menuurl": null,
"imgurl": "accountsystem",
"status": "1",
"organcode": "0",
"remark": null,
"subcrip": "1",
"sort": 2,
"children": [{
"id": 105,
"moduleid": 1,
"parentid": 2,
"plevel": "0",
"menuname": "子集002_01",
"menuurl": "/paramnter/admininfolist",
"imgurl": "admininfolist",
"status": "1",
"organcode": "0001",
"remark": "1",
"subcrip": "1",
"sort": 8,
"children": null
}, {
"id": 106,
"moduleid": 1,
"parentid": 2,
"plevel": "0",
"menuname": "子集002_02",
"menuurl": "/paramnter/openMerchantManage",
"imgurl": "openMerchantManage",
"status": "1",
"organcode": "0001",
"remark": "1",
"subcrip": "1",
"sort": 9,
"children": null
}, {
"id": 107,
"moduleid": 1,
"parentid": 2,
"plevel": "0",
"menuname": "子集002_03",
"menuurl": "/paramnter/syncMerchant",
"imgurl": "syncMerchant",
"status": "1",
"organcode": "0001",
"remark": "1",
"subcrip": "1",
"sort": 10,
"children": null
}]
}, {
"id": 3,
"moduleid": 1,
"parentid": 0,
"plevel": "1",
"menuname": "父级003",
"menuurl": null,
"imgurl": "busmanage",
"status": "1",
"organcode": "0",
"remark": null,
"subcrip": "1",
"sort": 3,
"children": [{
"id": 108,
"moduleid": 1,
"parentid": 3,
"plevel": "0",
"menuname": "子集003_01",
"menuurl": "/route_manage/route_create/index",
"imgurl": "route_create",
"status": "1",
"organcode": "0001",
"remark": "1",
"subcrip": "1",
"sort": 11,
"children": null
}, {
"id": 109,
"moduleid": 1,
"parentid": 3,
"plevel": "0",
"menuname": "子集003_02",
"menuurl": "/route_manage/route_release/index",
"imgurl": "route_release",
"status": "1",
"organcode": "0001",
"remark": "1",
"subcrip": "1",
"sort": 12,
"children": null
}]
}]
以上就是element-admin的动态路由了,搞完王者峡谷集合了!!!