199 lines
6.0 KiB
TypeScript
199 lines
6.0 KiB
TypeScript
'use client'
|
||
|
||
import React, { useState } from 'react'
|
||
import { useForm } from 'react-hook-form'
|
||
import { zodResolver } from '@hookform/resolvers/zod'
|
||
import { z } from 'zod'
|
||
import { trpc } from '@/lib/trpc'
|
||
import { Button } from '@/components/ui/button'
|
||
import { FormDialog, FormActionBar, FormGridContent, FormCancelAction, FormSubmitAction } from '@/components/common/form-dialog'
|
||
import {
|
||
AdvancedSelect,
|
||
SelectPopover,
|
||
SelectTrigger,
|
||
SelectContent,
|
||
SelectInput,
|
||
SelectItemList,
|
||
SelectedName,
|
||
SelectedBadges
|
||
} from '@/components/common/advanced-select'
|
||
import { Users } from 'lucide-react'
|
||
import { toast } from 'sonner'
|
||
|
||
// 定义表单数据结构
|
||
const batchAuthorizationSchema = z.object({
|
||
roleId: z.number({ message: '请选择角色' }),
|
||
deptCodes: z.array(z.string()).optional(),
|
||
})
|
||
|
||
type BatchAuthorizationFormData = z.input<typeof batchAuthorizationSchema>
|
||
|
||
export const BatchAuthorizationDialog = function BatchAuthorizationDialog() {
|
||
const [isOpen, setIsOpen] = useState(false)
|
||
const [currentAction, setCurrentAction] = useState<'grant' | 'revoke' | null>(null)
|
||
const utils = trpc.useUtils()
|
||
|
||
// 获取部门列表和角色列表
|
||
const { data: depts = [] } = trpc.common.getDepts.useQuery(undefined, {
|
||
enabled: isOpen
|
||
})
|
||
const { data: roles = [] } = trpc.users.getRoles.useQuery(undefined, {
|
||
enabled: isOpen
|
||
})
|
||
|
||
// 初始化表单
|
||
const form = useForm<BatchAuthorizationFormData>({
|
||
resolver: zodResolver(batchAuthorizationSchema),
|
||
defaultValues: {
|
||
roleId: undefined,
|
||
deptCodes: [],
|
||
},
|
||
})
|
||
|
||
// 批量更新角色mutation
|
||
const batchUpdateRoleMutation = trpc.users.batchUpdateRole.useMutation({
|
||
onSuccess: (result: { count: number }, variables) => {
|
||
const action = variables.action === 'grant' ? '授予' : '撤销'
|
||
toast.success(`成功为 ${result.count} 个用户${action}角色`)
|
||
utils.users.list.invalidate()
|
||
setCurrentAction(null)
|
||
setIsOpen(false)
|
||
},
|
||
onError: (error: { message?: string }) => {
|
||
toast.error(error.message || '批量操作失败')
|
||
setCurrentAction(null)
|
||
}
|
||
})
|
||
|
||
// 处理授权
|
||
const handleGrant = async (values: BatchAuthorizationFormData) => {
|
||
setCurrentAction('grant')
|
||
batchUpdateRoleMutation.mutate({
|
||
roleId: values.roleId,
|
||
deptCodes: values.deptCodes && values.deptCodes.length > 0 ? values.deptCodes : undefined,
|
||
action: 'grant'
|
||
})
|
||
}
|
||
|
||
// 处理撤销
|
||
const handleRevoke = async (values: BatchAuthorizationFormData) => {
|
||
setCurrentAction('revoke')
|
||
batchUpdateRoleMutation.mutate({
|
||
roleId: values.roleId,
|
||
deptCodes: values.deptCodes && values.deptCodes.length > 0 ? values.deptCodes : undefined,
|
||
action: 'revoke'
|
||
})
|
||
}
|
||
|
||
// 处理对话框关闭
|
||
const handleClose = () => {
|
||
setIsOpen(false)
|
||
setCurrentAction(null)
|
||
}
|
||
|
||
// 处理对话框打开
|
||
const handleOpen = () => {
|
||
setIsOpen(true)
|
||
}
|
||
|
||
const isLoading = batchUpdateRoleMutation.isPending
|
||
|
||
// 定义表单字段配置
|
||
const fields = [
|
||
{
|
||
name: 'roleId',
|
||
label: '选择角色',
|
||
required: true,
|
||
render: ({ field }: any) => (
|
||
<div className="space-y-2">
|
||
<AdvancedSelect
|
||
options={roles.map(role => ({ ...role, id: role.id.toString() }))}
|
||
value={field.value?.toString() || ''}
|
||
onChange={(value) => field.onChange(value ? Number(value) : undefined)}
|
||
disabled={isLoading}
|
||
>
|
||
<SelectPopover>
|
||
<SelectTrigger placeholder="请选择角色" clearable>
|
||
<SelectedName />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectInput placeholder="搜索角色..." />
|
||
<SelectItemList />
|
||
</SelectContent>
|
||
</SelectPopover>
|
||
</AdvancedSelect>
|
||
<p className="text-xs text-muted-foreground">
|
||
必选项,选择要授予或撤销的角色
|
||
</p>
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
name: 'deptCodes',
|
||
label: '选择院系(可选)',
|
||
render: ({ field }: any) => (
|
||
<div className="space-y-2">
|
||
<AdvancedSelect
|
||
options={depts.map(dept => ({ id: dept.code, name: dept.name }))}
|
||
value={field.value || []}
|
||
onChange={(value) => field.onChange(value)}
|
||
disabled={isLoading}
|
||
multiple={{ enable: true }}
|
||
>
|
||
<SelectPopover>
|
||
<SelectTrigger placeholder="不选择则针对所有用户" clearable>
|
||
<SelectedBadges maxDisplay={3} />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectInput placeholder="搜索院系..." />
|
||
<SelectItemList />
|
||
</SelectContent>
|
||
</SelectPopover>
|
||
</AdvancedSelect>
|
||
<p className="text-xs text-muted-foreground">
|
||
不选择院系时,将对所有用户进行操作
|
||
</p>
|
||
</div>
|
||
),
|
||
},
|
||
]
|
||
|
||
return (
|
||
<>
|
||
<Button variant="outline" className="flex items-center gap-2" onClick={handleOpen}>
|
||
<Users className="h-4 w-4" />
|
||
批量授权
|
||
</Button>
|
||
|
||
<FormDialog
|
||
isOpen={isOpen}
|
||
title="批量授权"
|
||
description="为指定范围的用户批量授予或撤销角色"
|
||
form={form}
|
||
fields={fields}
|
||
onClose={handleClose}
|
||
className="max-w-md"
|
||
>
|
||
<FormGridContent />
|
||
<FormActionBar>
|
||
<FormCancelAction />
|
||
<FormSubmitAction
|
||
onSubmit={handleRevoke}
|
||
variant="destructive"
|
||
isSubmitting={batchUpdateRoleMutation.isPending}
|
||
showSpinningLoader={currentAction === 'revoke'}
|
||
>
|
||
撤销权限
|
||
</FormSubmitAction>
|
||
<FormSubmitAction
|
||
onSubmit={handleGrant}
|
||
isSubmitting={batchUpdateRoleMutation.isPending}
|
||
showSpinningLoader={currentAction === 'grant'}
|
||
>
|
||
授权
|
||
</FormSubmitAction>
|
||
</FormActionBar>
|
||
</FormDialog>
|
||
</>
|
||
)
|
||
} |