Hair Keeper v1.0.0:一个高度集成、深度定制、约定优于配置的全栈Web应用模板,旨在保持灵活性的同时提供一套基于成熟架构的开发底座,自带身份认证、权限控制、丰富前端组件、文件上传、后台任务、智能体开发等丰富功能,提供AI开发辅助,免于纠结功能如何实现,可快速上手专注于业务逻辑

This commit is contained in:
2025-11-13 15:24:54 +08:00
commit 42be39b343
249 changed files with 38843 additions and 0 deletions

View File

@@ -0,0 +1,199 @@
'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>
</>
)
}