fix(user): align admin controls with permissions

This commit is contained in:
2026-09-08 07:30:04 +08:00
parent d39a88c445
commit 6dcc31a093
3 changed files with 58 additions and 40 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ export const PERM = {
PERMISSION_READ: 'auth:permission:read', PERMISSION_READ: 'auth:permission:read',
PERMISSION_MANAGE: 'auth:permission:manage', PERMISSION_MANAGE: 'auth:permission:manage',
USER_READ: 'admin:user:read', USER_READ: 'admin:user:read',
USER_MANAGE: 'admin:user:manage', USER_UPDATE: 'admin:user:update',
} as const } as const
export type Permission = (typeof PERM)[keyof typeof PERM] export type Permission = (typeof PERM)[keyof typeof PERM]
+1 -1
View File
@@ -107,7 +107,7 @@ export const NAV_GROUPS: NavGroup[] = [
{ {
label: '用户与系统', label: '用户与系统',
items: [ items: [
{ label: '用户列表', to: '/users', icon: Users, permission: 'auth:role:assign' }, { label: '用户列表', to: '/users', icon: Users, permission: 'admin:user:read' },
{ label: '角色管理', to: '/roles', icon: Shield, permission: 'auth:role:read' }, { label: '角色管理', to: '/roles', icon: Shield, permission: 'auth:role:read' },
{ label: '用户角色', to: '/admin-users', icon: Users, permission: 'auth:role:assign' }, { label: '用户角色', to: '/admin-users', icon: Users, permission: 'auth:role:assign' },
{ label: 'SID 管理', to: '/sid', icon: Hash, permission: 'admin:sid:manage' }, { label: 'SID 管理', to: '/sid', icon: Hash, permission: 'admin:sid:manage' },
+56 -38
View File
@@ -8,7 +8,7 @@ import type { ColumnDef } from '@tanstack/react-table'
import type { PlatformUser } from '@/api/types' import type { PlatformUser } from '@/api/types'
import { fetchUsers, updateUserStatus } from '@/api/modules/users' import { fetchUsers, updateUserStatus } from '@/api/modules/users'
import { fetchRoles, syncUserRoles } from '@/api/modules/roles' import { fetchRoles, syncUserRoles } from '@/api/modules/roles'
import { useAuthStore } from '@/auth/store' import { useCan, useAuthStore } from '@/auth/store'
import { handleApiError } from '@/lib/errors' import { handleApiError } from '@/lib/errors'
import { DataTable } from '@/components/data-table/data-table' import { DataTable } from '@/components/data-table/data-table'
import { PageHeader } from '@/components/page-header' import { PageHeader } from '@/components/page-header'
@@ -45,13 +45,18 @@ import { Switch } from '@/components/ui/switch'
import { UserDetailSheet } from '@/features/users/user-detail-sheet' import { UserDetailSheet } from '@/features/users/user-detail-sheet'
export const Route = createFileRoute('/_authed/users/')({ export const Route = createFileRoute('/_authed/users/')({
beforeLoad: () => requirePermission(PERM.ROLE_ASSIGN), beforeLoad: () => requirePermission(PERM.USER_READ),
component: UsersPage, component: UsersPage,
}) })
function UsersPage() { function UsersPage() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const myUid = useAuthStore((s) => s.user?.uid) const myUid = useAuthStore((s) => s.user?.uid)
const isSuperAdmin = useAuthStore((s) => s.roles.includes('super-admin'))
const canUpdateUsers = useCan(PERM.USER_UPDATE)
const canReadRoles = useCan(PERM.ROLE_READ)
const canAssignRoles = useCan(PERM.ROLE_ASSIGN)
const canManageRoles = canReadRoles && canAssignRoles
// Filter state // Filter state
const [searchInput, setSearchInput] = useState('') const [searchInput, setSearchInput] = useState('')
@@ -68,7 +73,11 @@ function UsersPage() {
const [togglingUser, setTogglingUser] = useState<{ user: PlatformUser; targetStatus: boolean } | null>(null) const [togglingUser, setTogglingUser] = useState<{ user: PlatformUser; targetStatus: boolean } | null>(null)
// Available roles query for filter dropdown // Available roles query for filter dropdown
const rolesQuery = useQuery({ queryKey: ['roles'], queryFn: fetchRoles }) const rolesQuery = useQuery({
queryKey: ['roles'],
queryFn: fetchRoles,
enabled: canReadRoles,
})
// Users listing query // Users listing query
const usersQuery = useQuery({ const usersQuery = useQuery({
@@ -172,11 +181,12 @@ function UsersPage() {
cell: ({ row }) => { cell: ({ row }) => {
const u = row.original const u = row.original
const isSelf = u.uid === myUid const isSelf = u.uid === myUid
const isProtectedSuperAdmin = u.roles.includes('super-admin') && !isSuperAdmin
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Switch <Switch
checked={u.status} checked={u.status}
disabled={isSelf} disabled={!canUpdateUsers || isSelf || isProtectedSuperAdmin}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
if (!checked) { if (!checked) {
setTogglingUser({ user: u, targetStatus: false }) setTogglingUser({ user: u, targetStatus: false })
@@ -215,15 +225,17 @@ function UsersPage() {
<Eye className="size-3.5 mr-1" /> <Eye className="size-3.5 mr-1" />
</Button> </Button>
<Button {canManageRoles && (
size="sm" <Button
variant="outline" size="sm"
onClick={() => setAssigningUser(u)} variant="outline"
title="分配角色" onClick={() => setAssigningUser(u)}
> title="分配角色"
<Shield className="size-3.5 mr-1" /> >
<Shield className="size-3.5 mr-1" />
</Button>
</Button>
)}
</div> </div>
) )
}, },
@@ -296,25 +308,27 @@ function UsersPage() {
</Select> </Select>
{/* Role Filter */} {/* Role Filter */}
<Select {canReadRoles && (
value={roleFilter} <Select
onValueChange={(val) => { value={roleFilter}
setRoleFilter(val) onValueChange={(val) => {
setPage(1) setRoleFilter(val)
}} setPage(1)
> }}
<SelectTrigger size="sm" className="w-[130px]"> >
<SelectValue placeholder="筛选角色" /> <SelectTrigger size="sm" className="w-[130px]">
</SelectTrigger> <SelectValue placeholder="筛选角色" />
<SelectContent> </SelectTrigger>
<SelectItem value="all"></SelectItem> <SelectContent>
{(rolesQuery.data?.data ?? []).map((r) => ( <SelectItem value="all"></SelectItem>
<SelectItem key={r.name} value={r.name}> {(rolesQuery.data?.data ?? []).map((r) => (
{r.name} <SelectItem key={r.name} value={r.name}>
</SelectItem> {r.name}
))} </SelectItem>
</SelectContent> ))}
</Select> </SelectContent>
</Select>
)}
</div> </div>
{/* Table */} {/* Table */}
@@ -382,12 +396,16 @@ function UsersPage() {
<UserDetailSheet <UserDetailSheet
uid={detailUid} uid={detailUid}
onClose={() => setDetailUid(null)} onClose={() => setDetailUid(null)}
onAssignRoles={() => { onAssignRoles={
const u = usersQuery.data?.data?.find((x) => x.uid === detailUid) canManageRoles
if (u) { ? () => {
setAssigningUser(u) const u = usersQuery.data?.data?.find((x) => x.uid === detailUid)
} if (u) {
}} setAssigningUser(u)
}
}
: undefined
}
/> />
{/* Assign Roles Modal */} {/* Assign Roles Modal */}