Vue实现菜单权限控制
vue-element-admin后端控制菜单的一种实践
·
我们使用vue-element-admin前端框架开发后台管理系统时,一般都会涉及到菜单的权限控制问题。这里介绍后端控制菜单的一种实践。前端每次刷新时根据用户角色,获取后台的菜单数据,添加到动态路由中,格式化成菜单数据,最终实现如下面:
以下是实现的代码:
1、route内index.js,配置好静态路由,声明动态路由,保留404组件
import Vue from'vue'
import Router from'vue-router'
Vue.use(Router)
import Layout from'@/layout'
/* 静态路由 */
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: '/401',
component: () => import('@/views/error-page/401'),
hidden: true
},
{
path: '',
component: Layout,
redirect: 'dashboard',
isShow: true,
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
name: 'Dashboard',
meta: { title: 'dashboard', icon: 'home', noCache: true, affix: true }
}
]
}
]
/* 动态路由 */
export const asyncRoutes = [
{
path: "*",
component: () => import('@/views/error-page/404'),
}
]
const createRouter = () => new Router({
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher
}
export default router
2、修改permission.js,使用钩子函数对路由进行权限跳转
import router from'./router'
import store from'./store'
import NProgress from'nprogress'
import'nprogress/nprogress.css'
import{ getToken, setToken }from'@/utils/auth'
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()
if(hasToken) {
if(to.path === '/login') {
next({ path: '/' })
NProgress.done()
}else{
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if(hasRoles) {
next()
}else{
try{
store.dispatch('user/getInfo').then(() => { // 拉取info
store.dispatch('permission/generateRoutes').then(res => { // 生成可访问的路由表
router.addRoutes(res) // 动态添加可访问路由表
const redirect = decodeURIComponent(from.query.redirect || to.path)
next({ ...to, replace: true })
})
}).catch(err => {
console.log(err);
})
}catch(error) {
console.log(error)
const origin = window.location.origin
if(origin.includes('localhost')) {
setToken('localhost')
}else{
window.location = origin + '/login'
}
NProgress.done()
}
}
}
}else{
if(whiteList.indexOf(to.path) !== -1) {
next()
}else{
window.location = origin + '/login'
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
3、修改store/modules/permission.js,请求后台获取菜单,解析后加入到动态路由
import { asyncRoutes, constantRoutes } from '@/router'
import Layout from '@/layout'
import { getToken }from'@/utils/auth'
import { getUserMenu } from '@/api/user'
function hasPermission (roles, route) {
if(route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
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: [],
btnRoles: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
},
SET_BTNROLES:(state, btnRoles) => {
state.btnRoles = btnRoles
}
}
export function generaMenu(routes, data) {
data.forEach(item => {
const menu = {
path: item.url,
children: [],
name: item.name,
isShow: true,
meta: { title: item.name, keepAlive: true }
}
if(item.url.indexOf("token") >= 0){
menu.path = item.url + getToken()
}
if(item.component!=null&&item.component!==''){
menu.component = item.component === 'Layout' ? Layout : resolve => require([`@/views${item.component}`], resolve)
}
if(item.redirect!=null&&item.redirect!==''){
menu.redirect = item.redirect
}
if(item.icon!=null&&item.icon!==''){
menu.meta.icon = item.icon
}else{
menu.meta.icon = 'task'
}
if (item.component === 'Layout') {
generaMenu(menu.children, item.childList)
}
routes.push(menu)
})
}
export function generaBtn(asyncBtns, data) {
data.forEach(item => {
if(item.buttonCode!=null&&item.buttonCode!==''){
asyncBtns.push(item.buttonCode)
}
})
}
const actions = {
generateRoutes({commit}) {
return new Promise(resolve => {
getUserMenu().then(response => {
let asyncBtns = []
let menuArr = response.data.object.menuList
let btnArr = response.data.object.btnList
generaMenu(asyncRoutes, menuArr)
generaBtn(asyncBtns, btnArr)
console.log(asyncRoutes)
commit('SET_ROUTES', asyncRoutes)
commit('SET_BTNROLES', asyncBtns)
resolve(asyncRoutes)
})
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
4、修改store/getters.js,对state.permission.routes设置别名
const getters = {
sidebar: state => state.app.sidebar,
language: state => state.app.language,
size: state => state.app.size,
device: state => state.app.device,
visitedViews: state => state.tagsView.visitedViews,
cachedViews: state => state.tagsView.cachedViews,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
introduction: state => state.user.introduction,
roles: state => state.user.roles,
permission_routes: state => state.permission.routes
};
export default getters
5、修改layout/components/Sidebar/index.vue,根据store的路由数据,渲染菜单
<template>
<div :class="{'has-logo':showLogo}">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="false"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
</el-menu>
</el-scrollbar>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'
export default {
components: { SidebarItem, Logo },
computed: {
...mapGetters([
'permission_routes',
'sidebar'
]),
activeMenu() {
const route = this.$route
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
showLogo() {
return this.$store.state.settings.sidebarLogo
},
variables() {
return variables
},
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>
至此前端就可以了,和后端约定好菜单数据结构即可,本地也可以使用mock模拟数据来测试
这里提供一份我测试的菜单结构供参考
{
"code":"0",
"data":{
"menuList":[
{
"name":"task",
"component":"Layout",
"hidden":null,
"icon":"task",
"url":"/task",
"childList":[
{
"authSign":"taskInbound",
"name":"taskInbound",
"component":"/task/task-inbound-search",
"hidden":null,
"icon":"task",
"url":"task-inbound-search",
"childList":[]
}
]
},
{
"childList":[
{
"authSign":"mapMonitor",
"childList":[],
"component":"",
"hidden":null,
"icon":"monitor",
"name":"mapMonitor",
"url":"/index.html?token="
}
],
"component":"Layout",
"hidden":null,
"icon":"monitor",
"name":"monitor",
"url":"/monitor"
}
]]
},
"msg":"访问成功",
"serverTime":"2020-05-22 13:53:05"
}
如果需要控制到按钮级别,可以参考我写的Vue按钮权限控制实践
-_-如果有其他更好的方式,欢迎下方留言-_-
更多推荐
已为社区贡献4条内容
所有评论(0)