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:小五。

原创:vue admin template最详细的动态菜单路由权限改造+按钮权限 - 掘金 

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
在`vue-admin-template`中,实现权限控制路由的方式是通过自定义指令和自定义路由结构文件来实现的。引用中的代码展示了一个名为`permissions`的自定义指令,它会根据用户的权限动态控制按钮的显示与隐藏。在这个指令中,通过获取当前用户的权限结构来判断是否具有该按钮权限。如果没有权限,就移除该按钮元素。 而引用和引用展示了自定义的路由结构文件。在这些文件中,定义了不同权限下的路由配置。通过配置不同的路由结构,可以根据用户的权限动态生成路由。 总结来说,通过自定义指令和自定义路由结构文件,可以实现在`vue-admin-template`中对权限的控制和管理。根据用户的权限,可以动态显示或隐藏按钮,并根据权限生成不同的路由配置。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [vue-admin-template用户权限控制及按钮权限](https://blog.csdn.net/qq_43030908/article/details/123925495)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [Vue2:实现权限路由(基于vue-admin-template)](https://blog.csdn.net/m0_62823653/article/details/125228452)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值