工单系统权限模型设计(RBAC)
工单系统的权限需求比一般系统复杂:不同角色能看到的工单范围不同,能执行的操作也不同。比如普通司机只能看自己的工单,调度员能看全部,管理员能看所有还能删除。
权限模型
三层控制
1 2
| 用户 → 角色 → 功能权限 → 角色 → 数据权限
|
- 功能权限:能做什么(创建工单、处理工单、删除工单)
- 数据权限:能看什么(自己的工单、本部门工单、全部工单)
角色定义
1 2 3 4 5 6 7 8 9 10 11 12 13
| interface Role { id: string name: string permissions: Permission[] dataScope: DataScope }
interface Permission { resource: string action: string }
type DataScope = 'self' | 'department' | 'all'
|
前端权限实现
路由守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const router = createRouter({ routes: [ { path: '/admin', meta: { requiresRole: 'admin' }, children: [...] } ] })
router.beforeEach((to) => { const userStore = useUserStore()
if (to.meta.requiresRole) { if (!userStore.hasRole(to.meta.requiresRole)) { return '/403' } } })
|
按钮级权限
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <button v-if="hasPermission('work_order', 'delete')" @click="deleteOrder"> 删除工单 </button> </template>
<script setup lang="ts"> function hasPermission(resource: string, action: string): boolean { const userStore = useUserStore() return userStore.permissions.some( p => p.resource === resource && p.action === action ) } </script>
|
数据范围过滤
1 2 3 4 5 6 7 8 9 10 11
| function buildDataFilter(user: User): Record<string, any> { switch (user.dataScope) { case 'self': return { assigneeId: user.id } case 'department': return { departmentId: user.departmentId } case 'all': return {} } }
|
权限缓存策略
权限数据不常变,但每次请求都查数据库没必要:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| async function login(username: string, password: string) { const { token, user } = await api.login(username, password) localStorage.setItem('token', token) localStorage.setItem('user', JSON.stringify(user))
const permissions = await api.getPermissions(user.id) localStorage.setItem('permissions', JSON.stringify(permissions)) }
function onPermissionChanged() { const permissions = await api.getPermissions(currentUser.id) localStorage.setItem('permissions', JSON.stringify(permissions)) }
|
踩坑记录
- 权限判断放前端只是体验优化 — 真正的权限控制必须在后端
- 别把权限存 Vuex — 页面刷新就丢了,存 localStorage 或每次登录拉取
- 动态路由要小心 — 权限变更后需要
router.removeRoute 再重新添加
- 数据范围要后端强制过滤 — 前端可以隐藏按钮,但不能依赖前端做数据过滤