本文详述了后台与前台进行动态菜单栏交互、以及按钮权限等。如果你有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最详细的动态菜单路由权限改造+按钮权限 - 掘金 

GitHub 加速计划 / vu / vue-admin-template
19.83 K
7.39 K
下载
PanJiaChen/vue-admin-template: 基于 Vue.js 和 Element UI 的后台管理系统模板,支持多语言、主题和布局切换。该项目提供了一个完整的后台管理系统模板,可以方便地实现后台管理系统的快速搭建和定制,同时支持多种数据源和插件扩展。
最近提交(Master分支:2 个月前 )
4c18a3f4 - 2 年前
714ded11 - 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐