118 lines
3.4 KiB
TypeScript
118 lines
3.4 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
|
|
import { toast } from 'sonner'
|
|
|
|
import { assignSysRoleMenus, getAllSysMenus, getSysRoleMenus } from '@/api'
|
|
import { CheckboxTree, useCheckboxTree, type CheckboxTreeNode } from '@/components/checkbox-tree'
|
|
import { DialogSkeleton } from '@/components/dialog-skeleton'
|
|
import { Button } from '@/components/ui/button.tsx'
|
|
import {
|
|
Dialog,
|
|
DialogClose,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog.tsx'
|
|
import { Spinner } from '@/components/ui/spinner.tsx'
|
|
import type { SysMenu, SysRole } from '@/schemas'
|
|
|
|
interface Props {
|
|
currentRow?: SysRole | null
|
|
onClose: () => void
|
|
onConfirm: () => void
|
|
open: boolean
|
|
}
|
|
|
|
const buildTreeData = (data: SysMenu[], parentId: string | null = null): CheckboxTreeNode[] => {
|
|
return data
|
|
.filter((item) => (item.parent_id?.toString() ?? null) === parentId)
|
|
.map((item) => {
|
|
const children = buildTreeData(data, item.id.toString())
|
|
return {
|
|
id: item.id.toString(),
|
|
label: item.name,
|
|
data: item,
|
|
children: children.length > 0 ? children : [],
|
|
}
|
|
})
|
|
}
|
|
|
|
export function AssignMenusDialog({ open, onClose, onConfirm, currentRow }: Props) {
|
|
const [treeData, setTreeData] = useState<CheckboxTreeNode[]>([])
|
|
const { getNodeState, toggleNode, setCheckedIds, getCheckedIdsWithParents } = useCheckboxTree(
|
|
treeData,
|
|
[],
|
|
)
|
|
const [loading, setLoading] = useState(false)
|
|
const [skeletonLoading, setSkeletonLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
const fetchMenus = async () => {
|
|
setSkeletonLoading(true)
|
|
|
|
const { data: menus } = await getAllSysMenus()
|
|
const { data: checkedMenus } = await getSysRoleMenus(currentRow?.id as number)
|
|
setTreeData(buildTreeData(menus))
|
|
setCheckedIds(checkedMenus.map((item) => item.id.toString()))
|
|
|
|
setSkeletonLoading(false)
|
|
}
|
|
|
|
if (open && currentRow?.id) fetchMenus()
|
|
}, [open, currentRow, setCheckedIds])
|
|
|
|
const handleConfirm = async () => {
|
|
try {
|
|
setLoading(true)
|
|
await assignSysRoleMenus(
|
|
currentRow?.id as number,
|
|
getCheckedIdsWithParents().map((item) => parseInt(item)),
|
|
)
|
|
toast('操作成功!')
|
|
onConfirm()
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onClose}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>分配菜单权限</DialogTitle>
|
|
<DialogDescription />
|
|
</DialogHeader>
|
|
|
|
<div className='h-125 overflow-y-auto'>
|
|
<DialogSkeleton rows={20} loading={skeletonLoading}>
|
|
<CheckboxTree
|
|
data={treeData}
|
|
getNodeState={getNodeState}
|
|
onToggle={toggleNode}
|
|
renderLabel={(node: CheckboxTreeNode) => (
|
|
<div className='flex gap-2'>
|
|
<div>{node.data?.name}</div>
|
|
</div>
|
|
)}
|
|
/>
|
|
</DialogSkeleton>
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<DialogClose asChild>
|
|
<Button disabled={loading} variant='outline'>
|
|
取消
|
|
</Button>
|
|
</DialogClose>
|
|
<Button disabled={loading} onClick={handleConfirm}>
|
|
保存
|
|
{loading && <Spinner data-icon='inline-start' />}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|