Files
admin/src/app/(root)/roles/page.tsx
2026-04-01 13:46:22 +08:00

224 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { Suspense, useState } from "react"
import { toast } from "sonner"
import { deleteRole, getPageRole, updateRole } from "@/actions/role"
import { DataTable, useDataTable } from "@/components/data-table"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
import type { Permission } from "@/models/permission"
import type { Role } from "@/models/role"
import { AssignPermissions } from "./assign-permissions"
import { CreateRole } from "./create"
import { UpdateRole } from "./update"
export default function RolesPage() {
const table = useDataTable((page, size) => getPageRole({ page, size }))
return (
<div className="flex flex-col gap-3">
{/* 操作栏 */}
<div className="flex justify-between items-stretch">
<div className="flex gap-3">
<CreateRole onSuccess={table.refresh} />
</div>
</div>
{/* 数据表 */}
<Suspense>
<DataTable
{...table}
columns={[
{ header: "名称", accessorKey: "name" },
{ header: "描述", accessorKey: "description" },
{
header: "状态",
accessorFn: row => (row.active ? "启用" : "停用"),
},
{
header: "权限",
cell: ({ row }) => (
<PermissionsCell permissions={row.original.permissions ?? []} />
),
},
{
id: "action",
meta: { pin: "right" },
header: "操作",
cell: ({ row }) => (
<div className="flex gap-2">
<UpdateRole role={row.original} onSuccess={table.refresh} />
<AssignPermissions
role={row.original}
onSuccess={table.refresh}
/>
<ToggleActiveButton
role={row.original}
onSuccess={table.refresh}
/>
<DeleteButton role={row.original} onSuccess={table.refresh} />
</div>
),
},
]}
/>
</Suspense>
</div>
)
}
function PermissionsCell({ permissions }: { permissions: Permission[] }) {
if (!permissions || permissions.length === 0) {
return <span className="text-muted-foreground text-xs"></span>
}
const preview = permissions.slice(0, 3)
const rest = permissions.length - preview.length
return (
<HoverCard>
<HoverCardTrigger asChild>
<div className="flex flex-wrap gap-1 cursor-default max-w-52">
{preview.map(p => (
<Badge key={p.id} variant="secondary">
{p.description}
</Badge>
))}
{rest > 0 && <Badge variant="outline">+{rest}</Badge>}
</div>
</HoverCardTrigger>
<HoverCardContent className="w-72" align="start">
<p className="text-xs font-medium text-muted-foreground mb-2">
{permissions.length}
</p>
<div className="flex flex-wrap gap-1.5">
{permissions.map(p => (
<Badge key={p.id} variant="secondary">
{p.description}
</Badge>
))}
</div>
</HoverCardContent>
</HoverCard>
)
}
function ToggleActiveButton({
role,
onSuccess,
}: {
role: Role
onSuccess?: () => void
}) {
const [loading, setLoading] = useState(false)
const handleConfirm = async () => {
setLoading(true)
try {
const resp = await updateRole({ id: role.id, active: !role.active })
if (resp.success) {
toast.success(role.active ? "已停用" : "已启用")
onSuccess?.()
} else {
toast.error(resp.message ?? "操作失败")
}
} catch (error) {
const message = error instanceof Error ? error.message : error
toast.error(`接口请求错误: ${message}`)
} finally {
setLoading(false)
}
}
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm" variant="secondary" disabled={loading}>
{role.active ? "停用" : "启用"}
</Button>
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogTitle>
{role.active ? "停用" : "启用"}
</AlertDialogTitle>
<AlertDialogDescription>
{role.active ? "停用" : "启用"}{role.name}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={handleConfirm}></AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
function DeleteButton({
role,
onSuccess,
}: {
role: Role
onSuccess?: () => void
}) {
const [loading, setLoading] = useState(false)
const handleConfirm = async () => {
setLoading(true)
try {
const resp = await deleteRole(role.id)
if (resp.success) {
toast.success("删除成功")
onSuccess?.()
} else {
toast.error(resp.message ?? "删除失败")
}
} catch (error) {
const message = error instanceof Error ? error.message : error
toast.error(`接口请求错误: ${message}`)
} finally {
setLoading(false)
}
}
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm" variant="destructive" disabled={loading}>
</Button>
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
{role.name}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction variant="destructive" onClick={handleConfirm}>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}