Initial commit
This commit is contained in:
349
src/pages/Roles.tsx
Normal file
349
src/pages/Roles.tsx
Normal file
@@ -0,0 +1,349 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { rolesApi, type RoleInfo, type Permission } from '@/api'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { RefreshCw, Loader2, Users, Shield, Check } from 'lucide-react'
|
||||
|
||||
// 角色颜色映射
|
||||
const roleColors: Record<string, { bg: string; text: string; border: string }> = {
|
||||
super_admin: { bg: 'bg-purple-100', text: 'text-purple-700', border: 'border-purple-300' },
|
||||
admin: { bg: 'bg-red-100', text: 'text-red-700', border: 'border-red-300' },
|
||||
moderator: { bg: 'bg-orange-100', text: 'text-orange-700', border: 'border-orange-300' },
|
||||
user: { bg: 'bg-blue-100', text: 'text-blue-700', border: 'border-blue-300' },
|
||||
}
|
||||
|
||||
// 角色名称映射
|
||||
const roleNameMap: Record<string, string> = {
|
||||
super_admin: '超级管理员',
|
||||
admin: '管理员',
|
||||
moderator: '版主',
|
||||
user: '普通用户',
|
||||
}
|
||||
|
||||
// 资源名称映射
|
||||
const resourceNames: Record<string, string> = {
|
||||
users: '用户',
|
||||
posts: '帖子',
|
||||
comments: '评论',
|
||||
groups: '群组',
|
||||
messages: '消息',
|
||||
reports: '举报',
|
||||
settings: '设置',
|
||||
logs: '日志',
|
||||
}
|
||||
|
||||
// 操作名称映射
|
||||
const actionNames: Record<string, string> = {
|
||||
read: '读取',
|
||||
write: '写入',
|
||||
delete: '删除',
|
||||
manage: '管理',
|
||||
}
|
||||
|
||||
// 预定义的权限矩阵结构
|
||||
const permissionMatrix = {
|
||||
resources: ['users', 'posts', 'comments', 'groups', 'messages', 'reports', 'settings', 'logs'],
|
||||
actions: ['read', 'write', 'delete', 'manage'],
|
||||
}
|
||||
|
||||
export default function Roles() {
|
||||
// 状态
|
||||
const [roles, setRoles] = useState<RoleInfo[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [selectedRole, setSelectedRole] = useState<string | null>(null)
|
||||
const [rolePermissions, setRolePermissions] = useState<Permission[]>([])
|
||||
const [permissionsLoading, setPermissionsLoading] = useState(false)
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
// 加载角色列表
|
||||
const loadRoles = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await rolesApi.getRoles()
|
||||
const data = response.data.data || []
|
||||
setRoles(data)
|
||||
if (data.length > 0 && !selectedRole) {
|
||||
setSelectedRole(data[0].name)
|
||||
loadRolePermissions(data[0].name)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load roles:', error)
|
||||
setRoles([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载角色权限
|
||||
const loadRolePermissions = async (roleName: string) => {
|
||||
setPermissionsLoading(true)
|
||||
try {
|
||||
const response = await rolesApi.getRolePermissions(roleName)
|
||||
setRolePermissions(response.data.data || [])
|
||||
} catch (error) {
|
||||
console.error('Failed to load role permissions:', error)
|
||||
setRolePermissions([])
|
||||
} finally {
|
||||
setPermissionsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始加载
|
||||
useEffect(() => {
|
||||
loadRoles()
|
||||
}, [])
|
||||
|
||||
// 选择角色时加载权限
|
||||
useEffect(() => {
|
||||
if (selectedRole) {
|
||||
loadRolePermissions(selectedRole)
|
||||
}
|
||||
}, [selectedRole])
|
||||
|
||||
// 检查权限是否存在
|
||||
const hasPermission = (resource: string, action: string) => {
|
||||
return rolePermissions.some(
|
||||
(p) => p.resource === resource && p.action === action
|
||||
)
|
||||
}
|
||||
|
||||
// 切换权限
|
||||
const togglePermission = async (resource: string, action: string) => {
|
||||
setSaving(true)
|
||||
try {
|
||||
const currentPermissions = [...rolePermissions]
|
||||
const hasIt = hasPermission(resource, action)
|
||||
|
||||
let newPermissions: Permission[]
|
||||
if (hasIt) {
|
||||
newPermissions = currentPermissions.filter(
|
||||
(p) => !(p.resource === resource && p.action === action)
|
||||
)
|
||||
} else {
|
||||
newPermissions = [
|
||||
...currentPermissions,
|
||||
{ resource, action },
|
||||
]
|
||||
}
|
||||
|
||||
// 更新权限 - 发送 {resource, action} 格式的数组
|
||||
await rolesApi.updateRolePermissions(
|
||||
selectedRole!,
|
||||
newPermissions.map((p) => ({ resource: p.resource, action: p.action }))
|
||||
)
|
||||
|
||||
setRolePermissions(newPermissions)
|
||||
} catch (error) {
|
||||
console.error('Failed to update permission:', error)
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新
|
||||
const handleRefresh = () => {
|
||||
loadRoles()
|
||||
if (selectedRole) {
|
||||
loadRolePermissions(selectedRole)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">角色管理</h1>
|
||||
<p className="text-muted-foreground">管理系统角色和权限</p>
|
||||
</div>
|
||||
<Button onClick={handleRefresh} disabled={loading}>
|
||||
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <RefreshCw className="h-4 w-4" />}
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 角色卡片 */}
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{loading ? (
|
||||
<Card className="col-span-full">
|
||||
<CardContent className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : roles.length === 0 ? (
|
||||
<Card className="col-span-full">
|
||||
<CardContent className="text-center py-8 text-muted-foreground">
|
||||
暂无角色数据
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
roles.map((role) => {
|
||||
const colors = roleColors[role.name] || { bg: 'bg-gray-100', text: 'text-gray-700', border: 'border-gray-300' }
|
||||
const isSelected = selectedRole === role.name
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={role.name}
|
||||
className={`cursor-pointer transition-all hover:shadow-md ${
|
||||
isSelected ? `ring-2 ring-primary ${colors.border}` : ''
|
||||
}`}
|
||||
onClick={() => setSelectedRole(role.name)}
|
||||
>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className={`text-lg ${colors.text}`}>
|
||||
{roleNameMap[role.name] || role.display_name || role.name}
|
||||
</CardTitle>
|
||||
{isSelected && (
|
||||
<Check className="h-5 w-5 text-primary" />
|
||||
)}
|
||||
</div>
|
||||
<CardDescription className="line-clamp-2">
|
||||
{role.description || '暂无描述'}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<Users className="h-4 w-4" />
|
||||
<span>{role.priority} 优先级</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 权限矩阵 */}
|
||||
{selectedRole && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>
|
||||
权限矩阵 - {roleNameMap[selectedRole] || selectedRole}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
管理角色的各项权限,点击复选框切换权限状态
|
||||
</CardDescription>
|
||||
</div>
|
||||
{saving && (
|
||||
<Badge variant="outline" className="text-orange-500">
|
||||
<Loader2 className="h-3 w-3 animate-spin mr-1" />
|
||||
保存中...
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{permissionsLoading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[120px] font-semibold">资源</TableHead>
|
||||
{permissionMatrix.actions.map((action) => (
|
||||
<TableHead key={action} className="text-center font-semibold">
|
||||
{actionNames[action] || action}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{permissionMatrix.resources.map((resource) => (
|
||||
<TableRow key={resource}>
|
||||
<TableCell className="font-medium">
|
||||
{resourceNames[resource] || resource}
|
||||
</TableCell>
|
||||
{permissionMatrix.actions.map((action) => {
|
||||
const has = hasPermission(resource, action)
|
||||
return (
|
||||
<TableCell key={`${resource}-${action}`} className="text-center">
|
||||
<div className="flex justify-center">
|
||||
<Checkbox
|
||||
checked={has}
|
||||
onCheckedChange={() => togglePermission(resource, action)}
|
||||
disabled={saving || selectedRole === 'super_admin'}
|
||||
className="data-[state=checked]:bg-primary"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedRole === 'super_admin' && (
|
||||
<p className="text-sm text-muted-foreground mt-4 text-center">
|
||||
超级管理员拥有所有权限,无法修改
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 角色说明 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>角色说明</CardTitle>
|
||||
<CardDescription>各角色的权限范围说明</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<Badge className={`${roleColors.super_admin?.bg} ${roleColors.super_admin?.text}`}>
|
||||
超级管理员
|
||||
</Badge>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
拥有系统所有权限,可以管理其他管理员、系统设置等核心功能
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Badge className={`${roleColors.admin?.bg} ${roleColors.admin?.text}`}>
|
||||
管理员
|
||||
</Badge>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
可以管理用户、帖子、评论、群组等内容,处理用户举报
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Badge className={`${roleColors.moderator?.bg} ${roleColors.moderator?.text}`}>
|
||||
版主
|
||||
</Badge>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
负责审核和管理帖子、评论等内容,处理违规内容
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Badge className={`${roleColors.user?.bg} ${roleColors.user?.text}`}>
|
||||
普通用户
|
||||
</Badge>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
基本用户权限,可以发帖、评论、加入群组等
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user