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_MANAGE: 'auth:permission:manage',
USER_READ: 'admin:user:read',
USER_MANAGE: 'admin:user:manage',
USER_UPDATE: 'admin:user:update',
} as const
export type Permission = (typeof PERM)[keyof typeof PERM]
+1 -1
View File
@@ -107,7 +107,7 @@ export const NAV_GROUPS: NavGroup[] = [
{
label: '用户与系统',
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: '/admin-users', icon: Users, permission: 'auth:role:assign' },
{ 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 { fetchUsers, updateUserStatus } from '@/api/modules/users'
import { fetchRoles, syncUserRoles } from '@/api/modules/roles'
import { useAuthStore } from '@/auth/store'
import { useCan, useAuthStore } from '@/auth/store'
import { handleApiError } from '@/lib/errors'
import { DataTable } from '@/components/data-table/data-table'
import { PageHeader } from '@/components/page-header'
@@ -45,13 +45,18 @@ import { Switch } from '@/components/ui/switch'
import { UserDetailSheet } from '@/features/users/user-detail-sheet'
export const Route = createFileRoute('/_authed/users/')({
beforeLoad: () => requirePermission(PERM.ROLE_ASSIGN),
beforeLoad: () => requirePermission(PERM.USER_READ),
component: UsersPage,
})
function UsersPage() {
const queryClient = useQueryClient()
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
const [searchInput, setSearchInput] = useState('')
@@ -68,7 +73,11 @@ function UsersPage() {
const [togglingUser, setTogglingUser] = useState<{ user: PlatformUser; targetStatus: boolean } | null>(null)
// 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
const usersQuery = useQuery({
@@ -172,11 +181,12 @@ function UsersPage() {
cell: ({ row }) => {
const u = row.original
const isSelf = u.uid === myUid
const isProtectedSuperAdmin = u.roles.includes('super-admin') && !isSuperAdmin
return (
<div className="flex items-center gap-2">
<Switch
checked={u.status}
disabled={isSelf}
disabled={!canUpdateUsers || isSelf || isProtectedSuperAdmin}
onCheckedChange={(checked) => {
if (!checked) {
setTogglingUser({ user: u, targetStatus: false })
@@ -215,15 +225,17 @@ function UsersPage() {
<Eye className="size-3.5 mr-1" />
</Button>
<Button
size="sm"
variant="outline"
onClick={() => setAssigningUser(u)}
title="分配角色"
>
<Shield className="size-3.5 mr-1" />
</Button>
{canManageRoles && (
<Button
size="sm"
variant="outline"
onClick={() => setAssigningUser(u)}
title="分配角色"
>
<Shield className="size-3.5 mr-1" />
</Button>
)}
</div>
)
},
@@ -296,25 +308,27 @@ function UsersPage() {
</Select>
{/* Role Filter */}
<Select
value={roleFilter}
onValueChange={(val) => {
setRoleFilter(val)
setPage(1)
}}
>
<SelectTrigger size="sm" className="w-[130px]">
<SelectValue placeholder="筛选角色" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
{(rolesQuery.data?.data ?? []).map((r) => (
<SelectItem key={r.name} value={r.name}>
{r.name}
</SelectItem>
))}
</SelectContent>
</Select>
{canReadRoles && (
<Select
value={roleFilter}
onValueChange={(val) => {
setRoleFilter(val)
setPage(1)
}}
>
<SelectTrigger size="sm" className="w-[130px]">
<SelectValue placeholder="筛选角色" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
{(rolesQuery.data?.data ?? []).map((r) => (
<SelectItem key={r.name} value={r.name}>
{r.name}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
{/* Table */}
@@ -382,12 +396,16 @@ function UsersPage() {
<UserDetailSheet
uid={detailUid}
onClose={() => setDetailUid(null)}
onAssignRoles={() => {
const u = usersQuery.data?.data?.find((x) => x.uid === detailUid)
if (u) {
setAssigningUser(u)
}
}}
onAssignRoles={
canManageRoles
? () => {
const u = usersQuery.data?.data?.find((x) => x.uid === detailUid)
if (u) {
setAssigningUser(u)
}
}
: undefined
}
/>
{/* Assign Roles Modal */}