本次更新包含以下主要改进: ## 新功能 - 添加quickstart.sh脚本帮助用户快速使用模板项目 - 添加simple_deploy.sh便于部署 - 新增院系管理功能(DeptAdmin),支持增删改查院系管理员信息 - 用户可以在header中切换管理的院系 - 添加zustand全局状态管理 - 添加DEFAULT_USER_PASSWORD环境变量,作为创建用户时的默认密码 - 添加p-limit库和DB_PARALLEL_LIMIT环境变量控制数据库批次操作并发数 ## 安全修复 - 修复Next.js CVE-2025-66478漏洞 - 限制只有超级管理员才能创建超级管理员用户 ## 开发环境优化 - 开发终端兼容云端环境 - MinIO客户端直传兼容云端环境 - 开发容器增加vim和Claude Code插件 - 编程代理改用Claude - docker-compose.yml添加全局name属性 ## Bug修复与代码优化 - 删除用户时级联删除SelectionLog - 手机端关闭侧边栏后刷新页面延迟调整(300ms=>350ms) - instrumentation.ts移至src内部以适配生产环境 - 删除部分引发类型错误的无用代码 - 优化quickstart.sh远程仓库推送相关配置 ## 文件变更 - 新增49个文件,修改多个配置和源代码文件 - 重构用户管理模块目录结构 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
230 lines
6.5 KiB
TypeScript
230 lines
6.5 KiB
TypeScript
'use client'
|
||
|
||
import React, { useState, useEffect } from 'react'
|
||
import { useForm } from 'react-hook-form'
|
||
import { z } from 'zod'
|
||
import { zodResolver } from '@hookform/resolvers/zod'
|
||
import { trpc } from '@/lib/trpc'
|
||
import { updateUserSchema, userStatusOptions } from '@/lib/schema/user'
|
||
import { Input } from '@/components/ui/input'
|
||
import { Checkbox } from '@/components/ui/checkbox'
|
||
import { Label } from '@/components/ui/label'
|
||
import { toast } from 'sonner'
|
||
import { FormDialog, FormActionBar, FormGridContent, FormCancelAction, FormSubmitAction, type FormFieldConfig } from '@/components/common/form-dialog'
|
||
import { CheckboxGroup } from '@/components/common/checkbox-group'
|
||
import {
|
||
AdvancedSelect,
|
||
SelectPopover,
|
||
SelectTrigger,
|
||
SelectContent,
|
||
SelectInput,
|
||
SelectItemList,
|
||
SelectedName
|
||
} from '@/components/common/advanced-select'
|
||
import { useSmartSelectOptions } from '@/hooks/use-smart-select-options'
|
||
|
||
type UpdateUserInput = z.infer<typeof updateUserSchema>
|
||
|
||
const updateUserDefaultValues: UpdateUserInput = {
|
||
id: '',
|
||
name: '',
|
||
status: '',
|
||
deptCode: '',
|
||
password: '',
|
||
roleIds: [],
|
||
isSuperAdmin: false,
|
||
}
|
||
|
||
interface UserUpdateDialogProps {
|
||
userId: string | null
|
||
isOpen: boolean
|
||
onClose: () => void
|
||
onUserUpdated: () => void
|
||
}
|
||
|
||
export function UserUpdateDialog({ userId, isOpen, onClose, onUserUpdated }: UserUpdateDialogProps) {
|
||
// react-hook-form 管理更新表单
|
||
const updateForm = useForm<UpdateUserInput>({
|
||
resolver: zodResolver(updateUserSchema),
|
||
defaultValues: updateUserDefaultValues,
|
||
})
|
||
|
||
// 获取用户详情
|
||
const { data: user, isLoading: isLoadingUser } = trpc.users.getById.useQuery(
|
||
{ id: userId! },
|
||
{ enabled: !!userId && isOpen }
|
||
)
|
||
|
||
// 获取角色列表和院系列表
|
||
const { data: roles } = trpc.users.getRoles.useQuery()
|
||
const { data: depts } = trpc.common.getDepts.useQuery()
|
||
|
||
const deptOptions = React.useMemo(() => depts?.map(dept => ({ id: dept.code, name: dept.fullName, shortName: dept.name })) || [], [depts])
|
||
const { sortedOptions: sortedDeptOptions, logSelection: logDeptSelection } = useSmartSelectOptions({
|
||
options: deptOptions,
|
||
context: 'user.update.dept',
|
||
scope: 'personal',
|
||
})
|
||
|
||
// 更新用户 mutation
|
||
const updateUserMutation = trpc.users.update.useMutation({
|
||
onSuccess: () => {
|
||
onClose()
|
||
toast.success('用户更新成功')
|
||
onUserUpdated()
|
||
},
|
||
onError: (error) => {
|
||
toast.error(error.message || '更新用户失败')
|
||
},
|
||
})
|
||
|
||
|
||
// 定义字段配置
|
||
const formFields: FormFieldConfig[] = React.useMemo(() => [
|
||
{
|
||
name: 'id',
|
||
label: '用户ID',
|
||
required: true,
|
||
render: ({ field }) => (
|
||
<Input {...field} placeholder="请输入用户ID(职工号)" disabled />
|
||
),
|
||
},
|
||
{
|
||
name: 'name',
|
||
label: '姓名',
|
||
render: ({ field }) => (
|
||
<Input {...field} placeholder="请输入姓名" />
|
||
),
|
||
},
|
||
{
|
||
name: 'status',
|
||
label: '状态',
|
||
render: ({ field }) => (
|
||
<AdvancedSelect
|
||
{...field}
|
||
options={userStatusOptions}
|
||
>
|
||
<SelectPopover>
|
||
<SelectTrigger placeholder="请选择状态">
|
||
<SelectedName />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItemList />
|
||
</SelectContent>
|
||
</SelectPopover>
|
||
</AdvancedSelect>
|
||
),
|
||
},
|
||
{
|
||
name: 'deptCode',
|
||
label: '所属院系',
|
||
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="请选择院系" clearable>
|
||
<SelectedName />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectInput placeholder="搜索院系名称/代码" />
|
||
<SelectItemList />
|
||
</SelectContent>
|
||
</SelectPopover>
|
||
</AdvancedSelect>
|
||
),
|
||
},
|
||
{
|
||
name: 'password',
|
||
label: '新密码',
|
||
render: ({ field }) => (
|
||
<Input {...field} type="password" placeholder="留空则不修改密码" />
|
||
),
|
||
},
|
||
{
|
||
name: 'roleIds',
|
||
label: '角色',
|
||
render: ({ field }) => (
|
||
<CheckboxGroup
|
||
{...field}
|
||
options={roles || []}
|
||
idPrefix="role"
|
||
/>
|
||
),
|
||
},
|
||
{
|
||
name: 'isSuperAdmin',
|
||
label: '超级管理员',
|
||
render: ({ field }) => (
|
||
<div className="flex items-center space-x-2">
|
||
<Checkbox
|
||
id="isSuperAdmin"
|
||
checked={field.value || false}
|
||
onCheckedChange={field.onChange}
|
||
/>
|
||
<Label htmlFor="isSuperAdmin" className="text-sm">
|
||
超级管理员
|
||
</Label>
|
||
</div>
|
||
),
|
||
},
|
||
], [sortedDeptOptions, logDeptSelection, roles])
|
||
|
||
// 当用户数据加载完成时,重置表单
|
||
useEffect(() => {
|
||
if (user && isOpen) {
|
||
const defaultValues: UpdateUserInput = {
|
||
id: user.id,
|
||
name: user.name || '',
|
||
status: user.status || '',
|
||
deptCode: user.deptCode || '',
|
||
password: '', // 密码字段默认为空,只有填写时才更新
|
||
roleIds: user.roles?.map((role) => role.id) || [],
|
||
isSuperAdmin: user.isSuperAdmin || false,
|
||
}
|
||
updateForm.reset(defaultValues)
|
||
}
|
||
}, [user, isOpen, updateForm])
|
||
|
||
// 当对话框关闭时,清理状态
|
||
useEffect(() => {
|
||
if (!isOpen) {
|
||
updateForm.reset()
|
||
}
|
||
}, [isOpen, updateForm])
|
||
|
||
const handleSubmit = async (data: UpdateUserInput) => {
|
||
updateUserMutation.mutate(data)
|
||
}
|
||
|
||
const handleClose = () => {
|
||
onClose()
|
||
}
|
||
|
||
if (!isOpen) return null
|
||
|
||
return (
|
||
<FormDialog
|
||
isOpen={isOpen}
|
||
title="编辑用户"
|
||
description="请填写用户信息"
|
||
form={updateForm}
|
||
fields={formFields}
|
||
onClose={handleClose}
|
||
isLoading={isLoadingUser}
|
||
>
|
||
<FormGridContent />
|
||
<FormActionBar>
|
||
<FormCancelAction />
|
||
<FormSubmitAction onSubmit={handleSubmit} isSubmitting={updateUserMutation.isPending}>更新</FormSubmitAction>
|
||
</FormActionBar>
|
||
</FormDialog>
|
||
)
|
||
} |