一、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 通用)
- 后端只返回 perms 数组 → 前端根据它控制 UI
- 前端菜单 / 路由根据 perms 过滤
- 按钮或页面模块用 perms.includes(...) 控制显示
- 后端接口 仍然要校验 perms → 真正的安全保障
菜单写死在前端,通过权限点 v-if 显示/隐藏(大厂常用)
流程:
- 菜单是前端写死的(存 routes.ts)
- 用户登录,后台只返回权限点(字符串数组,如 ['user:add', 'user:delete'])
- 前端根据权限点过滤导航栏、路由
-
示例
{
path: '/user',
name: 'User',
meta: { title: '用户管理', perms: ['user:view'] }
}
✔ 优点:
- 前端掌控路由、菜单,灵活更容易维护
- 不需要后端维护菜单表
- 常用于大厂 组件化、前后端分仓 的模式
- 权限点即可驱动显示/隐藏按钮
前端菜单只是"显示控制",不是安全控制
就算你用后台动态返回菜单,也不能防止用户:
- 手动输入地址
- 用 F12 改源码
- 注入 JS
- 抓包直接调接口
真正的安全是 API 权限验证
所以前端写死菜单 + 权限过滤 = 大厂最佳体验
| 措施 | 目的 |
|---|---|
| 前端菜单过滤 | 体验层:让用户不看到没权限的东西 |
| 后端接口校验 | 安全层:真正的权限防护(不可绕过) |
后端控制实际操作
- 所有 API 请求都要校验用户权限
- 后端拿 token → 查角色 → 查 perms → 判断是否允许该操作
- 例如:POST /api/user/add
- 用户 perms 包含 user:add → 允许
- 用户 perms 不包含 user:add → 返回 403 Forbidden
重点
- 前端 不需要 每次请求都传 user:add
- 后端 通过 token 或 session 自动知道 谁在操作,然后根据权限码判断是否允许
- 前端只是可视化限制,真正的安全由后端负责
最终总结(Vue + React 通用)
| 功能点 | Vue3 实现 | React 实现 |
|---|---|---|
| 菜单写死 | asyncRoutes | routes.tsx |
| 权限点存储 | Pinia | Zustand/Redux |
| 菜单过滤 | filterRoutes() | filterRoutes() |
| 按钮权限 | v-if hasPerm() | perms.includes() |
| 安全关键 | 必须后端接口校验 | 必须后端接口校验 |
另外一种方案
菜单从后台返回(动态菜单),超级管理员添加菜单,登录后通过用户权限返回菜单的方式
流程:
- 超级管理员在后台添加菜单
- 菜单存数据库
- 用户登录 → 后端根据权限返回"可访问菜单树"
- 前端根据菜单渲染侧边栏、路由
✔ 优点:
- 菜单可配置,前端不用改代码
- 多系统保持一致
- 超级管理员可随时调整菜单、权限
✘ 缺点: - 需要后端写复杂的"菜单树 + 角色过滤"
- 前端路由也要动态生成
- 有些公司根本不想把菜单配到数据库里
现在已经放弃这个方案了 - 大厂前端团队多,菜单都是前端组件结构
- 菜单不需要天天变,写死才稳定
- 权限只是控制菜单是否渲染,不需要写数据表