返回正文
Are you an LLM? You can read better optimized documentation at /zh-CN/blog/2025-07/frontend_permission.md for this page in Markdown format
后台管理系统权限控制实战:从入门到优雅实现
今天想和大家聊聊后台管理系统里一个老生常谈但又极其重要的话题——用户权限控制。
权限控制这玩意儿,说简单也简单,说复杂也复杂。简单是因为核心逻辑就那么点东西,复杂是因为在实际业务中,不同公司的需求千奇百怪,稍不注意就会写出又臭又长的代码。今天我就结合自己的踩坑经验,分享一套既清晰又灵活的权限控制方案。
一、权限控制的核心是什么?
权限控制的本质就两点:
- 用户能不能看到这个页面/菜单?(路由权限)
- 用户能不能操作这个按钮/功能?(功能权限)
听起来很简单对吧?但实际开发中,很多同学容易把这两块逻辑混在一起写,最后代码像一团乱麻,维护起来想哭。
二、路由权限:如何控制用户能访问哪些页面?
基础方案:动态路由
最常见的做法是根据用户角色动态生成可访问的路由。比如管理员能看到所有页面,普通用户只能看部分页面。
假设我们有一个后台管理系统,菜单结构如下:
ts
const allRoutes = [
{
path: '/dashboard',
name: 'Dashboard',
meta: { requiresAuth: true, role: ['admin', 'user'] }
},
{
path: '/user-management',
name: 'UserManagement',
meta: { requiresAuth: true, role: ['admin'] } // 只有admin能看
}
]
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
在路由守卫里,我们可以这样检查:
ts
router.beforeEach((to, from, next) => {
const myRole = store.getters.userRole // 假设从Vuex获取用户角色
if (to.meta.requiresAuth && !to.meta.role.includes(myRole)) {
next('/403') // 没权限就去403页面
} else {
next()
}
})
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
更精细化的方案:后端返回路由表
有些公司为了安全,连前端路由结构都不暴露。这时候可以让后端返回用户可访问的路由结构,前端再动态注册
ts
// 假设后端返回的数据格式
const backendRoutes = [
{
path: '/dashboard',
component: 'Dashboard' // 后端返回组件名,前端需要做映射
}
]
// 前端转换
const loadComponent = (name) => () => import(`@/views/${name}.vue`)
const formattedRoutes = backendRoutes.map(route => ({
...route,
component: loadComponent(route.component)
}))
router.addRoutes(formattedRoutes) // 动态添加路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
坑点预警:动态路由的404页面要最后添加,否则会被提前匹配到。
三、功能权限:如何优雅地控制按钮显示?
方案1:v-permission指令(推荐)
比起在模板里写一堆v-if,用自定义指令更优雅:
ts
// 注册指令
Vue.directive('permission', {
inserted(el, binding, vnode) {
const { value } = binding
const myPermissions = store.getters.permissions
if (!myPermissions.includes(value)) {
el.parentNode?.removeChild(el) // 直接移除元素
}
}
})
// 使用方式
<button v-permission="'user:delete'">删除用户</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
ts
// 自定义指令
app.directive('permission', {
mounted(el, binding) {
if (Session.get('userInfo')) {
const permission = binding.value
const buttonList = Session.get('userInfo')['buttonList']
const hasPermission = buttonList.includes(permission)
if (!hasPermission) {
el.style.display = 'none'
}
}
},
updated(el, binding, vnode, oldVnode) {
if (Session.get('userInfo')) {
const permission = binding.value
const buttonList = Session.get('userInfo')['buttonList']
const hasPermission = buttonList.includes(permission)
el.style.display = hasPermission ? 'inherit' : 'none'
}
}
})
app.mount('#app')
// 使用方式
<a-button
type="primary"
v-permission="'Nest:assign:AddUser'"
v-if="hasUserButtons"
:disabled="!currentSelectRoles[0]"
@click="showModal(ModalType.AddUser)"
>
<PlusOutlined /> 新增用户
</a-button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
方案2:权限校验函数
对于更复杂的场景(比如某个按钮需要同时满足多个权限),可以封装一个校验函数:
ts
// 权限检查工具
function checkPermission(neededPermissions) {
const myPermissions = store.getters.permissions
if (Array.isArray(neededPermissions)) {
return neededPermissions.some(perm => myPermissions.includes(perm))
}
return myPermissions.includes(neededPermissions)
}
// 在组件中使用
computed: {
canEdit() {
return checkPermission(['article:edit', 'article:admin'])
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
四、高级技巧:权限与组件解耦
有时候权限逻辑会污染组件代码,这时候可以考虑用高阶组件或Render Props的方式解耦:
ts
// 权限包装组件
<Permission :value="'user:create'">
<button>创建用户</button>
</Permission>
// Permission组件实现
export default {
render() {
if (checkPermission(this.value)) {
return this.$slots.default[0]
}
return null
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
五、性能优化小贴士
- 缓存权限数据:用户权限通常不会频繁变化,可以存到localStorage避免重复请求
- 按需加载权限模块:对于大型系统,可以分模块加载权限配置
- 服务端配合:让后端返回权限树的hash值,只有变化时才重新拉取
六、总结
权限控制的关键在于分层处理:
- 路由层:控制页面级的访问权限
- 视图层:控制按钮/模块的显示隐藏
- API层:最后一道防线(前端永远不可信!)
“好的权限系统是用户无感知的,但开发者必须心中有数” 。
VN/A |
本站访客数
--次 本站总访问量
--人次