工单系统权限模型设计(RBAC)
工单系统的权限需求比一般系统复杂:不同角色能看到的工单范围不同,能执行的操作也不同。比如普通司机只能看自己的工单,调度员能看全部,管理员能看所有还能删除。
权限模型
三层控制
用户 → 角色 → 功能权限
→ 角色 → 数据权限- 功能权限:能做什么(创建工单、处理工单、删除工单)
- 数据权限:能看什么(自己的工单、本部门工单、全部工单)
角色定义
ts
interface Role {
id: string
name: string
permissions: Permission[] // 功能权限
dataScope: DataScope // 数据范围
}
interface Permission {
resource: string // 'work_order' | 'report' | 'user'
action: string // 'create' | 'read' | 'update' | 'delete'
}
type DataScope = 'self' | 'department' | 'all'前端权限实现
路由守卫
ts
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'
}
}
})按钮级权限
vue
<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>数据范围过滤
ts
// 根据数据范围构建查询参数
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 {}
}
}权限缓存策略
权限数据不常变,但每次请求都查数据库没必要:
ts
// 登录时拉取权限,存 localStorage
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再重新添加 - 数据范围要后端强制过滤 — 前端可以隐藏按钮,但不能依赖前端做数据过滤