feat: 增加DEFAULT_USER_PASSWORD,作为创建用户时的默认密码,添加p-limit库,添加DB_PARALLEL_LIMIT = 32环境变量作为“数据库批次操作默认并发数” 限制只有超级管理员才能创建超级管理员用户 删除用户时可以级联删除SelectionLog 添加zustand全局状态管理 一对多的院系管理功能 ,支持增删改查院系管理员信息、用户可以在header中切换管理的院系

This commit is contained in:
2025-11-18 20:07:42 +08:00
parent 7f3190a223
commit 2a80a44972
31 changed files with 1651 additions and 96 deletions

View File

@@ -32,12 +32,14 @@ import {
import { useIsMobile } from '@/hooks/use-mobile'
import { cn } from '@/lib/utils'
import { Loader2 } from 'lucide-react'
import { Skeleton } from '@/components/ui/skeleton'
// FormDialog Context
export interface FormDialogContextValue {
form: UseFormReturn<any>
close: () => void
fields: FormFieldConfig[]
isLoading?: boolean
}
const FormDialogContext = createContext<FormDialogContextValue | null>(null)
@@ -77,7 +79,7 @@ export function FormCancelAction({ children = '取消', variant = 'outline', onC
}
return (
<Button type="button" variant={variant} onClick={handleClick} {...props}>
<Button type="button" variant={variant} onClick={handleClick} disabled={props.disabled} {...props}>
{children}
</Button>
)
@@ -99,7 +101,7 @@ export function FormResetAction({
confirmDescription = '确定要重置表单吗?表单将回到打开时的状态。',
...props
}: FormResetActionProps) {
const { form } = useFormDialogContext()
const { form, isLoading } = useFormDialogContext()
const [showConfirm, setShowConfirm] = useState(false)
const handleConfirm = () => {
@@ -113,7 +115,7 @@ export function FormResetAction({
return (
<>
<Button type="button" variant={variant} onClick={() => setShowConfirm(true)} {...props}>
<Button type="button" variant={variant} onClick={() => setShowConfirm(true)} disabled={isLoading || props.disabled} {...props}>
{children}
</Button>
@@ -151,14 +153,14 @@ export function FormSubmitAction({
variant = 'default',
...props
}: FormSubmitActionProps) {
const { form } = useFormDialogContext()
const { form, isLoading } = useFormDialogContext()
return (
<Button
type="button"
variant={variant}
onClick={form.handleSubmit(onSubmit)}
disabled={isSubmitting || disabled}
disabled={isSubmitting || disabled || isLoading}
{...props}
>
{children}
@@ -186,7 +188,21 @@ export interface FormGridContentProps {
}
export function FormGridContent({ className = 'grid grid-cols-1 gap-4' }: FormGridContentProps) {
const { form, fields } = useFormDialogContext()
const { form, fields, isLoading } = useFormDialogContext()
// 如果正在加载,显示骨架屏
if (isLoading) {
return (
<div className={cn("p-1", className)}>
{fields.map((fieldConfig) => (
<div key={fieldConfig.name} className={cn("space-y-2", fieldConfig.className || '')}>
<Skeleton className="h-4 w-20" />
<Skeleton className="h-10 w-full" />
</div>
))}
</div>
)
}
return (
<div className={cn("p-1", className)}>
@@ -223,6 +239,7 @@ export interface FormDialogProps {
className?: string // 允许自定义对话框内容样式,可控制宽度
formClassName?: string // 允许自定义表格样式
children: React.ReactNode // 操作按钮区域内容
isLoading?: boolean // 是否正在加载数据
}
export function FormDialog({
@@ -235,13 +252,14 @@ export function FormDialog({
className = 'max-w-md',
formClassName,
children,
isLoading = false,
}: FormDialogProps) {
const isMobile = useIsMobile()
const formRef = useRef<HTMLFormElement>(null)
// 当对话框打开时,自动聚焦到第一个表单输入控件
useEffect(() => {
if (isOpen) {
if (isOpen && !isLoading) {
// 使当前拥有焦点的元素通常是用来触发打开这个drawer的控件失去焦点不然控制台会警告焦点在一个要被隐藏于屏幕阅读器的控件上
(document.activeElement as HTMLElement)?.blur();
// 使用 setTimeout 确保 DOM 已完全渲染
@@ -259,7 +277,7 @@ export function FormDialog({
return () => clearTimeout(timer)
}
}, [isOpen])
}, [isOpen, isLoading])
const close = () => {
onClose()
@@ -269,7 +287,8 @@ export function FormDialog({
const contextValue: FormDialogContextValue = {
form,
close,
fields
fields,
isLoading
}
// 表单内容组件,在 Dialog 和 Drawer 中复用