当前位置: 首页 > news >正文

Vue 菜单权限管理的计与实现

设计理念

1. 权限模型设计

  • RBAC (基于角色的访问控制):用户-角色-权限三级模型

  • 前端路由与菜单分离:路由负责页面跳转,菜单负责导航展示

  • 数据驱动:权限数据决定菜单渲染

  • 分层控制:页面级、模块级、操作级权限控制

2. 核心原则

  • 安全性:前端权限验证是用户体验优化,核心验证应在后端

  • 可维护性:权限配置集中管理,易于修改和扩展

  • 用户体验:无权限内容对用户完全隐藏

  • 性能:按需加载权限相关资源

完整实现示例

1. 项目结构

text

src/
├── components/
│   ├── Layout/
│   │   ├── AppLayout.vue
│   │   └── Sidebar.vue
│   └── common/
├── router/
│   ├── index.js
│   └── routes.js
├── store/
│   ├── index.js
│   ├── modules/
│   │   └── user.js
│   └── types.js
├── utils/
│   └── permission.js
├── api/
│   └── user.js
└── views/├── dashboard/├── user/├── settings/└── 404.vue

2. 权限数据模型

javascript

// store/types.js
export const USER_SET_ROLES = 'USER_SET_ROLES'
export const USER_SET_PERMISSIONS = 'USER_SET_PERMISSIONS'
export const USER_SET_MENUS = 'USER_SET_MENUS'// 权限类型枚举
export const PERMISSION_TYPES = {MENU: 'menu',BUTTON: 'button',API: 'api'
}

3. 路由配置

javascript

// router/routes.js
// 静态路由 - 所有用户都可访问
export const constantRoutes = [{path: '/login',component: () => import('@/views/Login.vue'),hidden: true},{path: '/404',component: () => import('@/views/404.vue'),hidden: true}
]// 动态路由 - 根据权限动态添加
export const asyncRoutes = [{path: '/dashboard',component: () => import('@/views/dashboard/Index.vue'),name: 'Dashboard',meta: {title: '仪表盘',icon: 'dashboard',permissions: ['dashboard:view']}},{path: '/user',component: () => import('@/views/user/Index.vue'),redirect: '/user/list',name: 'User',meta: {title: '用户管理',icon: 'user',permissions: ['user:view']},children: [{path: 'list',component: () => import('@/views/user/List.vue'),name: 'UserList',meta: {title: '用户列表',permissions: ['user:list']}},{path: 'create',component: () => import('@/views/user/Create.vue'),name: 'UserCreate',meta: {title: '新增用户',permissions: ['user:create']}},{path: 'edit/:id',component: () => import('@/views/user/Edit.vue'),name: 'UserEdit',meta: {title: '编辑用户',permissions: ['user:edit'],hidden: true // 不在菜单显示}}]},{path: '/system',component: () => import('@/views/system/Index.vue'),redirect: '/system/role',name: 'System',meta: {title: '系统管理',icon: 'system',permissions: ['system:view']},children: [{path: 'role',component: () => import('@/views/system/Role.vue'),name: 'Role',meta: {title: '角色管理',permissions: ['role:view']}},{path: 'permission',component: () => import('@/views/system/Permission.vue'),name: 'Permission',meta: {title: '权限管理',permissions: ['permission:view']}}]},// 404页面必须放在最后{ path: '*', redirect: '/404', hidden: true }
]

4. Vuex状态管理

javascript

// store/modules/user.js
import { constantRoutes, asyncRoutes } from '@/router/routes'
import { USER_SET_ROLES, USER_SET_PERMISSIONS, USER_SET_MENUS } from '../types'const state = {roles: [],permissions: [],menus: [],routes: constantRoutes // 初始只有静态路由
}const mutations = {[USER_SET_ROLES](state, roles) {state.roles = roles},[USER_SET_PERMISSIONS](state, permissions) {state.permissions = permissions},[USER_SET_MENUS](state, menus) {state.menus = menus},SET_ROUTES(state, routes) {state.routes = constantRoutes.concat(routes)}
}const actions = {// 生成用户权限路由generateRoutes({ commit, state }, permissions) {return new Promise(resolve => {const accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)commit('SET_ROUTES', accessedRoutes)resolve(accessedRoutes)})},// 获取用户信息async getUserInfo({ commit, dispatch }) {try {// 模拟API调用const userInfo = await getUserInfoAPI()const { roles, permissions } = userInfocommit(USER_SET_ROLES, roles)commit(USER_SET_PERMISSIONS, permissions)// 根据权限生成路由const accessedRoutes = await dispatch('generateRoutes', permissions)// 生成菜单(过滤掉hidden为true的路由)const menus = generateMenus(accessedRoutes)commit(USER_SET_MENUS, menus)return {roles,permissions,menus}} catch (error) {console.error('获取用户信息失败:', error)throw error}}
}// 工具函数:根据权限过滤路由
function filterAsyncRoutes(routes, permissions) {const res = []routes.forEach(route => {const tmp = { ...route }// 检查路由权限if (hasPermission(permissions, tmp)) {if (tmp.children) {tmp.children = filterAsyncRoutes(tmp.children, permissions)// 如果过滤后子路由不为空,才保留父路由if (tmp.children.length > 0) {res.push(tmp)}} else {res.push(tmp)}}})return res
}// 检查是否有权限
function hasPermission(permissions, route) {if (route.meta && route.meta.permissions) {return permissions.some(permission => route.meta.permissions.includes(permission))}return true // 没有设置权限要求的路由默认允许访问
}// 生成菜单
function generateMenus(routes) {const menus = []routes.forEach(route => {// 跳过隐藏的路由if (route.hidden) returnconst menu = {path: route.path,name: route.name,meta: { ...route.meta },children: []}if (route.children && route.children.length > 0) {menu.children = generateMenus(route.children)// 如果子菜单为空,则不显示父菜单if (menu.children.length === 0) return}menus.push(menu)})return menus
}export default {namespaced: true,state,mutations,actions
}

5. 权限工具函数

javascript

// utils/permission.js
import store from '@/store'/*** 检查是否有权限* @param {Array} needPermissions 需要的权限* @returns {Boolean}*/
export function hasPermission(needPermissions) {if (!needPermissions || needPermissions.length === 0) {return true}const userPermissions = store.getters.permissionsreturn userPermissions.some(permission => needPermissions.includes(permission))
}/*** 检查是否有角色* @param {Array} needRoles 需要的角色* @returns {Boolean}*/
export function hasRole(needRoles) {if (!needRoles || needRoles.length === 0) {return true}const userRoles = store.getters.rolesreturn userRoles.some(role => needRoles.includes(role))
}// 权限指令
export const permissionDirective = {inserted(el, binding) {const { value } = bindingconst permissions = store.getters.permissionsif (value && value instanceof Array && value.length > 0) {const hasPerm = permissions.some(permission => value.includes(permission))if (!hasPerm) {el.parentNode && el.parentNode.removeChild(el)}} else {throw new Error(`需要权限数组! 如: v-permission="['user:create']"`)}}
}// 角色指令
export const roleDirective = {inserted(el, binding) {const { value } = bindingconst roles = store.getters.rolesif (value && value instanceof Array && value.length > 0) {const hasRole = roles.some(role => value.includes(role))if (!hasRole) {el.parentNode && el.parentNode.removeChild(el)}} else {throw new Error(`需要角色数组! 如: v-role="['admin']"`)}}
}

6. 侧边栏菜单组件

vue

<!-- components/Layout/Sidebar.vue -->
<template><el-scrollbar wrap-class="scrollbar-wrapper"><el-menu:default-active="activeMenu":collapse="isCollapse":background-color="variables.menuBg":text-color="variables.menuText":active-text-color="variables.menuActiveText":unique-opened="false":collapse-transition="false"mode="vertical"><sidebar-itemv-for="route in menus":key="route.path":item="route":base-path="route.path"/></el-menu></el-scrollbar>
</template><script>
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem.vue'
import variables from '@/styles/variables.scss'export default {name: 'Sidebar',components: { SidebarItem },computed: {...mapGetters(['menus']),activeMenu() {const route = this.$routeconst { meta, path } = routeif (meta.activeMenu) {return meta.activeMenu}return path},isCollapse() {return !this.$store.state.app.sidebar.opened},variables() {return variables}}
}
</script>

7. 菜单项组件

vue

<!-- components/Layout/SidebarItem.vue -->
<template><div v-if="!item.hidden"><template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren)"><app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)"><el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}"><item :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" :title="onlyOneChild.meta.title" /></el-menu-item></app-link></template><el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body><template slot="title"><item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" /></template><sidebar-itemv-for="child in item.children":key="child.path":is-nest="true":item="child":base-path="resolvePath(child.path)"class="nest-menu"/></el-submenu></div>
</template><script>
import path from 'path'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'export default {name: 'SidebarItem',components: { Item, AppLink },props: {item: {type: Object,required: true},isNest: {type: Boolean,default: false},basePath: {type: String,default: ''}},data() {this.onlyOneChild = nullreturn {}},methods: {hasOneShowingChild(children = [], parent) {const showingChildren = children.filter(item => {if (item.hidden) {return false} else {this.onlyOneChild = itemreturn true}})if (showingChildren.length === 1) {return true}if (showingChildren.length === 0) {this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }return true}return false},resolvePath(routePath) {if (isExternal(routePath)) {return routePath}if (isExternal(this.basePath)) {return this.basePath}return path.resolve(this.basePath, routePath)}}
}
</script>

8. 权限指令注册

javascript

// main.js
import Vue from 'vue'
import { permissionDirective, roleDirective } from '@/utils/permission'// 注册权限指令
Vue.directive('permission', permissionDirective)
Vue.directive('role', roleDirective)

9. 路由守卫

javascript

// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import { constantRoutes } from './routes'
import store from '@/store'
import { Message } from 'element-ui'Vue.use(Router)const router = new Router({mode: 'history',scrollBehavior: () => ({ y: 0 }),routes: constantRoutes
})// 白名单
const whiteList = ['/login', '/auth-redirect']router.beforeEach(async (to, from, next) => {// 设置页面标题if (to.meta.title) {document.title = to.meta.title}// 判断是否有tokenconst hasToken = store.getters.tokenif (hasToken) {if (to.path === '/login') {next({ path: '/' })} else {const hasRoles = store.getters.roles && store.getters.roles.length > 0if (hasRoles) {next()} else {try {// 获取用户信息const { roles } = await store.dispatch('user/getUserInfo')// 根据角色生成可访问路由const accessRoutes = await store.dispatch('user/generateRoutes', roles)// 动态添加路由router.addRoutes(accessRoutes)// 确保addRoutes完成后nextnext({ ...to, replace: true })} catch (error) {// 失败则重置token并跳转到登录页await store.dispatch('user/resetToken')Message.error(error || 'Has Error')next(`/login?redirect=${to.path}`)}}}} else {// 没有tokenif (whiteList.indexOf(to.path) !== -1) {next()} else {next(`/login?redirect=${to.path}`)}}
})router.afterEach(() => {// 完成进度条
})export default router

10. 在页面中使用权限控制

vue

<!-- views/user/List.vue -->
<template><div class="user-list"><div class="header"><el-button v-permission="['user:create']"type="primary" @click="handleCreate">新增用户</el-button><el-button v-permission="['user:export']"@click="handleExport">导出数据</el-button></div><el-table :data="userList"><el-table-column prop="name" label="姓名" /><el-table-column prop="email" label="邮箱" /><el-table-column prop="role" label="角色" /><el-table-column label="操作"><template slot-scope="scope"><el-button v-permission="['user:edit']"size="mini" @click="handleEdit(scope.row)">编辑</el-button><el-button v-permission="['user:delete']"size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button></template></el-table-column></el-table></div>
</template><script>
export default {name: 'UserList',data() {return {userList: []}},methods: {handleCreate() {// 新增用户逻辑},handleEdit(user) {// 编辑用户逻辑},handleDelete(user) {// 删除用户逻辑},handleExport() {// 导出数据逻辑}}
}
</script>

设计要点说明

1. 权限粒度控制

  • 页面级:通过路由守卫控制

  • 菜单级:通过动态菜单生成控制

  • 操作级:通过指令控制按钮显示

2. 性能优化

  • 路由懒加载

  • 按需生成权限路由

  • 菜单数据缓存

3. 安全性

  • 前端权限验证是用户体验优化

  • 关键接口必须在后端验证权限

  • 敏感操作需要二次确认

4. 扩展性

  • 支持多级嵌套路由

  • 支持多种权限验证方式

  • 易于集成新的权限类型

这个权限管理系统提供了完整的解决方案,从路由控制到按钮级别的权限管理,具有良好的可维护性和扩展性。

http://www.dtcms.com/a/462595.html

相关文章:

  • 怎么自己做网站共享办公室 设计
  • Linux进程第八讲——进程状态全景解析(二):从阻塞到消亡的完整生命周期
  • 网站设计审美角度公司手机网站建设
  • 【数据结构】优先级队列(堆)
  • 合肥网站建设首选 晨飞网络nas的wordpress的端口
  • 动漫网站开发优势网店交易哪个平台好
  • Python 中的集合(set)知识大全
  • 网站开发工具的功能包括现在的公司都有自己的网站吗
  • 浏览器访问百度首页的七层协议协作流程
  • 陕西中小企业网站建设推广促销直播网站开发
  • 算起计算器APP发布
  • 做类似淘宝的网站设计需要什么视频素材模板免费下载网
  • 商业网站源码安卓app是用什么语言开发的
  • ros2 自定义消息、服务、动作接口详细范例
  • 企业网站托管后果html5和h5的区别
  • 做网站营业执照经营范围怎么填写如何修改asp网站栏目
  • css里的where选择器的用法
  • 网站人多怎么优化全网引擎搜索
  • 10-七麦js扣代码
  • 做一婚恋网站多少钱医疗网络营销方式
  • golang面经——sync相关
  • wordpress 财经插件wordpress mysql优化
  • 软考中级习题与解答——第十四章_UML建模(5)
  • 万网怎样做网站调试字体怎么装到wordpress
  • 【Open3D】Ch.2:点云体素下采样 | Python
  • 奉贤区做网站wordpress五分钟安装
  • 复杂网络仿真从入门到精通:0 学习路线
  • 怎样创建网站数据库服装租赁 网站 php
  • 礼品工艺品网站建设首页网站模板
  • 福州推广企业网站网站优化助手