diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 27629b6..3fd1037 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -17,7 +17,10 @@ "Edit", "Read", "Glob", - "Grep" + "Grep", + "Bash(cd /home/long/project/蚊子/frontend/admin && npm run build 2>&1 | head -30)", + "Bash(npm run build 2>&1 | head -40)", + "Bash(npm run build 2>&1 | head -50)" ], "deny": [] }, diff --git a/.ralph/state.md b/.ralph/state.md index dc0db72..d9ab328 100644 --- a/.ralph/state.md +++ b/.ralph/state.md @@ -6,9 +6,9 @@ - **Max Iterations**: 100 ## Current State -- **Iteration**: 5 +- **Iteration**: 6 - **Status**: In Progress -- **Current Phase**: Phase 2 - 权限核心模块后端完成 +- **Current Phase**: Phase 2 - 前端权限组件开发 ## Progress - Phase 2 - [x] Phase 1: 数据库表创建(10张表)✅ @@ -19,17 +19,24 @@ - [x] 权限判断服务 (PermissionCheckService) - 已完善 - [x] 用户角色关联 (SysUserRole + UserRoleRepository) - [x] 角色权限关联 (SysRolePermission + RolePermissionRepository) -- [ ] Phase 2: 前端页面和组件 +- [x] Phase 2: 前端权限组件 + - [x] 扩展 auth/roles.ts - 添加13个新角色和40+权限 + - [x] 创建 services/permission.ts - 权限API服务 + - [x] 创建 composables/usePermission.ts - 权限组合函数 + - [x] 创建 router/permissionGuard.ts - 路由权限守卫 + - [x] 更新路由配置 - 使用新角色系统 + - [x] 更新 App.vue, LoginView, UsersView 等使用新角色 - [ ] Phase 3: 审批流引擎 ## Completion Criteria - [x] Phase 1: 数据库表创建 - 100% - [x] Phase 2: 后端核心模块 - 100% -- [ ] Phase 2: 前端页面 - 0% +- [x] Phase 2: 前端权限组件 - 90% - [ ] Phase 3: 审批流引擎 - 0% - [ ] Phase 4: 业务模块开发 - 0% -## Recent Changes (Iteration 5) -- 创建 UserRoleRepository 实现用户角色关联查询 -- 创建 RolePermissionRepository 实现角色权限关联查询 -- 完善 PermissionCheckService 实现核心权限验证逻辑 +## Recent Changes (Iteration 6) +- 扩展前端角色权限类型定义 +- 创建权限服务和路由守卫 +- 更新前端视图使用新角色系统 +- 前端编译成功 diff --git a/frontend/admin/src/App.vue b/frontend/admin/src/App.vue index 64437ff..d8c84ad 100644 --- a/frontend/admin/src/App.vue +++ b/frontend/admin/src/App.vue @@ -27,7 +27,7 @@ 活动 角色 @@ -140,15 +150,15 @@ import { useAuthStore } from './stores/auth' import { downloadCsv } from './utils/export' import ExportFieldPanel, { type ExportField } from './components/ExportFieldPanel.vue' import { useExportFields } from './composables/useExportFields' +import { RoleLabels, type AdminRole } from './auth/roles' const route = useRoute() const auth = useAuthStore() -const selectedRole = ref(auth.role) +const selectedRole = ref(auth.role) const roleLabel = computed(() => { - if (auth.role === 'admin') return '管理员' - if (auth.role === 'operator') return '运营' - return '只读' + const role = auth.role as AdminRole + return RoleLabels[role] || '未知角色' }) const onRoleChange = () => { @@ -158,7 +168,7 @@ const onRoleChange = () => { watch( () => auth.role, (value) => { - selectedRole.value = value + selectedRole.value = value as AdminRole } ) diff --git a/frontend/admin/src/auth/adapters/DemoAuthAdapter.ts b/frontend/admin/src/auth/adapters/DemoAuthAdapter.ts index 0c63f80..e3df514 100644 --- a/frontend/admin/src/auth/adapters/DemoAuthAdapter.ts +++ b/frontend/admin/src/auth/adapters/DemoAuthAdapter.ts @@ -1,28 +1,45 @@ import type { AdminRole, Permission } from '../roles' -import { RolePermissions } from '../roles' +import { RolePermissions, RoleLabels } from '../roles' import type { AuthAdapter, AuthUser, LoginResult } from '../types' +// 角色名称映射 +const roleNameMap: Record = { + 'super_admin': '演示超级管理员', + 'system_admin': '演示系统管理员', + 'operation_manager': '演示运营经理', + 'operation_member': '演示运营成员', + 'marketing_manager': '演示市场经理', + 'marketing_member': '演示市场成员', + 'finance_manager': '演示财务经理', + 'finance_member': '演示财务成员', + 'risk_manager': '演示风控经理', + 'risk_member': '演示风控成员', + 'customer_service': '演示客服', + 'auditor': '演示审计员', + 'viewer': '演示访客' +} + const demoUser = (role: AdminRole): AuthUser => ({ id: `demo-${role}`, - name: role === 'admin' ? '演示管理员' : role === 'operator' ? '演示运营' : '演示访客', + name: roleNameMap[role] || '演示用户', email: 'demo@mosquito.local', role }) export class DemoAuthAdapter implements AuthAdapter { - private currentUser: AuthUser | null = demoUser('admin') + private currentUser: AuthUser = demoUser('super_admin') async loginWithPassword(_username: string, _password: string): Promise { - return { user: demoUser('admin') } + return { user: demoUser('super_admin') } } - async loginDemo(role: AdminRole = 'admin'): Promise { + async loginDemo(role: AdminRole = 'super_admin'): Promise { this.currentUser = demoUser(role) return { user: this.currentUser } } async logout(): Promise { - this.currentUser = null + this.currentUser = null as any } async switchRole(role: AdminRole): Promise { @@ -35,6 +52,6 @@ export class DemoAuthAdapter implements AuthAdapter { } hasPermission(role: AdminRole, permission: Permission): boolean { - return RolePermissions[role].includes(permission) + return RolePermissions[role]?.includes(permission) ?? false } } diff --git a/frontend/admin/src/auth/roles.ts b/frontend/admin/src/auth/roles.ts index ff3fda8..cec53e7 100644 --- a/frontend/admin/src/auth/roles.ts +++ b/frontend/admin/src/auth/roles.ts @@ -1,44 +1,265 @@ -export type AdminRole = 'admin' | 'operator' | 'viewer' +/** + * 角色权限类型定义 + * 对应后端数据库中的角色和权限 + */ +// 角色类型 - 对应 sys_role 表 +export type AdminRole = + | 'super_admin' // 超级管理员 + | 'system_admin' // 系统管理员 + | 'operation_manager' // 运营经理 + | 'operation_member' // 运营成员 + | 'marketing_manager' // 市场经理 + | 'marketing_member' // 市场成员 + | 'finance_manager' // 财务经理 + | 'finance_member' // 财务成员 + | 'risk_manager' // 风控经理 + | 'risk_member' // 风控成员 + | 'customer_service' // 客服 + | 'auditor' // 审计员 + | 'viewer' // 只读 + +// 权限代码 - 对应 sys_permission 表 export type Permission = - | 'view:dashboard' - | 'view:activities' - | 'view:leaderboard' - | 'view:alerts' - | 'view:notifications' - | 'manage:users' - | 'manage:rewards' - | 'manage:risk' - | 'manage:config' - | 'view:audit' + // 仪表盘 + | 'dashboard:view' + | 'dashboard:export' + // 用户管理 + | 'user:view' + | 'user:create' + | 'user:update' + | 'user:delete' + | 'user:freeze' + | 'user:unfreeze' + | 'user:certify' + | 'user:export' + + // 活动管理 + | 'activity:view' + | 'activity:create' + | 'activity:update' + | 'activity:delete' + | 'activity:publish' + | 'activity:pause' + | 'activity:end' + | 'activity:export' + + // 奖励管理 + | 'reward:view' + | 'reward:approve' + | 'reward:发放' + | 'reward:reject' + | 'reward:export' + + // 风险管理 + | 'risk:view' + | 'risk:rule' + | 'risk:audit' + | 'risk:blacklist' + | 'risk:export' + + // 审批中心 + | 'approval:view' + | 'approval:handle' + | 'approval:delegate' + + // 审计日志 + | 'audit:view' + | 'audit:export' + + // 系统配置 + | 'system:view' + | 'system:config' + | 'system:cache' + + // 权限管理 + | 'permission:view' + | 'permission:manage' + | 'role:view' + | 'role:manage' + | 'dept:view' + | 'dept:manage' + +// 数据权限范围 +export type DataScope = 'ALL' | 'DEPARTMENT' | 'OWN' + +// 角色信息 +export interface RoleInfo { + roleCode: string + roleName: string + roleLevel: number + dataScope: DataScope + description?: string + status: number +} + +// 权限信息 +export interface PermissionInfo { + permissionCode: string + permissionName: string + moduleCode: string + resourceCode: string + operationCode: string + dataScope: DataScope + description?: string +} + +// 角色权限映射 export const RolePermissions: Record = { - admin: [ - 'view:dashboard', - 'view:activities', - 'view:leaderboard', - 'view:alerts', - 'view:notifications', - 'manage:users', - 'manage:rewards', - 'manage:risk', - 'manage:config', - 'view:audit' + super_admin: [ + 'dashboard:view', 'dashboard:export', + 'user:view', 'user:create', 'user:update', 'user:delete', 'user:freeze', 'user:unfreeze', 'user:certify', 'user:export', + 'activity:view', 'activity:create', 'activity:update', 'activity:delete', 'activity:publish', 'activity:pause', 'activity:end', 'activity:export', + 'reward:view', 'reward:approve', 'reward:发放', 'reward:reject', 'reward:export', + 'risk:view', 'risk:rule', 'risk:audit', 'risk:blacklist', 'risk:export', + 'approval:view', 'approval:handle', 'approval:delegate', + 'audit:view', 'audit:export', + 'system:view', 'system:config', 'system:cache', + 'permission:view', 'permission:manage', 'role:view', 'role:manage', 'dept:view', 'dept:manage' ], - operator: [ - 'view:dashboard', - 'view:activities', - 'view:leaderboard', - 'view:alerts', - 'view:notifications', - 'manage:rewards', - 'manage:risk' + system_admin: [ + 'dashboard:view', 'dashboard:export', + 'user:view', 'user:create', 'user:update', 'user:delete', 'user:freeze', 'user:unfreeze', 'user:export', + 'activity:view', 'activity:create', 'activity:update', 'activity:delete', 'activity:export', + 'approval:view', 'approval:handle', + 'audit:view', 'audit:export', + 'system:view', 'system:config', 'system:cache', + 'permission:view', 'role:view', 'dept:view', 'dept:manage' + ], + operation_manager: [ + 'dashboard:view', 'dashboard:export', + 'user:view', 'user:export', + 'activity:view', 'activity:create', 'activity:update', 'activity:publish', 'activity:pause', 'activity:end', 'activity:export', + 'reward:view', 'reward:approve', 'reward:export', + 'approval:view', 'approval:handle' + ], + operation_member: [ + 'dashboard:view', + 'activity:view', 'activity:create', 'activity:update', + 'reward:view' + ], + marketing_manager: [ + 'dashboard:view', 'dashboard:export', + 'user:view', 'user:export', + 'activity:view', 'activity:create', 'activity:update', 'activity:publish', 'activity:export', + 'reward:view', 'reward:approve', 'reward:export', + 'approval:view', 'approval:handle' + ], + marketing_member: [ + 'dashboard:view', + 'activity:view', 'activity:create', 'activity:update' + ], + finance_manager: [ + 'dashboard:view', 'dashboard:export', + 'reward:view', 'reward:approve', 'reward:发放', 'reward:export', + 'approval:view', 'approval:handle', + 'audit:view', 'audit:export' + ], + finance_member: [ + 'dashboard:view', + 'reward:view', 'reward:approve' + ], + risk_manager: [ + 'dashboard:view', 'dashboard:export', + 'risk:view', 'risk:rule', 'risk:audit', 'risk:blacklist', 'risk:export', + 'user:view', 'user:freeze', 'user:export', + 'approval:view', 'approval:handle', + 'audit:view', 'audit:export' + ], + risk_member: [ + 'dashboard:view', + 'risk:view', 'risk:audit', 'risk:blacklist' + ], + customer_service: [ + 'dashboard:view', + 'user:view', 'user:update', 'user:certify', + 'activity:view' + ], + auditor: [ + 'dashboard:view', 'dashboard:export', + 'user:view', 'user:export', + 'activity:view', 'activity:export', + 'reward:view', 'reward:export', + 'risk:view', 'risk:export', + 'audit:view', 'audit:export', + 'system:view' ], viewer: [ - 'view:dashboard', - 'view:activities', - 'view:leaderboard', - 'view:alerts', - 'view:notifications' + 'dashboard:view', + 'user:view', + 'activity:view', + 'reward:view', + 'risk:view' ] } + +// 兼容旧版角色 +export const LegacyRoleMapping: Record = { + 'admin': 'super_admin', + 'operator': 'operation_manager', + 'viewer': 'viewer' +} + +// 角色显示名称 +export const RoleLabels: Record = { + super_admin: '超级管理员', + system_admin: '系统管理员', + operation_manager: '运营经理', + operation_member: '运营成员', + marketing_manager: '市场经理', + marketing_member: '市场成员', + finance_manager: '财务经理', + finance_member: '财务成员', + risk_manager: '风控经理', + risk_member: '风控成员', + customer_service: '客服', + auditor: '审计员', + viewer: '只读' +} + +// 权限显示名称 +export const PermissionLabels: Record = { + 'dashboard:view': '查看仪表盘', + 'dashboard:export': '导出仪表盘', + 'user:view': '查看用户', + 'user:create': '创建用户', + 'user:update': '更新用户', + 'user:delete': '删除用户', + 'user:freeze': '冻结用户', + 'user:unfreeze': '解冻用户', + 'user:certify': '实名认证', + 'user:export': '导出用户', + 'activity:view': '查看活动', + 'activity:create': '创建活动', + 'activity:update': '更新活动', + 'activity:delete': '删除活动', + 'activity:publish': '发布活动', + 'activity:pause': '暂停活动', + 'activity:end': '结束活动', + 'activity:export': '导出活动', + 'reward:view': '查看奖励', + 'reward:approve': '审批奖励', + 'reward:发放': '发放奖励', + 'reward:reject': '拒绝奖励', + 'reward:export': '导出奖励', + 'risk:view': '查看风控', + 'risk:rule': '管理风控规则', + 'risk:audit': '审核风控', + 'risk:blacklist': '管理黑名单', + 'risk:export': '导出风控', + 'approval:view': '查看审批', + 'approval:handle': '处理审批', + 'approval:delegate': '委托审批', + 'audit:view': '查看审计', + 'audit:export': '导出审计', + 'system:view': '查看系统', + 'system:config': '系统配置', + 'system:cache': '缓存管理', + 'permission:view': '查看权限', + 'permission:manage': '权限管理', + 'role:view': '查看角色', + 'role:manage': '角色管理', + 'dept:view': '查看部门', + 'dept:manage': '部门管理' +} diff --git a/frontend/admin/src/composables/usePermission.ts b/frontend/admin/src/composables/usePermission.ts new file mode 100644 index 0000000..3ed025b --- /dev/null +++ b/frontend/admin/src/composables/usePermission.ts @@ -0,0 +1,199 @@ +/** + * 权限组合式函数 + * 用于在组件中方便地使用权限功能 + */ + +import { ref, computed, onMounted } from 'vue' +import { RolePermissions, type Permission, type AdminRole, type DataScope } from '../auth/roles' +import { permissionService, type UserPermissions } from '../services/permission' + +// 当前用户权限 +const currentPermissions = ref([]) +const currentRoles = ref([]) +const currentDataScope = ref('OWN') +const isLoading = ref(false) +const isInitialized = ref(false) + +/** + * 初始化权限信息 + */ +export function usePermission() { + const initialized = computed(() => isInitialized.value) + + /** + * 从后端加载权限信息 + */ + async function loadPermissions() { + if (isLoading.value) return + isLoading.value = true + try { + const perms = await permissionService.getUserPermissions() + currentPermissions.value = perms.permissions as Permission[] + currentRoles.value = perms.roles as AdminRole[] + currentDataScope.value = perms.dataScope + isInitialized.value = true + } catch (error) { + console.error('加载权限失败:', error) + // 使用本地角色权限作为后备 + loadLocalPermissions() + } finally { + isLoading.value = false + } + } + + /** + * 使用本地角色权限(后备方案) + */ + function loadLocalPermissions() { + // 从 localStorage 获取用户角色 + const storedRole = localStorage.getItem('userRole') as AdminRole + if (storedRole && RolePermissions[storedRole]) { + currentRoles.value = [storedRole] + currentPermissions.value = RolePermissions[storedRole] + // 模拟数据权限 + if (storedRole === 'super_admin' || storedRole === 'system_admin' || storedRole === 'auditor') { + currentDataScope.value = 'ALL' + } else if (storedRole.includes('manager')) { + currentDataScope.value = 'DEPARTMENT' + } else { + currentDataScope.value = 'OWN' + } + } + isInitialized.value = true + } + + /** + * 检查是否拥有指定权限 + */ + function hasPermission(permission: Permission): boolean { + return currentPermissions.value.includes(permission) + } + + /** + * 检查是否拥有指定角色 + */ + function hasRole(role: AdminRole): boolean { + return currentRoles.value.includes(role) + } + + /** + * 检查是否拥有任意一个权限 + */ + function hasAnyPermission(permissions: Permission[]): boolean { + return permissions.some(p => hasPermission(p)) + } + + /** + * 检查是否拥有所有权限 + */ + function hasAllPermissions(permissions: Permission[]): boolean { + return permissions.every(p => hasPermission(p)) + } + + /** + * 获取当前数据权限范围 + */ + function getDataScope(): DataScope { + return currentDataScope.value + } + + /** + * 检查是否拥有所有数据权限 + */ + function hasAllDataScope(): boolean { + return currentDataScope.value === 'ALL' + } + + /** + * 检查是否拥有部门数据权限 + */ + function hasDepartmentDataScope(): boolean { + return currentDataScope.value === 'DEPARTMENT' || currentDataScope.value === 'ALL' + } + + /** + * 获取当前用户角色列表 + */ + function getRoles(): AdminRole[] { + return currentRoles.value + } + + /** + * 获取当前用户权限列表 + */ + function getPermissions(): Permission[] { + return currentPermissions.value + } + + /** + * 设置用户角色(用于本地开发/测试) + */ + function setUserRole(role: AdminRole) { + localStorage.setItem('userRole', role) + currentRoles.value = [role] + currentPermissions.value = RolePermissions[role] || [] + if (role === 'super_admin' || role === 'system_admin' || role === 'auditor') { + currentDataScope.value = 'ALL' + } else if (role.includes('manager')) { + currentDataScope.value = 'DEPARTMENT' + } else { + currentDataScope.value = 'OWN' + } + isInitialized.value = true + } + + /** + * 清除权限信息 + */ + function clearPermissions() { + currentPermissions.value = [] + currentRoles.value = [] + currentDataScope.value = 'OWN' + isInitialized.value = false + localStorage.removeItem('userRole') + } + + // 初始化时自动加载 + onMounted(() => { + if (!isInitialized.value) { + loadPermissions() + } + }) + + return { + // 状态 + initialized, + isLoading, + currentPermissions, + currentRoles, + currentDataScope, + + // 方法 + loadPermissions, + hasPermission, + hasRole, + hasAnyPermission, + hasAllPermissions, + getDataScope, + hasAllDataScope, + hasDepartmentDataScope, + getRoles, + getPermissions, + setUserRole, + clearPermissions + } +} + +/** + * 权限指令 composable + * 用于模板中快速检查权限 + */ +export function usePermissionCheck() { + const { hasPermission, hasRole, hasAnyPermission } = usePermission() + + return { + can: hasPermission, + is: hasRole, + canAny: hasAnyPermission + } +} diff --git a/frontend/admin/src/router/index.ts b/frontend/admin/src/router/index.ts index 740dff4..ed3c422 100644 --- a/frontend/admin/src/router/index.ts +++ b/frontend/admin/src/router/index.ts @@ -16,6 +16,26 @@ import ActivityConfigWizardView from '../views/ActivityConfigWizardView.vue' import ApprovalCenterView from '../views/ApprovalCenterView.vue' import UserDetailView from '../views/UserDetailView.vue' import PermissionsView from '../views/PermissionsView.vue' +import type { AdminRole } from '../auth/roles' + +// 路由权限配置 - 使用新的角色系统 +const routeRoles: Record = { + 'dashboard': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member', 'finance_manager', 'finance_member', 'risk_manager', 'risk_member', 'customer_service', 'auditor', 'viewer'], + 'activities': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member', 'customer_service', 'auditor', 'viewer'], + 'activity-create': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member'], + 'activity-detail': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member', 'customer_service', 'auditor', 'viewer'], + 'activity-config': ['super_admin', 'system_admin', 'operation_manager', 'marketing_manager'], + 'users': ['super_admin', 'system_admin'], + 'user-detail': ['super_admin', 'system_admin'], + 'user-invite': ['super_admin', 'system_admin'], + 'rewards': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member', 'finance_manager', 'finance_member', 'auditor', 'viewer'], + 'risk': ['super_admin', 'system_admin', 'risk_manager', 'risk_member', 'auditor'], + 'audit': ['super_admin', 'system_admin', 'finance_manager', 'auditor'], + 'approvals': ['super_admin', 'system_admin', 'operation_manager', 'marketing_manager', 'finance_manager', 'risk_manager'], + 'permissions': ['super_admin', 'system_admin'], + 'notifications': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member', 'finance_manager', 'finance_member', 'risk_manager', 'risk_member', 'customer_service', 'auditor', 'viewer'], + 'system': ['super_admin', 'system_admin', 'auditor'] +} const router = createRouter({ history: createWebHistory(), @@ -29,91 +49,91 @@ const router = createRouter({ path: '/', name: 'dashboard', component: DashboardView, - meta: { roles: ['admin', 'operator', 'viewer'] } + meta: { roles: routeRoles.dashboard } }, { path: '/activities', name: 'activities', component: ActivityListView, - meta: { roles: ['admin', 'operator', 'viewer'] } + meta: { roles: routeRoles.activities } }, { path: '/activities/new', name: 'activity-create', component: ActivityCreateView, - meta: { roles: ['admin', 'operator'] } + meta: { roles: routeRoles['activity-create'] } }, { path: '/activities/:id', name: 'activity-detail', component: ActivityDetailView, - meta: { roles: ['admin', 'operator', 'viewer'] } + meta: { roles: routeRoles['activity-detail'] } }, { path: '/activities/config', name: 'activity-config', component: ActivityConfigWizardView, - meta: { roles: ['admin', 'operator'] } + meta: { roles: routeRoles['activity-config'] } }, { path: '/activities/:id', name: 'activity-detail', component: ActivityDetailView, - meta: { roles: ['admin', 'operator', 'viewer'] } + meta: { roles: routeRoles['activity-detail'] } }, { path: '/users', name: 'users', component: UsersView, - meta: { roles: ['admin'] } + meta: { roles: routeRoles.users } }, { path: '/users/:id', name: 'user-detail', component: UserDetailView, - meta: { roles: ['admin'] } + meta: { roles: routeRoles['user-detail'] } }, { path: '/users/invite', name: 'user-invite', component: InviteUserView, - meta: { roles: ['admin'] } + meta: { roles: routeRoles['user-invite'] } }, { path: '/rewards', name: 'rewards', component: RewardsView, - meta: { roles: ['admin', 'operator'] } + meta: { roles: routeRoles.rewards } }, { path: '/risk', name: 'risk', component: RiskView, - meta: { roles: ['admin', 'operator'] } + meta: { roles: routeRoles.risk } }, { path: '/audit', name: 'audit', component: AuditLogView, - meta: { roles: ['admin'] } + meta: { roles: routeRoles.audit } }, { path: '/approvals', name: 'approvals', component: ApprovalCenterView, - meta: { roles: ['admin'] } + meta: { roles: routeRoles.approvals } }, { path: '/permissions', name: 'permissions', component: PermissionsView, - meta: { roles: ['admin'] } + meta: { roles: routeRoles.permissions } }, { path: '/notifications', name: 'notifications', component: NotificationsView, - meta: { roles: ['admin', 'operator', 'viewer'] } + meta: { roles: routeRoles.notifications } }, { path: '/403', @@ -126,10 +146,10 @@ const router = createRouter({ router.beforeEach(async (to) => { const auth = useAuthStore() if (!auth.isAuthenticated && to.name !== 'login') { - await auth.loginDemo('admin') + await auth.loginDemo('super_admin') } - const roles = (to.meta?.roles as string[] | undefined) ?? null - if (roles && !roles.includes(auth.role)) { + const roles = (to.meta?.roles as AdminRole[] | undefined) ?? null + if (roles && !roles.includes(auth.role as AdminRole)) { return { name: 'forbidden' } } return true diff --git a/frontend/admin/src/router/permissionGuard.ts b/frontend/admin/src/router/permissionGuard.ts new file mode 100644 index 0000000..98ea955 --- /dev/null +++ b/frontend/admin/src/router/permissionGuard.ts @@ -0,0 +1,140 @@ +/** + * 权限路由守卫 + * 根据用户权限控制页面访问 + */ + +import type { Router } from 'vue-router' +import type { Permission } from '../auth/roles' +import { usePermission } from '../composables/usePermission' + +export interface RoutePermission { + /** 路由名称 */ + name: string + /** 所需权限 */ + requiredPermissions?: Permission[] + /** 所需角色 */ + requiredRoles?: string[] + /** 是否需要登录 */ + requiresAuth?: boolean +} + +/** + * 默认路由权限配置 + */ +export const routePermissions: RoutePermission[] = [ + // 仪表盘 + { name: 'Dashboard', requiredPermissions: ['dashboard:view'] }, + + // 用户管理 + { name: 'Users', requiredPermissions: ['user:view'] }, + { name: 'UserDetail', requiredPermissions: ['user:view'] }, + + // 活动管理 + { name: 'Activities', requiredPermissions: ['activity:view'] }, + { name: 'ActivityDetail', requiredPermissions: ['activity:view'] }, + { name: 'ActivityCreate', requiredPermissions: ['activity:create'] }, + { name: 'ActivityConfigWizard', requiredPermissions: ['activity:create'] }, + + // 奖励管理 + { name: 'Rewards', requiredPermissions: ['reward:view'] }, + + // 风险管理 + { name: 'Risk', requiredPermissions: ['risk:view'] }, + + // 审批中心 + { name: 'Approvals', requiredPermissions: ['approval:view'] }, + + // 审计日志 + { name: 'AuditLogs', requiredPermissions: ['audit:view'] }, + + // 系统配置 + { name: 'System', requiredPermissions: ['system:view'] }, + + // 权限管理 + { name: 'Permissions', requiredPermissions: ['permission:view'] }, + + // 邀请用户 + { name: 'InviteUser', requiredPermissions: ['user:create'] }, + + // 通知 + { name: 'Notifications', requiredPermissions: ['dashboard:view'] } +] + +/** + * 创建权限路由守卫 + */ +export function createPermissionGuard(router: Router) { + const { hasPermission, hasRole, initialized } = usePermission() + + router.beforeEach(async (to, from, next) => { + // 等待权限初始化 + if (!initialized.value) { + await new Promise(resolve => { + const checkInit = setInterval(() => { + if (initialized.value) { + clearInterval(checkInit) + resolve(true) + } + }, 100) + // 超时5秒后继续 + setTimeout(() => { + clearInterval(checkInit) + resolve(true) + }, 5000) + }) + } + + // 检查路由权限 + const routePermission = routePermissions.find(rp => rp.name === to.name) + + if (routePermission) { + // 检查所需权限 + if (routePermission.requiredPermissions?.length) { + const hasRequired = routePermission.requiredPermissions.some(permission => + hasPermission(permission as Permission) + ) + if (!hasRequired) { + // 没有权限,跳转到403页面 + return next({ name: 'Forbidden' }) + } + } + + // 检查所需角色 + if (routePermission.requiredRoles?.length) { + const hasRequiredRole = routePermission.requiredRoles.some(role => + hasRole(role as any) + ) + if (!hasRequiredRole) { + return next({ name: 'Forbidden' }) + } + } + } + + next() + }) +} + +/** + * 检查路由是否有权限访问 + */ +export function canAccessRoute(routeName: string): boolean { + const { hasPermission } = usePermission() + const routePermission = routePermissions.find(rp => rp.name === routeName) + + if (!routePermission) { + return true // 没有配置权限的路由默认允许访问 + } + + if (routePermission.requiredPermissions?.length) { + return routePermission.requiredPermissions.some(permission => + hasPermission(permission as Permission) + ) + } + + if (routePermission.requiredRoles?.length) { + const { hasRole } = usePermission() + return routePermission.requiredRoles.some(role => hasRole(role as any)) + } + + return true +} diff --git a/frontend/admin/src/services/demo/DemoDataService.ts b/frontend/admin/src/services/demo/DemoDataService.ts index 08fcd47..d5f9f17 100644 --- a/frontend/admin/src/services/demo/DemoDataService.ts +++ b/frontend/admin/src/services/demo/DemoDataService.ts @@ -177,8 +177,8 @@ const demoAlerts: DemoAlert[] = [ ] const demoUsers: DemoUser[] = [ - { id: 'u-1001', name: '王晨', email: 'wangchen@demo.com', role: 'operator', status: '正常', managerName: '演示管理员' }, - { id: 'u-1002', name: '李雪', email: 'lixue@demo.com', role: 'operator', status: '正常', managerName: '演示管理员' }, + { id: 'u-1001', name: '王晨', email: 'wangchen@demo.com', role: 'operation_manager', status: '正常', managerName: '演示管理员' }, + { id: 'u-1002', name: '李雪', email: 'lixue@demo.com', role: 'operation_member', status: '正常', managerName: '演示管理员' }, { id: 'u-1003', name: '周宁', email: 'zhouning@demo.com', role: 'viewer', status: '冻结', managerName: '王晨' } ] @@ -236,7 +236,7 @@ export const demoDataService = { { id: 'invite-1', email: 'newuser@demo.com', - role: 'operator', + role: 'operation_manager', status: '待接受', invitedAt: isoDays(-1) }, @@ -251,7 +251,7 @@ export const demoDataService = { { id: 'invite-3', email: 'accepted@demo.com', - role: 'admin', + role: 'super_admin', status: '已接受', invitedAt: isoDays(-6), acceptedAt: isoDays(-4) @@ -263,8 +263,8 @@ export const demoDataService = { { id: 'role-1', userId: 'u-1002', - currentRole: 'operator', - targetRole: 'admin', + currentRole: 'operation_member', + targetRole: 'operation_manager', reason: '需要管理活动权限', status: '待审批', requestedAt: isoDays(-2) diff --git a/frontend/admin/src/services/permission.ts b/frontend/admin/src/services/permission.ts new file mode 100644 index 0000000..25e0620 --- /dev/null +++ b/frontend/admin/src/services/permission.ts @@ -0,0 +1,138 @@ +/** + * 权限服务 - 与后端权限API交互 + */ + +import type { AdminRole, Permission, DataScope, RoleInfo, PermissionInfo } from '../auth/roles' + +export interface UserPermissions { + userId: number + roles: string[] + permissions: string[] + dataScope: DataScope +} + +export interface ApiResponse { + code: number + data: T + message?: string +} + +/** + * 权限服务类 + */ +class PermissionService { + private baseUrl = '/api' + + /** + * 获取当前用户权限信息 + */ + async getUserPermissions(): Promise { + const response = await fetch(`${this.baseUrl}/permissions/current`, { + credentials: 'include' + }) + const result = await response.json() as ApiResponse + if (result.code !== 200) { + throw new Error(result.message || '获取权限失败') + } + return result.data + } + + /** + * 检查用户是否拥有指定权限 + */ + async hasPermission(permissionCode: Permission): Promise { + const response = await fetch(`${this.baseUrl}/permissions/check?permissionCode=${permissionCode}`, { + credentials: 'include' + }) + const result = await response.json() as ApiResponse + return result.code === 200 && result.data + } + + /** + * 检查用户是否拥有指定角色 + */ + async hasRole(roleCode: AdminRole): Promise { + const response = await fetch(`${this.baseUrl}/permissions/role?roleCode=${roleCode}`, { + credentials: 'include' + }) + const result = await response.json() as ApiResponse + return result.code === 200 && result.data + } + + /** + * 获取用户数据权限范围 + */ + async getDataScope(): Promise { + const response = await fetch(`${this.baseUrl}/permissions/datascope`, { + credentials: 'include' + }) + const result = await response.json() as ApiResponse + if (result.code !== 200) { + return 'OWN' // 默认个人权限 + } + return result.data + } + + /** + * 获取所有角色列表 + */ + async getRoles(): Promise { + const response = await fetch(`${this.baseUrl}/roles`, { + credentials: 'include' + }) + const result = await response.json() as ApiResponse + if (result.code !== 200) { + throw new Error(result.message || '获取角色列表失败') + } + return result.data + } + + /** + * 获取所有权限列表 + */ + async getPermissions(): Promise { + const response = await fetch(`${this.baseUrl}/permissions`, { + credentials: 'include' + }) + const result = await response.json() as ApiResponse + if (result.code !== 200) { + throw new Error(result.message || '获取权限列表失败') + } + return result.data + } + + /** + * 分配角色给用户 + */ + async assignRole(userId: number, roleIds: number[]): Promise { + const response = await fetch(`${this.baseUrl}/users/${userId}/roles`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify({ roleIds }) + }) + const result = await response.json() as ApiResponse + if (result.code !== 200) { + throw new Error(result.message || '分配角色失败') + } + } + + /** + * 分配权限给角色 + */ + async assignPermissions(roleId: number, permissionIds: number[]): Promise { + const response = await fetch(`${this.baseUrl}/roles/${roleId}/permissions`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify({ permissionIds }) + }) + const result = await response.json() as ApiResponse + if (result.code !== 200) { + throw new Error(result.message || '分配权限失败') + } + } +} + +export const permissionService = new PermissionService() +export default permissionService diff --git a/frontend/admin/src/stores/__tests__/users.test.ts b/frontend/admin/src/stores/__tests__/users.test.ts index 02cb5a9..80246e9 100644 --- a/frontend/admin/src/stores/__tests__/users.test.ts +++ b/frontend/admin/src/stores/__tests__/users.test.ts @@ -6,7 +6,7 @@ describe('useUserStore invites', () => { it('expires invite', () => { setActivePinia(createPinia()) const store = useUserStore() - store.init([], [{ id: 'invite-1', email: 'a@demo.com', role: 'operator', status: '待接受', invitedAt: '2026-02-01T00:00:00Z' }], []) + store.init([], [{ id: 'invite-1', email: 'a@demo.com', role: 'operation_manager', status: '待接受', invitedAt: '2026-02-01T00:00:00Z' }], []) store.expireInvite('invite-1') diff --git a/frontend/admin/src/stores/auth.ts b/frontend/admin/src/stores/auth.ts index ddb8a3a..55f7a65 100644 --- a/frontend/admin/src/stores/auth.ts +++ b/frontend/admin/src/stores/auth.ts @@ -10,9 +10,9 @@ export const useAuthStore = defineStore('auth', { state: (): AuthState => ({ user: { id: 'demo-admin', - name: '演示管理员', + name: '演示超级管理员', email: 'demo@mosquito.local', - role: 'admin' + role: 'super_admin' }, mode: (import.meta.env.VITE_MOSQUITO_AUTH_MODE as AuthState['mode']) || 'demo' }), @@ -25,7 +25,7 @@ export const useAuthStore = defineStore('auth', { } }, actions: { - async loginDemo(role: AdminRole = 'admin') { + async loginDemo(role: AdminRole = 'super_admin') { const result = await demoAdapter.loginDemo(role) this.user = result.user this.mode = 'demo' diff --git a/frontend/admin/src/views/InviteUserView.vue b/frontend/admin/src/views/InviteUserView.vue index d7cced2..1b6572e 100644 --- a/frontend/admin/src/views/InviteUserView.vue +++ b/frontend/admin/src/views/InviteUserView.vue @@ -13,9 +13,19 @@
@@ -75,6 +85,7 @@ import { useAuditStore } from '../stores/audit' import { useUserStore } from '../stores/users' import { useDataService } from '../services' import ListSection from '../components/ListSection.vue' +import { RoleLabels, type AdminRole } from '../auth/roles' const auditStore = useAuditStore() const userStore = useUserStore() @@ -85,7 +96,7 @@ const page = ref(0) const pageSize = 6 const form = ref({ email: '', - role: '运营' + role: 'operation_manager' }) onMounted(async () => { @@ -94,18 +105,16 @@ onMounted(async () => { }) const roleLabel = (role: string) => { - if (role === 'admin') return '管理员' - if (role === 'operator') return '运营' - return '只读' + return RoleLabels[role as AdminRole] || role } const formatDate = (value: string) => new Date(value).toLocaleString('zh-CN') const sendInvite = () => { - userStore.addInvite(form.value.email || '未填写邮箱', form.value.role === '管理员' ? 'admin' : form.value.role === '运营' ? 'operator' : 'viewer') + userStore.addInvite(form.value.email || '未填写邮箱', form.value.role as AdminRole) auditStore.addLog('发送用户邀请', form.value.email || '未填写邮箱') form.value.email = '' - form.value.role = '运营' + form.value.role = 'operation_manager' } const resendInvite = (id: string) => { diff --git a/frontend/admin/src/views/LoginView.vue b/frontend/admin/src/views/LoginView.vue index e0c7ab5..da726e7 100644 --- a/frontend/admin/src/views/LoginView.vue +++ b/frontend/admin/src/views/LoginView.vue @@ -36,7 +36,7 @@ const auth = useAuthStore() const router = useRouter() const loginDemo = async () => { - await auth.loginDemo('admin') + await auth.loginDemo('super_admin') await router.push('/') } diff --git a/frontend/admin/src/views/PermissionsView.vue b/frontend/admin/src/views/PermissionsView.vue index 5bd5ccb..692eb99 100644 --- a/frontend/admin/src/views/PermissionsView.vue +++ b/frontend/admin/src/views/PermissionsView.vue @@ -46,8 +46,9 @@ import { computed } from 'vue' import { RolePermissions, type AdminRole, type Permission } from '../auth/roles' const roles: { key: AdminRole; label: string }[] = [ - { key: 'admin', label: '管理员' }, - { key: 'operator', label: '运营' }, + { key: 'super_admin', label: '超级管理员' }, + { key: 'system_admin', label: '系统管理员' }, + { key: 'operation_manager', label: '运营经理' }, { key: 'viewer', label: '只读' } ] @@ -55,21 +56,20 @@ const permissionSections: { group: string; items: { key: Permission; label: stri { group: '可视化与运营查看', items: [ - { key: 'view:dashboard', label: '看板查看', description: '访问运营概览与关键指标' }, - { key: 'view:activities', label: '活动查看', description: '查看活动列表与详情信息' }, - { key: 'view:leaderboard', label: '排行榜查看', description: '查看活动排行榜与排名' }, - { key: 'view:alerts', label: '告警查看', description: '查看风控与系统告警信息' }, - { key: 'view:notifications', label: '通知查看', description: '查看审批与系统通知' } + { key: 'dashboard:view', label: '看板查看', description: '访问运营概览与关键指标' }, + { key: 'activity:view', label: '活动查看', description: '查看活动列表与详情信息' }, + { key: 'dashboard:export', label: '导出数据', description: '导出看板数据' }, + { key: 'risk:view', label: '告警查看', description: '查看风控与系统告警信息' } ] }, { group: '运营与风控管理', items: [ - { key: 'manage:users', label: '用户管理', description: '管理运营成员、审批与角色' }, - { key: 'manage:rewards', label: '奖励管理', description: '配置与执行奖励发放' }, - { key: 'manage:risk', label: '风控管理', description: '维护风控规则与黑名单' }, - { key: 'manage:config', label: '配置管理', description: '管理系统配置与策略' }, - { key: 'view:audit', label: '审计查看', description: '查看关键操作审计日志' } + { key: 'user:view', label: '用户管理', description: '管理运营成员、审批与角色' }, + { key: 'reward:view', label: '奖励管理', description: '配置与执行奖励发放' }, + { key: 'risk:rule', label: '风控管理', description: '维护风控规则与黑名单' }, + { key: 'system:config', label: '配置管理', description: '管理系统配置与策略' }, + { key: 'audit:view', label: '审计查看', description: '查看关键操作审计日志' } ] } ] diff --git a/frontend/admin/src/views/UsersView.vue b/frontend/admin/src/views/UsersView.vue index 2cf22a4..447edbb 100644 --- a/frontend/admin/src/views/UsersView.vue +++ b/frontend/admin/src/views/UsersView.vue @@ -136,6 +136,7 @@ import { useUserStore } from '../stores/users' import { useActivityStore } from '../stores/activities' import { useAuthStore } from '../stores/auth' import ListSection from '../components/ListSection.vue' +import { RoleLabels, type AdminRole } from '../auth/roles' type UserItem = { id: string @@ -190,14 +191,12 @@ const toggleUser = (user: UserItem) => { } const requestRole = (user: UserItem) => { - userStore.requestRoleChange(user.id, 'admin', '需要更高权限') + userStore.requestRoleChange(user.id, 'super_admin' as AdminRole, '需要更高权限') auditStore.addLog('提交角色变更申请', user.name) } const roleLabel = (role: string) => { - if (role === 'admin') return '管理员' - if (role === 'operator') return '运营' - return '只读' + return RoleLabels[role as AdminRole] || role } const resolveUserId = () => {