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

@@ -0,0 +1,186 @@
'use client'
import React, { useState } from 'react'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { trpc } from '@/lib/trpc'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Dialog, DialogTrigger } from '@/components/ui/dialog'
import { Plus } from 'lucide-react'
import { toast } from 'sonner'
import { FormDialog, FormActionBar, FormGridContent, FormCancelAction, FormSubmitAction, type FormFieldConfig } from '@/components/common/form-dialog'
import {
AdvancedSelect,
SelectPopover,
SelectTrigger,
SelectContent,
SelectInput,
SelectItemList,
SelectedName
} from '@/components/common/advanced-select'
import { useSmartSelectOptions } from '@/hooks/use-smart-select-options'
import { Textarea } from '@/components/ui/textarea'
// 创建院系管理员的 schema
const createDeptAdminSchema = z.object({
uid: z.string().min(1, '用户ID不能为空'),
deptCode: z.string().min(1, '院系代码不能为空'),
adminEmail: z.string().email('邮箱格式不正确').optional().or(z.literal('')),
adminLinePhone: z.string().optional(),
adminMobilePhone: z.string().optional(),
note: z.string().optional(),
})
type CreateDeptAdminInput = z.input<typeof createDeptAdminSchema>
const createDeptAdminDefaultValues: CreateDeptAdminInput = {
uid: '',
deptCode: '',
adminEmail: '',
adminLinePhone: '',
adminMobilePhone: '',
note: '',
}
interface DeptAdminCreateDialogProps {
onDeptAdminCreated: () => void
}
export function DeptAdminCreateDialog({ onDeptAdminCreated }: DeptAdminCreateDialogProps) {
// 表单 dialog 控制
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
// react-hook-form 管理创建表单
const createForm = useForm<CreateDeptAdminInput>({
resolver: zodResolver(createDeptAdminSchema),
defaultValues: createDeptAdminDefaultValues,
})
// 获取院系列表
const { data: depts } = trpc.common.getDepts.useQuery()
const deptOptions = depts?.map(dept => ({ id: dept.code, name: dept.fullName, shortName: dept.name })) || []
const { sortedOptions: sortedDeptOptions, logSelection: logDeptSelection } = useSmartSelectOptions({
options: deptOptions,
context: 'deptAdmin.create.dept',
scope: 'personal',
})
// 创建院系管理员 mutation
const createDeptAdminMutation = trpc.deptAdmin.create.useMutation({
onSuccess: () => {
setIsCreateDialogOpen(false)
createForm.reset(createDeptAdminDefaultValues)
toast.success('院系管理员创建成功')
onDeptAdminCreated()
},
onError: (error) => {
toast.error(error.message || '创建院系管理员失败')
},
})
// 定义字段配置
const formFields: FormFieldConfig[] = React.useMemo(() => [
{
name: 'uid',
label: '用户ID',
required: true,
render: ({ field }) => (
<Input {...field} placeholder="请输入用户ID职工号" />
),
},
{
name: 'deptCode',
label: '院系',
required: true,
render: ({ field }) => (
<AdvancedSelect
{...field}
options={sortedDeptOptions}
onChange={(value) => { logDeptSelection(value); field.onChange(value) }}
filterFunction={(option, searchValue) => {
const search = searchValue.toLowerCase()
return option.id.includes(search) || option.name.toLowerCase().includes(search) ||
(option.shortName && option.shortName.toLowerCase().includes(search))
}}
>
<SelectPopover>
<SelectTrigger placeholder="请选择院系">
<SelectedName />
</SelectTrigger>
<SelectContent>
<SelectInput placeholder="搜索院系名称/代码" />
<SelectItemList />
</SelectContent>
</SelectPopover>
</AdvancedSelect>
),
},
{
name: 'adminEmail',
label: '邮箱',
render: ({ field }) => (
<Input {...field} type="email" placeholder="请输入邮箱" />
),
},
{
name: 'adminLinePhone',
label: '座机',
render: ({ field }) => (
<Input {...field} placeholder="请输入座机号码" />
),
},
{
name: 'adminMobilePhone',
label: '手机',
render: ({ field }) => (
<Input {...field} placeholder="请输入手机号码" />
),
},
{
name: 'note',
label: '备注',
render: ({ field }) => (
<Textarea {...field} placeholder="请输入备注信息" className="min-h-[80px]" />
),
},
], [sortedDeptOptions, logDeptSelection])
const handleSubmit = async (data: CreateDeptAdminInput) => {
createDeptAdminMutation.mutate(data)
}
const handleClose = () => {
setIsCreateDialogOpen(false)
}
return (
<>
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogTrigger asChild>
<Button className="flex items-center gap-2">
<Plus className="h-4 w-4" />
</Button>
</DialogTrigger>
</Dialog>
<FormDialog
isOpen={isCreateDialogOpen}
title="创建院系管理员"
description="请填写院系管理员信息"
form={createForm}
fields={formFields}
onClose={handleClose}
>
<FormGridContent />
<FormActionBar>
<FormCancelAction />
<FormSubmitAction onSubmit={handleSubmit} isSubmitting={createDeptAdminMutation.isPending}></FormSubmitAction>
</FormActionBar>
</FormDialog>
</>
)
}