Files
admin/src/app/(root)/permissions/page.tsx

187 lines
4.6 KiB
TypeScript

"use client"
import {
getCoreRowModel,
getExpandedRowModel,
type Row,
useReactTable,
} from "@tanstack/react-table"
import { Copy } from "lucide-react"
import { Suspense, useCallback, useEffect, useState } from "react"
import { toast } from "sonner"
import { getAllPermissions } from "@/actions/permission"
import { Page } from "@/components/page"
import { Button } from "@/components/ui/button"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { cn } from "@/lib/utils"
type Node = {
id: number
name: string
description: string
children: Node[]
}
function toConstName(name: string): string {
return (
"Scope" +
name
.split(/[:_]/)
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
.join("")
)
}
function generateScopeCode(roots: Node[]): string {
function generateNode(node: Node, isRoot: boolean): string[] {
const lines: string[] = []
if (isRoot) {
lines.push(`// ${node.description}`)
}
const constName = toConstName(node.name)
const comment = isRoot ? "" : ` // ${node.description}`
lines.push(`export const ${constName} = "${node.name}"${comment}`)
for (const child of node.children) {
lines.push(...generateNode(child, false))
}
return lines
}
return roots.map(root => generateNode(root, true).join("\n")).join("\n\n")
}
export default function PermissionsPage() {
return (
<Page>
<Suspense>
<PermissionTable />
</Suspense>
</Page>
)
}
function PermissionTable() {
const [data, setData] = useState<Node[]>([])
const table = useReactTable({
getCoreRowModel: getCoreRowModel(),
getExpandedRowModel: getExpandedRowModel(),
data,
columns: [
{ header: "编码", accessorKey: "name" },
{ header: "描述", accessorKey: "description" },
],
getSubRows: row => row.children,
})
const refresh = useCallback(async () => {
try {
const resp = await getAllPermissions()
if (!resp.success) {
throw new Error(resp.message)
}
const map = new Map<number, Node>()
resp.data.forEach(permission => {
map.set(permission.id, {
id: permission.id,
name: permission.name,
description: permission.description,
children: [],
})
})
const roots: Node[] = []
resp.data.forEach(permission => {
const node = map.get(permission.id)
if (!node) {
throw new Error(`找不到权限节点: ${permission.name}`)
}
if (!permission.parent_id) {
roots.push(node)
return
}
const parent = map.get(permission.parent_id)
if (!parent) {
throw new Error(`找不到父权限节点: ${permission.name}`)
}
parent.children.push(node)
})
setData(roots)
console.log(roots)
} catch (e) {
toast.error(e instanceof Error ? e.message : "获取权限列表失败")
}
}, [])
useEffect(() => {
refresh()
}, [refresh])
const handleCopy = useCallback(async () => {
try {
const code = generateScopeCode(data)
await navigator.clipboard.writeText(code)
toast.success("已复制权限代码到剪贴板")
} catch (e) {
toast.error(e instanceof Error ? e.message : "复制失败")
}
}, [data])
return (
<div className="flex flex-col gap-3 overflow-auto">
{process.env.NODE_ENV === "development" && (
<div>
<Button variant="outline" size="sm" onClick={handleCopy}>
<Copy className="mr-2 h-4 w-4" />
</Button>
</div>
)}
<Table>
<TableHeader>
<TableRow className="h-10 sticky top-0 bg-background">
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{table.getRowModel().rows.map(row => (
<PermisionRow key={row.id} row={row} />
))}
</TableBody>
</Table>
</div>
)
}
function PermisionRow(props: { row: Row<Node> }) {
const row = props.row
return (
<>
<TableRow key={row.id} className="h-10">
<TableCell className="flex">
<div style={{ width: row.depth * 16 }}></div>
<span className={cn(row.subRows.length ? "font-bold" : "text-sm")}>
{row.original.name}
</span>
</TableCell>
<TableCell>{row.original.description}</TableCell>
</TableRow>
{row.subRows.map(subRow => (
<PermisionRow key={subRow.id} row={subRow} />
))}
</>
)
}