管理系统权限管理(菜单、页面、按钮)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 避坑指南:让电脑卡死的“无限循环“
前端·react.js
IT_陈寒2 小时前
Java 21新特性实战:5个杀手级功能让你的代码效率提升50%
前端·人工智能·后端
Komorebi゛2 小时前
【Vue3+Element Plus+Vite】按需导入Element Plus组件并配置全局样式文件
前端·css·vue.js
by__csdn2 小时前
JavaScript性能优化:减少重绘和回流(Reflow和Repaint)
开发语言·前端·javascript·vue.js·性能优化·typescript·vue
十一.3662 小时前
106-110 操作内联样式,获取元素的样式,其他样式相关的属性
前端·html
张人玉2 小时前
高德API精讲系——vue+高德API搭建前端环境页面
前端·javascript·vue.js·高德api
西西偷西瓜2 小时前
Trae IDE 读取并解析接口文档:trae-swagger-mcp 插件开发分享
前端·ide·自动化·yapi·ai编程
PineappleCoder11 小时前
性能数据别再瞎轮询了!PerformanceObserver 异步捕获 LCP/CLS,不卡主线程
前端·性能优化
PineappleCoder11 小时前
告别字体闪烁 / 首屏卡顿!preload 让关键资源 “高优先级” 提前到
前端·性能优化