管理系统权限管理(菜单、页面、按钮)react+redux/vue3 pinia实现方式

一、Vue3 + Pinia 权限控制示例

1️⃣ 权限点接口示例(后端返回)

复制代码
{
  "token": "xxx",
  "perms": [
    "user:list",
    "user:add",
    "order:view"
  ]
}

2️⃣ Pinia Store (stores/permission.ts)

复制代码
import { defineStore } from 'pinia'

interface UserData {
  id: string;
  name: string;
  role: string;
}

interface PermissionState {
  token: string
  userData: UserData | null
  perms: string[]
}

export const usePermissionStore = defineStore('permission', {
  state: (): PermissionState => ({
    token: '',
    userData: null,
    perms: []
  }),
  actions: {
    setToken(token: string) {
      this.token = token
    },
    setUserData(user: UserData) {
      this.userData = user
    },
    setPerms(perms: string[]) {
      this.perms = perms
    },
    clear() {
      this.token = ''
      this.userData = null
      this.perms = []
    },
    // 根据 perms 过滤路由
    filterRoutes(routes: any[]) {
      return routes.filter(route => {
        if (!route.meta?.perms) return true
        return route.meta.perms.some((p: string) => this.perms.includes(p))
      }).map(route => {
        if (route.children) {
          route.children = this.filterRoutes(route.children)
        }
        return route
      })
    },
    // 菜单生成
    generateMenus(routes: any[]) {
      return this.filterRoutes(routes)
    }
  }
})

路由配置示例

复制代码
export const asyncRoutes = [
  {
    path: '/user',
    name: 'User',
    component: () => import('@/views/user/index.vue'),
    meta: { title: '用户管理', perms: ['user:list'] },
    children: [
      {
        path: 'add',
        name: 'UserAdd',
        component: () => import('@/views/user/add.vue'),
        meta: { title: '新增用户', perms: ['user:add'] }
      }
    ]
  },
  {
    path: '/order',
    name: 'Order',
    component: () => import('@/views/order/index.vue'),
    meta: { title: '订单管理', perms: ['order:view'] }
  }
]

登录后调用

复制代码
import { usePermissionStore } from '@/stores/permission'

const permissionStore = usePermissionStore()
const { token, userData, perms } = await api.login()

permissionStore.setToken(token)
permissionStore.setUserData(userData)
permissionStore.setPerms(perms)

// 生成菜单
const menuList = permissionStore.generateMenus(asyncRoutes)

按钮权限判断

复制代码
<template>
  <button v-if="hasPerm('user:add')">新增用户</button>
</template>

<script setup lang="ts">
import { usePermissionStore } from '@/stores/permission'

const permissionStore = usePermissionStore()

function hasPerm(code: string) {
  return permissionStore.perms.includes(code)
}
</script>

二、React + Redux Toolkit 权限控制示例

Redux Slice (store/permissionSlice.ts)

复制代码
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

interface UserData {
  id: string
  name: string
  role: string
}

interface PermissionState {
  token: string
  userData: UserData | null
  perms: string[]
}

const initialState: PermissionState = {
  token: '',
  userData: null,
  perms: []
}

const permissionSlice = createSlice({
  name: 'permission',
  initialState,
  reducers: {
    setToken: (state, action: PayloadAction<string>) => {
      state.token = action.payload
    },
    setUserData: (state, action: PayloadAction<UserData>) => {
      state.userData = action.payload
    },
    setPerms: (state, action: PayloadAction<string[]>) => {
      state.perms = action.payload
    },
    clear: (state) => {
      state.token = ''
      state.userData = null
      state.perms = []
    }
  }
})

export const { setToken, setUserData, setPerms, clear } = permissionSlice.actions
export default permissionSlice.reducer

Redux Store (store/index.ts)

复制代码
import { configureStore } from '@reduxjs/toolkit'
import permissionReducer from './permissionSlice'

export const store = configureStore({
  reducer: {
    permission: permissionReducer
  }
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

路由配置示例

复制代码
import { lazy } from 'react'

export const asyncRoutes = [
  {
    path: '/user',
    element: lazy(() => import('@/pages/User')),
    meta: { title: '用户管理', perms: ['user:list'] },
    children: [
      {
        path: 'add',
        element: lazy(() => import('@/pages/UserAdd')),
        meta: { title: '新增用户', perms: ['user:add'] }
      }
    ]
  },
  {
    path: '/order',
    element: lazy(() => import('@/pages/Order')),
    meta: { title: '订单管理', perms: ['order:view'] }
  }
]

登录后过滤路由

复制代码
import { useSelector } from 'react-redux'
import { RootState } from './store'

const perms = useSelector((state: RootState) => state.permission.perms)

function filterRoutes(routes: any[], perms: string[]) {
  return routes.filter(route => {
    if (!route.meta?.perms) return true
    return route.meta.perms.some((p: string) => perms.includes(p))
  }).map(route => {
    if (route.children) route.children = filterRoutes(route.children, perms)
    return route
  })
}

const allowedRoutes = filterRoutes(asyncRoutes, perms)

菜单渲染 & 按钮权限

复制代码
// 菜单渲染
<Menu>
  {allowedRoutes.map(r => (
    <Menu.Item key={r.path}>{r.meta.title}</Menu.Item>
  ))}
</Menu>

// 按钮权限
{perms.includes('user:add') && <Button>新增用户</Button>}

核心思路(Vue / React 通用)

  1. 后端只返回 perms 数组 → 前端根据它控制 UI
  2. 前端菜单 / 路由根据 perms 过滤
  3. 按钮或页面模块用 perms.includes(...) 控制显示
  4. 后端接口 仍然要校验 perms → 真正的安全保障

菜单写死在前端,通过权限点 v-if 显示/隐藏(大厂常用)

流程:

  1. 菜单是前端写死的(存 routes.ts)
  2. 用户登录,后台只返回权限点(字符串数组,如 ['user:add', 'user:delete'])
  3. 前端根据权限点过滤导航栏、路由
  • 示例

    {
    path: '/user',
    name: 'User',
    meta: { title: '用户管理', perms: ['user:view'] }
    }

✔ 优点:

  1. 前端掌控路由、菜单,灵活更容易维护
  2. 不需要后端维护菜单表
  3. 常用于大厂 组件化、前后端分仓 的模式
  4. 权限点即可驱动显示/隐藏按钮

前端菜单只是"显示控制",不是安全控制

就算你用后台动态返回菜单,也不能防止用户:

  1. 手动输入地址
  2. 用 F12 改源码
  3. 注入 JS
  4. 抓包直接调接口

真正的安全是 API 权限验证

所以前端写死菜单 + 权限过滤 = 大厂最佳体验

措施 目的
前端菜单过滤 体验层:让用户不看到没权限的东西
后端接口校验 安全层:真正的权限防护(不可绕过)

后端控制实际操作

  1. 所有 API 请求都要校验用户权限
  2. 后端拿 token → 查角色 → 查 perms → 判断是否允许该操作
  3. 例如:POST /api/user/add
    • 用户 perms 包含 user:add → 允许
    • 用户 perms 不包含 user:add → 返回 403 Forbidden

重点

  1. 前端 不需要 每次请求都传 user:add
  2. 后端 通过 token 或 session 自动知道 谁在操作,然后根据权限码判断是否允许
  3. 前端只是可视化限制,真正的安全由后端负责

最终总结(Vue + React 通用)

功能点 Vue3 实现 React 实现
菜单写死 asyncRoutes routes.tsx
权限点存储 Pinia Zustand/Redux
菜单过滤 filterRoutes() filterRoutes()
按钮权限 v-if hasPerm() perms.includes()
安全关键 必须后端接口校验 必须后端接口校验

另外一种方案

菜单从后台返回(动态菜单),超级管理员添加菜单,登录后通过用户权限返回菜单的方式

流程:

  1. 超级管理员在后台添加菜单
  2. 菜单存数据库
  3. 用户登录 → 后端根据权限返回"可访问菜单树"
  4. 前端根据菜单渲染侧边栏、路由
    ✔ 优点:
  • 菜单可配置,前端不用改代码
  • 多系统保持一致
  • 超级管理员可随时调整菜单、权限
    ✘ 缺点:
  • 需要后端写复杂的"菜单树 + 角色过滤"
  • 前端路由也要动态生成
  • 有些公司根本不想把菜单配到数据库里
    现在已经放弃这个方案了
  • 大厂前端团队多,菜单都是前端组件结构
  • 菜单不需要天天变,写死才稳定
  • 权限只是控制菜单是否渲染,不需要写数据表
相关推荐
摘星编程2 分钟前
React Native鸿蒙版:Drawer抽屉导航实现
react native·react.js·harmonyos
东东51617 分钟前
智能社区管理系统的设计与实现ssm+vue
前端·javascript·vue.js·毕业设计·毕设
catino21 分钟前
图片、文件的预览
前端·javascript
2501_920931702 小时前
React Native鸿蒙跨平台实现推箱子游戏,完成玩家移动与箱子推动,当所有箱子都被推到目标位置时,玩家获胜
javascript·react native·react.js·游戏·ecmascript·harmonyos
layman05282 小时前
webpack5 css-loader:从基础到原理
前端·css·webpack
半桔2 小时前
【前端小站】CSS 样式美学:从基础语法到界面精筑的实战宝典
前端·css·html
AI老李2 小时前
PostCSS完全指南:功能/配置/插件/SourceMap/AST/插件开发/自定义语法
前端·javascript·postcss
_OP_CHEN2 小时前
【前端开发之CSS】(一)初识 CSS:网页化妆术的终极指南,新手也能轻松拿捏页面美化!
前端·css·html·网页开发·样式表·界面美化
啊哈一半醒2 小时前
CSS 主流布局
前端·css·css布局·标准流 浮动 定位·flex grid 响应式布局
PHP武器库2 小时前
ULUI:不止于按钮和菜单,一个专注于“业务组件”的纯 CSS 框架
前端·css