vue-admin-template史上最强权限按钮+动态菜单栏+SpringBoot
本文详述了后台与前台进行动态菜单栏交互、以及按钮权限等。如果你有web项目功底的话,那么这篇文档你会觉得很舒服,功底不扎实的可能稍微有点困难,不过没关系,有问题及时评论或私信,或邮箱:zxphouhou@163.com,废话不多,直接干。 ps: 小五
前端实现动态菜单栏,前提条件后端提供数据,如果后端没有提供,可以使用mock进行调试,我这里后端已经实现,只贴核心代码,后端树形数据实现:
@Override
public List<IntergatedMenu> getMenuAllByAdminID(Long adminID) {
List<IntergatedMenu> menuList = intergatedMenuMapper.getMenuAllByAdminID(adminID);
if(StringUtils.isEmpty(menuList)){
logger.error("menu信息为空");
return null;
}
//获取父节点数据
List<IntergatedMenu> collect = menuList.stream().filter(menu -> {
return menu.getParentId() == 0L;
}).map(menu1 -> {
menu1.setChildren(getAllChildrenMenu(menu1,menuList));
MetaVO meta = new MetaVO();
meta.setIcon(menu1.getIcon());
meta.setTitle(menu1.getTitle());
menu1.setMeta(meta);
return menu1;
}).collect(Collectors.toList());
return collect;
}
public List<IntergatedMenu> getAllChildrenMenu(IntergatedMenu menu,List<IntergatedMenu> menus) {
List<IntergatedMenu> menuList = menus.stream().filter(menu1 -> {
return menu1.getParentId().equals(menu.getMenuId());
}).map(menu2 -> {
menu2.setChildren(getAllChildrenMenu(menu2, menus));
MetaVO meta = new MetaVO();
meta.setIcon(menu2.getIcon());
meta.setTitle(menu2.getTitle());
menu2.setMeta(meta);
return menu2;
}).collect(Collectors.toList());
return menuList;
}
返回格式数据如下:
{
"code": 20000,
"msg": "业务成功",
"data": {
"menuList": [
{
"menuId": 7,
"path": "/admin",
"name": "admin",
"title": "用户管理",
"icon": "el-icon-s-help",
"component": "Layout",
"parentId": 0,
"redirect": "/admin/list",
"children": [
{
"menuId": 8,
"path": "/admin/list",
"name": "list",
"title": "用户列表信息",
"icon": "el-icon-s-help",
"component": "admin/list",
"parentId": 7,
"redirect": "",
"children": [],
"meta": {
"title": "用户列表信息",
"icon": "el-icon-s-help"
}
},
{
"menuId": 9,
"path": "/admin/tree",
"name": "Tree",
"title": "用户操作日志",
"icon": "el-icon-s-help",
"component": "Layout",
"parentId": 7,
"redirect": null,
"children": [],
"meta": {
"title": "用户操作日志",
"icon": "el-icon-s-help"
}
}
],
"meta": {
"title": "用户管理",
"icon": "el-icon-s-help"
}
}
],
"roleNameList": [
"主管",
"普通用户"
],
"admin": {
"id": 36,
"username": "小五爱喝珍珠奶茶",
"account": "admin123456",
"status": "2",
"number": "A-517067",
"workdate": "2022-07-13",
"email": "1323349692@qq.com",
"phone": null,
"headimg": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
"isDeleted": "0",
"createDate": "2022-07-13 04:36:49",
"updateDate": "2022-07-13 04:36:49",
"requestid": null,
"respcode": null,
"respmessage": null,
"respbizid": null,
"businesstype": "admin-register",
"registertype": "1",
"remark3": null,
"remark2": null,
"remark1": "CWgLOJ/cWwxc9g3tbuxsmA==",
"code": null
}
}
}
接下来开始前端,roter-index.js文件修改,注册页我已经写好了,这里就不贴注册页了:
//多余的路由不要,只需要登录、注册、主页路由,其余不需要
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/register',
component: () => import('@/views/register/index'),
hidden: true
},
// {
// path: '/404',
// component: () => import('@/views/404'),
// hidden: true
// },
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '主页', icon: 'dashboard' }
}]
},
]
修改获取用户信息:
//位置:store——modules——user.js,这里只贴与本章相关的代码,另外后端反的数据结构已经贴出来了
import {login,getInfo,logout} from '@/api/user'
import {getToken,setToken,removeToken} from '@/utils/auth'
import {resetRouter} from '@/router'
const getDefaultState = () => {
return {
token: getToken(),
name: '',
avatar: '',
roles: [], // 角色权限控制按钮显示
menus: [] // 菜单权限
}
}
const state = getDefaultState()
const mutations = {
SET_ROLES: (state, roles) => {
state.roles = roles // 角色权限
},
SET_MENUS: (state, menus) => {
state.menus = menus // 菜单权限
}
}
const actions = {
// get user info
getInfo({commit,state}) {
return new Promise((resolve, reject) => {
getInfo().then(response => {
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
data.menuList.push({ 'path': '/404', 'component': '404', 'hide': 'true' }, {
'path': '*',
'redirect': '/404',
'hidden': 'true'
})
commit('SET_NAME', data.admin.username)
commit('SET_AVATAR', data.admin.headimg)
commit('SET_ROLES', data.roleNameList) // 角色权限
commit('SET_MENUS', data.menuList) // 菜单权限
resolve(data)
}).catch(error => {
reject()
})
})
},
}
修改getters.js文件,位置:store—getters.js:
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
menus: state => state.user.menus, // 菜单权限信息
roles: state => state.user.roles // 角色信息
}
export default getters
request.js修改,位置:utils—request.js:
import axios from 'axios'
import { Message, MessageBox } from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'
// 创建axios实例
const service = axios.create({
// baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
baseURL: process.env.VUE_APP_BASE_API,
// withCredentials: true, // send cookies when cross-domain requests
timeout: 60000 // request timeout
})
// request拦截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['Login-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
},
error => {
// Do something with request error
//console.log(error) // for debug
//Promise.reject(error)
}
)
// response 拦截器
service.interceptors.response.use(
response => {
/**
* code为20000 :业务成功
*/
const res = response.data
if (res.code !== 20000) {
Message({
message: res.msg,
type: 'error',
duration: 5 * 1000
})
//常见特殊响应码判断
switch (code) {
case 401:
if (router.currentRoute.name === 'login') {
return Promise.reject(new Error(message))
} else {
Message({
message: '您没有权限访问该资源',
type: 'error'
})
}
break
case 403:
Message({
message: message,
type: 'error'
})
break
case 500:
Message({
message: '服务异常,请联系管理员',
type: 'error'
})
break
default:
Message({
message: message,
type: 'error'
})
}
// 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了;
// if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// MessageBox.confirm(
// '你已被登出,可以取消继续留在该页面,或者重新登录',
// '确定登出',
// {
// confirmButtonText: '重新登录',
// cancelButtonText: '取消',
// type: 'warning'
// }
// ).then(() => {
// store.dispatch('FedLogOut').then(() => {
// location.reload() // 为了重新实例化vue-router对象 避免bug
// })
// })
// }
//return Promise.reject('error')
return Promise.reject(new Error(message || 'Error'))
} else {
if(res.msg == '用户登出成功'){
Message({
message: res.msg,
type: 'success',
duration: 5 * 1000
})
}
if(res.msg == '登录成功'){
Message({
message: res.msg,
type: 'success',
duration: 5 * 1000
})
}
return response.data
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.msg,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
router下新增文件:_import_development.js,文件内容:
// 开发环境导入组件
module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+
router下新增文件:_import_proudction.js,如果是生产环境可以使用这个,文件内容:
module.exports = file => () => import('@/views/' + file + '.vue')
修改权限文件,src—permission.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'
import Layout from '@/layout' // 引入Layout
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login', '/register'] // no redirect whitelist
const _import = require('./router/_import_' + process.env.NODE_ENV) // 引入获取组件的方法
// 路由拦截器
router.beforeEach(async(to, from, next) => {
// 进度条加载
//NProgress.start()
// 获取page标题
document.title = getPageTitle(to.meta.title)
// 获取token决定用户是否可以登录
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// 如果已登录,则重定向到主页
next({ path: '/' })
NProgress.done()
} else {
const hasGetUserInfo = store.getters.name
if (hasGetUserInfo) {
next()
} else {
try {
// 每次刷新都会执行try获取用户信息,所以getInfo必须要有,后端不提供怎么办?揍他
await store.dispatch('user/getInfo')
// **在这里做动态路由**
if (store.getters.menus.length < 1) {
global.antRouter = []
next()
}
const menus = filterAsyncRouter(store.getters.menus) // 过滤路由
router.addRoutes(menus) // 动态添加路由
global.antRouter = menus // 将路由数据传递给全局变量,做侧边栏菜单渲染工作
next({ ...to, replace: true })
} catch (error) {
// 移除token去登录页
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
/* 没有token */
if (whiteList.indexOf(to.path) !== -1) {
// 如果该路由在白名单内, 放行
next()
} else {
// 不在白名单内不允许通过重定向到登录页
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap) {
const accessedRouters = asyncRouterMap.filter(route => {
if (route.component) {
if (route.component === 'Layout') { // Layout组件特殊处理
route.component = Layout
} else {
route.component = _import(route.component) // 导入组件
}
}
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children)
}
return true
})
return accessedRouters
}
更换路由,src—layout—components—Sidebar—index.vue:
routes() {
return this.$router.options.routes.concat(global.antRouter); // 新路由连接
},
以上步骤完成就可以看到效果了,我们使用俩个账号分别登录看一下效果。
普通用户效果,可以看到只有一个主页菜单:
接着,我们在使用主管账号进行查看效果,可以看到效果显著:
以上动态菜单栏至此已全部完成,记着重启项目哦,npm run dev;我们接着开始完善按钮权限:
通过以上信息,我们刚刚使用了俩个账户进行登录的,一个是普通用户角色,一个是主管角色,
那么普通用户应该是只有一个按钮,主管角色有俩个按钮,因为主管比普通角色大,所以拥有普通角色的权限,下面开始开发:
首先在src下创建文件夹directive,并在directive文件夹下创建permission文件夹,在permission文件夹下创建 index.js、permission.js 文件。
index.js文件内容为:
import permission from './permission'
const install = function(Vue) {
Vue.directive('permission', permission)
}
if (window.Vue) {
window['permission'] = permission
Vue.use(install); // eslint-disable-line
}
permission.install = install
export default permission
permission.js文件内容为:
import store from '@/store'
function checkPermission(el, binding) {
const { value } = binding
const roles = store.getters && store.getters.roles
if (value && value instanceof Array) {
if (value.length > 0) {
const permissionRoles = value
const hasPermission = roles.some(role => {
return permissionRoles.includes(role)
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
}
} else {
throw new Error(`need roles! Like v-permission="['权限不足','权限不足']"`)
}
}
export default {
inserted(el, binding) {
checkPermission(el, binding)
},
update(el, binding) {
checkPermission(el, binding)
}
}
这里以登录之后主页面进行效果展示:views—dashboard—index.vue:
<template>
<div class="dashboard-container">
<div class="dashboard-text">name: {{ name }}</div>
<el-tooltip class="item" effect="dark" content="普通用户只拥有普通权限" placement="top-start">
<el-button v-permission="['主管','普通用户']">admin</el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="主管所有权限都可查看" placement="top-start">
<el-button v-permission="['主管']">editor</el-button>
</el-tooltip>
</div>
</template>
<script>
import permission from '@/directive/permission/index.js' // 权限判断指令
import { mapGetters } from 'vuex'
export default {
name: 'Dashboard',
computed: {
...mapGetters([
'name'
])
},
directives: { permission } // 自定义指令
}
</script>
<style lang="scss" scoped>
.dashboard {
&-container {
margin: 30px;
}
&-text {
font-size: 30px;
line-height: 46px;
}
}
</style>
改完之后重启项目,还是以普通用户、主管账号进行分别登录查看效果:
普通用户查看效果,可以看到只有一个 admin 按钮:
接着使用主管账号进行登录查看,可以看到 admin、editor按钮都展示出来了:
至此,本章完毕,ps:小五。
更多推荐
所有评论(0)