From b3525aee7d6bc866b9adff59a086f7a1d63bb37a Mon Sep 17 00:00:00 2001 From: liguang <1590686939@qq.com> Date: Fri, 20 Jun 2025 20:26:28 +0800 Subject: [PATCH] =?UTF-8?q?refactor(usermanagement):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97=E7=9A=84?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=AE=9A=E4=B9=89=E5=92=8C=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 adminActions、guestActions、problemActions 和 teacherActions 文件中的函数添加了明确的类型注解 - 更新了函数参数和返回值的类型,提高了代码的可读性和可维护性 - 在相关页面组件中添加了类型注解,明确了数据结构 - 移除了未使用的 Separator 组件导入 --- .../usermanagement/_actions/adminActions.ts | 7 +- .../usermanagement/_actions/guestActions.ts | 7 +- .../usermanagement/_actions/problemActions.ts | 7 +- .../usermanagement/_actions/teacherActions.ts | 7 +- src/app/(app)/usermanagement/admin/page.tsx | 3 +- src/app/(app)/usermanagement/guest/page.tsx | 4 +- src/app/(app)/usermanagement/problem/page.tsx | 3 +- src/app/(app)/usermanagement/teacher/page.tsx | 3 +- src/components/site-header.tsx | 2 - .../user-management/components/user-table.tsx | 499 ++++++++++-------- tailwind.config.ts | 2 - 11 files changed, 291 insertions(+), 253 deletions(-) diff --git a/src/app/(app)/usermanagement/_actions/adminActions.ts b/src/app/(app)/usermanagement/_actions/adminActions.ts index 9874ba1..bc3a3c5 100644 --- a/src/app/(app)/usermanagement/_actions/adminActions.ts +++ b/src/app/(app)/usermanagement/_actions/adminActions.ts @@ -2,8 +2,9 @@ import prisma from '@/lib/prisma' import { revalidatePath } from 'next/cache' import bcrypt from 'bcryptjs' +import type { User } from '@/generated/client' -export async function createAdmin(data) { +export async function createAdmin(data: Omit & { password?: string }) { let password = data.password if (password) { password = await bcrypt.hash(password, 10) @@ -12,12 +13,12 @@ export async function createAdmin(data) { revalidatePath('/usermanagement/admin') } -export async function updateAdmin(id, data) { +export async function updateAdmin(id: string, data: Partial>) { await prisma.user.update({ where: { id }, data }) revalidatePath('/usermanagement/admin') } -export async function deleteAdmin(id) { +export async function deleteAdmin(id: string) { await prisma.user.delete({ where: { id } }) revalidatePath('/usermanagement/admin') } \ No newline at end of file diff --git a/src/app/(app)/usermanagement/_actions/guestActions.ts b/src/app/(app)/usermanagement/_actions/guestActions.ts index cb1cdd8..73c3d90 100644 --- a/src/app/(app)/usermanagement/_actions/guestActions.ts +++ b/src/app/(app)/usermanagement/_actions/guestActions.ts @@ -2,8 +2,9 @@ import prisma from '@/lib/prisma' import { revalidatePath } from 'next/cache' import bcrypt from 'bcryptjs' +import type { User } from '@/generated/client' -export async function createGuest(data) { +export async function createGuest(data: Omit & { password?: string }) { let password = data.password if (password) { password = await bcrypt.hash(password, 10) @@ -12,12 +13,12 @@ export async function createGuest(data) { revalidatePath('/usermanagement/guest') } -export async function updateGuest(id, data) { +export async function updateGuest(id: string, data: Partial>) { await prisma.user.update({ where: { id }, data }) revalidatePath('/usermanagement/guest') } -export async function deleteGuest(id) { +export async function deleteGuest(id: string) { await prisma.user.delete({ where: { id } }) revalidatePath('/usermanagement/guest') } \ No newline at end of file diff --git a/src/app/(app)/usermanagement/_actions/problemActions.ts b/src/app/(app)/usermanagement/_actions/problemActions.ts index 6e682fa..4d5ed90 100644 --- a/src/app/(app)/usermanagement/_actions/problemActions.ts +++ b/src/app/(app)/usermanagement/_actions/problemActions.ts @@ -1,18 +1,19 @@ 'use server' import prisma from '@/lib/prisma' import { revalidatePath } from 'next/cache' +import type { Problem } from '@/generated/client' -export async function createProblem(data) { +export async function createProblem(data: Omit) { await prisma.problem.create({ data }) revalidatePath('/usermanagement/problem') } -export async function updateProblem(id, data) { +export async function updateProblem(id: string, data: Partial>) { await prisma.problem.update({ where: { id }, data }) revalidatePath('/usermanagement/problem') } -export async function deleteProblem(id) { +export async function deleteProblem(id: string) { await prisma.problem.delete({ where: { id } }) revalidatePath('/usermanagement/problem') } \ No newline at end of file diff --git a/src/app/(app)/usermanagement/_actions/teacherActions.ts b/src/app/(app)/usermanagement/_actions/teacherActions.ts index 5326fce..094c5bc 100644 --- a/src/app/(app)/usermanagement/_actions/teacherActions.ts +++ b/src/app/(app)/usermanagement/_actions/teacherActions.ts @@ -2,8 +2,9 @@ import prisma from '@/lib/prisma' import { revalidatePath } from 'next/cache' import bcrypt from 'bcryptjs' +import type { User } from '@/generated/client' -export async function createTeacher(data) { +export async function createTeacher(data: Omit & { password?: string }) { let password = data.password if (password) { password = await bcrypt.hash(password, 10) @@ -12,12 +13,12 @@ export async function createTeacher(data) { revalidatePath('/usermanagement/teacher') } -export async function updateTeacher(id, data) { +export async function updateTeacher(id: string, data: Partial>) { await prisma.user.update({ where: { id }, data }) revalidatePath('/usermanagement/teacher') } -export async function deleteTeacher(id) { +export async function deleteTeacher(id: string) { await prisma.user.delete({ where: { id } }) revalidatePath('/usermanagement/teacher') } \ No newline at end of file diff --git a/src/app/(app)/usermanagement/admin/page.tsx b/src/app/(app)/usermanagement/admin/page.tsx index d5e41dc..543ce47 100644 --- a/src/app/(app)/usermanagement/admin/page.tsx +++ b/src/app/(app)/usermanagement/admin/page.tsx @@ -1,8 +1,9 @@ import { UserTable } from '@/features/user-management/components/user-table' import { adminConfig } from '@/features/user-management/config/admin' import prisma from '@/lib/prisma' +import type { User } from '@/generated/client' export default async function AdminPage() { - const data = await prisma.user.findMany({ where: { role: 'ADMIN' } }) + const data: User[] = await prisma.user.findMany({ where: { role: 'ADMIN' } }) return } \ No newline at end of file diff --git a/src/app/(app)/usermanagement/guest/page.tsx b/src/app/(app)/usermanagement/guest/page.tsx index b64edbf..8dfaae8 100644 --- a/src/app/(app)/usermanagement/guest/page.tsx +++ b/src/app/(app)/usermanagement/guest/page.tsx @@ -1,7 +1,9 @@ import { UserTable } from '@/features/user-management/components/user-table' import { guestConfig } from '@/features/user-management/config/guest' import prisma from '@/lib/prisma' +import type { User } from '@/generated/client' + export default async function GuestPage() { - const data = await prisma.user.findMany({ where: { role: 'GUEST' as any } }) + const data: User[] = await prisma.user.findMany({ where: { role: 'GUEST' } }) return } \ No newline at end of file diff --git a/src/app/(app)/usermanagement/problem/page.tsx b/src/app/(app)/usermanagement/problem/page.tsx index 08bca0b..3d96f71 100644 --- a/src/app/(app)/usermanagement/problem/page.tsx +++ b/src/app/(app)/usermanagement/problem/page.tsx @@ -1,8 +1,9 @@ import { UserTable } from '@/features/user-management/components/user-table' import { problemConfig } from '@/features/user-management/config/problem' import prisma from '@/lib/prisma' +import type { Problem } from '@/generated/client' export default async function ProblemPage() { - const data = await prisma.problem.findMany({}) + const data: Problem[] = await prisma.problem.findMany({}) return } \ No newline at end of file diff --git a/src/app/(app)/usermanagement/teacher/page.tsx b/src/app/(app)/usermanagement/teacher/page.tsx index 13d854d..93d6933 100644 --- a/src/app/(app)/usermanagement/teacher/page.tsx +++ b/src/app/(app)/usermanagement/teacher/page.tsx @@ -1,8 +1,9 @@ import { UserTable } from '@/features/user-management/components/user-table' import { teacherConfig } from '@/features/user-management/config/teacher' import prisma from '@/lib/prisma' +import type { User } from '@/generated/client' export default async function TeacherPage() { - const data = await prisma.user.findMany({ where: { role: 'TEACHER' as any } }) + const data: User[] = await prisma.user.findMany({ where: { role: 'TEACHER' } }) return } \ No newline at end of file diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index 0801404..d0a90b7 100644 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -1,5 +1,3 @@ -import { Separator } from "@/components/ui/separator" - export function SiteHeader() { return (
diff --git a/src/features/user-management/components/user-table.tsx b/src/features/user-management/components/user-table.tsx index 185dd5d..62e2ff2 100644 --- a/src/features/user-management/components/user-table.tsx +++ b/src/features/user-management/components/user-table.tsx @@ -70,11 +70,12 @@ import { } from "@/components/ui/tabs" import { createAdmin, updateAdmin, deleteAdmin } from '@/app/(app)/usermanagement/_actions/adminActions' -import { createTeacher, updateTeacher, deleteTeacher } from '@/app/(app)/usermanagement/_actions/teacherActions' -import { createGuest, updateGuest, deleteGuest } from '@/app/(app)/usermanagement/_actions/guestActions' +import { createTeacher, updateTeacher } from '@/app/(app)/usermanagement/_actions/teacherActions' +import { createGuest, updateGuest } from '@/app/(app)/usermanagement/_actions/guestActions' import { createProblem, updateProblem, deleteProblem } from '@/app/(app)/usermanagement/_actions/problemActions' +import type { User, Problem } from '@/generated/client' +import { Difficulty, Role } from '@/generated/client' -// 通用用户类型 export interface UserConfig { userType: string title: string @@ -106,69 +107,85 @@ export interface UserConfig { } } -interface UserTableProps { - config: UserConfig - data: any[] +type UserTableProps = + | { config: UserConfig; data: User[] } + | { config: UserConfig; data: Problem[] } + +type UserForm = { + id?: string + name: string + email: string + password: string + createdAt: string + role: Role + image: string | null + emailVerified: Date | null } -// 在组件内部定义 schema +// 新增用户表单类型 +type AddUserForm = Omit + const addUserSchema = z.object({ - name: z.string().optional(), - email: z.string().email("请输入有效的邮箱地址"), - password: z.string().optional(), - createdAt: z.string().optional(), + name: z.string(), + email: z.string().email(), + password: z.string(), + createdAt: z.string(), + image: z.string().nullable(), + emailVerified: z.date().nullable(), + role: z.nativeEnum(Role), }) const editUserSchema = z.object({ - id: z.string(), - name: z.string().optional(), - email: z.string().email("请输入有效的邮箱地址"), - password: z.string().optional(), - role: z.string().optional(), + id: z.string().default(''), + name: z.string(), + email: z.string().email(), + password: z.string(), createdAt: z.string(), + image: z.string().nullable(), + emailVerified: z.date().nullable(), + role: z.nativeEnum(Role), }) +// 题目表单 schema 兼容 null/undefined const addProblemSchema = z.object({ - displayId: z.number(), - difficulty: z.string(), + displayId: z.number().optional().default(0), + difficulty: z.nativeEnum(Difficulty).default(Difficulty.EASY), }) const editProblemSchema = z.object({ - id: z.string(), - displayId: z.number(), - difficulty: z.string(), + id: z.string().default(''), + displayId: z.number().optional().default(0), + difficulty: z.nativeEnum(Difficulty).default(Difficulty.EASY), }) -export function UserTable({ config, data }: UserTableProps) { +export function UserTable(props: UserTableProps) { + const isProblem = props.config.userType === 'problem' + const router = useRouter() + const problemData = isProblem ? (props.data as Problem[]) : undefined + const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) - const [editingUser, setEditingUser] = useState(null) + const [editingUser, setEditingUser] = useState(null) + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) + const [deleteBatch, setDeleteBatch] = useState(false) const [rowSelection, setRowSelection] = useState({}) const [columnVisibility, setColumnVisibility] = useState({}) const [columnFilters, setColumnFilters] = useState([]) const [sorting, setSorting] = useState([]) const [pagination, setPagination] = useState({ pageIndex: 0, - pageSize: config.pagination.defaultPageSize, + pageSize: props.config.pagination.defaultPageSize, }) - - // 删除确认对话框相关state - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) - const [deleteTargetId, setDeleteTargetId] = useState(null) - const [deleteBatch, setDeleteBatch] = useState(false) - - // 页码输入本地state const [pageInput, setPageInput] = useState(pagination.pageIndex + 1) + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false) + const [pendingDeleteItem, setPendingDeleteItem] = useState(null) useEffect(() => { setPageInput(pagination.pageIndex + 1) }, [pagination.pageIndex]) - // 判断是否为题目管理 - const isProblem = config.userType === "problem" - - // 动态生成表格列 - const tableColumns = React.useMemo[]>(() => { - const columns: ColumnDef[] = [ + // 表格列 + const tableColumns = React.useMemo[]>(() => { + const columns: ColumnDef[] = [ { id: "select", header: ({ table }) => ( @@ -189,34 +206,17 @@ export function UserTable({ config, data }: UserTableProps) { enableHiding: false, }, ] - - // 添加配置的列 - config.columns.forEach((col) => { - const column: ColumnDef = { + props.config.columns.forEach((col) => { + const column: ColumnDef = { accessorKey: col.key, - header: ({ column: tableColumn }) => { - if (col.searchable) { - return ( -
- {col.label} - { - const v = tableColumn.getFilterValue() - return typeof v === 'string' ? v : '' - })()} - onChange={e => tableColumn.setFilterValue(e.target.value)} - style={{ minWidth: 0 }} - /> -
- ) - } - return col.label - }, + header: col.label, cell: ({ row }) => { - const value = row.getValue(col.key) - if (col.key === 'createdAt' || col.key === 'updatedAt') { + // 类型安全分流 + if (col.key === 'displayId' && isProblem) { + return (row.original as Problem).displayId + } + if ((col.key === 'createdAt' || col.key === 'updatedAt')) { + const value = row.getValue(col.key) if (value instanceof Date) { return value.toLocaleString() } @@ -224,7 +224,7 @@ export function UserTable({ config, data }: UserTableProps) { return new Date(value).toLocaleString() } } - return value + return row.getValue(col.key) }, enableSorting: col.sortable !== false, filterFn: col.searchable ? (row, columnId, value) => { @@ -235,13 +235,11 @@ export function UserTable({ config, data }: UserTableProps) { } columns.push(column) }) - - // 添加操作列 columns.push({ id: "actions", header: () =>
操作
, cell: ({ row }) => { - const user = row.original + const item = row.original return (
-
) }, }) - return columns - }, [config]) + }, [props.config, router, isProblem]) const table = useReactTable({ - data, + data: props.data, columns: tableColumns, state: { sorting, @@ -300,50 +296,33 @@ export function UserTable({ config, data }: UserTableProps) { getFacetedUniqueValues: getFacetedUniqueValues(), }) - // 生成唯一ID - function generateUniqueId(existingIds: string[]): string { - let id: string - do { - id = Math.random().toString(36).substr(2, 9) - } while (existingIds.includes(id)) - return id - } - // 添加用户对话框组件(仅用户) function AddUserDialogUser({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) { const [isLoading, setIsLoading] = useState(false) - const form = useForm({ + const form = useForm({ resolver: zodResolver(addUserSchema), - defaultValues: { name: "", email: "", password: "", createdAt: "" }, + defaultValues: { name: '', email: '', password: '', createdAt: '', image: null, emailVerified: null, role: Role.GUEST }, }) React.useEffect(() => { if (open) { - form.reset({ name: "", email: "", password: "", createdAt: "" }) + form.reset({ name: '', email: '', password: '', createdAt: '', image: null, emailVerified: null, role: Role.GUEST }) } }, [open, form]) - async function onSubmit(formData: any) { + async function onSubmit(data: AddUserForm) { try { setIsLoading(true) const submitData = { - ...formData, - // 移除手动生成的 id,让数据库自动生成 - // 移除 createdAt,让数据库自动设置 + ...data, + image: data.image ?? null, + emailVerified: data.emailVerified ?? null, + role: data.role ?? Role.GUEST, } - // 清理空字段 - if (!submitData.name) delete submitData.name - if (!submitData.password) delete submitData.password - if (!submitData.createdAt) delete submitData.createdAt - - // 如果用户提供了创建时间,转换为完整的 ISO-8601 格式 - if (submitData.createdAt) { - const date = new Date(submitData.createdAt) - submitData.createdAt = date.toISOString() - } - - if (config.userType === 'admin') await createAdmin(submitData) - else if (config.userType === 'teacher') await createTeacher(submitData) - else if (config.userType === 'guest') await createGuest(submitData) - else if (config.userType === 'problem') await createProblem(submitData) + if (!submitData.name) submitData.name = '' + if (!submitData.createdAt) submitData.createdAt = new Date().toISOString() + else submitData.createdAt = new Date(submitData.createdAt).toISOString() + if (props.config.userType === 'admin') await createAdmin(submitData) + else if (props.config.userType === 'teacher') await createTeacher(submitData) + else if (props.config.userType === 'guest') await createGuest(submitData) onOpenChange(false) toast.success('添加成功', { duration: 1500 }) router.refresh() @@ -354,33 +333,32 @@ export function UserTable({ config, data }: UserTableProps) { setIsLoading(false) } } - return ( - {config.actions.add.label} + {props.config.actions.add.label} 请填写信息,ID自动生成。
- {config.formFields.map((field) => ( + {props.config.formFields.filter(field => field.key !== 'id').map((field) => (
{field.type === 'select' && field.options ? ( @@ -416,27 +394,28 @@ export function UserTable({ config, data }: UserTableProps) { // 添加题目对话框组件(仅题目) function AddUserDialogProblem({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) { const [isLoading, setIsLoading] = useState(false) - const form = useForm({ + const form = useForm>({ resolver: zodResolver(addProblemSchema), - defaultValues: { displayId: 0, difficulty: "" }, + defaultValues: { displayId: 0, difficulty: Difficulty.EASY }, }) React.useEffect(() => { if (open) { - form.reset({ displayId: 0, difficulty: "" }) + form.reset({ displayId: 0, difficulty: Difficulty.EASY }) } }, [open, form]) - async function onSubmit(formData: any) { + async function onSubmit(formData: Partial) { try { setIsLoading(true) - const submitData = { - ...formData, - displayId: Number(formData.displayId), - // 移除手动生成的 id,让数据库自动生成 - } - if (config.userType === 'admin') await createAdmin(submitData) - else if (config.userType === 'teacher') await createTeacher(submitData) - else if (config.userType === 'guest') await createGuest(submitData) - else if (config.userType === 'problem') await createProblem(submitData) + const submitData: Partial = { ...formData, displayId: Number(formData.displayId) } + await createProblem({ + displayId: Number(submitData.displayId), + difficulty: submitData.difficulty ?? Difficulty.EASY, + isPublished: false, + isTrim: false, + timeLimit: 1000, + memoryLimit: 134217728, + userId: null, + }) onOpenChange(false) toast.success('添加成功', { duration: 1500 }) router.refresh() @@ -447,30 +426,45 @@ export function UserTable({ config, data }: UserTableProps) { setIsLoading(false) } } - return ( - {config.actions.add.label} + {props.config.actions.add.label} 请填写信息,ID自动生成。
- {config.formFields.map((field) => ( + {props.config.formFields.map((field) => (
- + {field.key === 'difficulty' ? ( + + ) : ( + + )} {form.formState.errors[field.key as keyof typeof form.formState.errors]?.message && (

{form.formState.errors[field.key as keyof typeof form.formState.errors]?.message as string} @@ -491,31 +485,49 @@ export function UserTable({ config, data }: UserTableProps) { } // 编辑用户对话框组件(仅用户) - function EditUserDialogUser({ open, onOpenChange, user }: { open: boolean; onOpenChange: (open: boolean) => void; user: any }) { + function EditUserDialogUser({ open, onOpenChange, user }: { open: boolean; onOpenChange: (open: boolean) => void; user: User }) { const [isLoading, setIsLoading] = useState(false) - const form = useForm({ + const editForm = useForm({ resolver: zodResolver(editUserSchema), - defaultValues: { id: user.id, name: user.name || "", email: user.email || "", password: "", role: user.role || "", createdAt: user.createdAt ? new Date(user.createdAt).toISOString().slice(0, 16) : new Date().toISOString().slice(0, 16) }, + defaultValues: { + id: typeof user.id === 'string' ? user.id : '', + name: user.name ?? '', + email: user.email ?? '', + password: '', + role: user.role ?? Role.GUEST, + createdAt: user.createdAt ? new Date(user.createdAt).toISOString().slice(0, 16) : '', + image: user.image ?? null, + emailVerified: user.emailVerified ?? null, + }, }) React.useEffect(() => { if (open) { - form.reset({ id: user.id, name: user.name || "", email: user.email || "", password: "", role: user.role || "", createdAt: user.createdAt ? new Date(user.createdAt).toISOString().slice(0, 16) : new Date().toISOString().slice(0, 16) }) + editForm.reset({ + id: typeof user.id === 'string' ? user.id : '', + name: user.name ?? '', + email: user.email ?? '', + password: '', + role: user.role ?? Role.GUEST, + createdAt: user.createdAt ? new Date(user.createdAt).toISOString().slice(0, 16) : '', + image: user.image ?? null, + emailVerified: user.emailVerified ?? null, + }) } - }, [open, user, form]) - async function onSubmit(formData: any) { + }, [open, user, editForm]) + async function onSubmit(data: UserForm) { try { setIsLoading(true) const submitData = { - ...formData, - createdAt: formData.createdAt ? new Date(formData.createdAt).toISOString() : new Date().toISOString(), + ...data, + createdAt: data.createdAt ? new Date(data.createdAt).toISOString() : new Date().toISOString(), + image: data.image ?? null, + emailVerified: data.emailVerified ?? null, + role: data.role ?? Role.GUEST, } - if (!submitData.password) { - delete submitData.password; - } - if (config.userType === 'admin') await updateAdmin(submitData.id, submitData) - else if (config.userType === 'teacher') await updateTeacher(submitData.id, submitData) - else if (config.userType === 'guest') await updateGuest(submitData.id, submitData) - else if (config.userType === 'problem') await updateProblem(submitData.id, submitData) + const id = typeof submitData.id === 'string' ? submitData.id : '' + if (props.config.userType === 'admin') await updateAdmin(id, submitData) + else if (props.config.userType === 'teacher') await updateTeacher(id, submitData) + else if (props.config.userType === 'guest') await updateGuest(id, submitData) onOpenChange(false) toast.success('修改成功', { duration: 1500 }) } catch { @@ -524,19 +536,18 @@ export function UserTable({ config, data }: UserTableProps) { setIsLoading(false) } } - return (

- {config.actions.edit.label} + {props.config.actions.edit.label} 修改信息 - +
- {config.formFields.map((field) => ( + {props.config.formFields.map((field) => (
))} - {/* 编辑时显示角色选择 */} - {config.userType !== 'problem' && ( + {props.config.userType !== 'problem' && (
- {form.formState.errors.role?.message && ( + {editForm.formState.errors.role?.message && (

- {form.formState.errors.role?.message as string} + {editForm.formState.errors.role?.message as string}

)}
@@ -606,28 +616,30 @@ export function UserTable({ config, data }: UserTableProps) { } // 编辑题目对话框组件(仅题目) - function EditUserDialogProblem({ open, onOpenChange, user }: { open: boolean; onOpenChange: (open: boolean) => void; user: any }) { + function EditUserDialogProblem({ open, onOpenChange, user }: { open: boolean; onOpenChange: (open: boolean) => void; user: Problem }) { const [isLoading, setIsLoading] = useState(false) - const form = useForm({ + const form = useForm>({ resolver: zodResolver(editProblemSchema), - defaultValues: { id: user.id, displayId: Number(user.displayId), difficulty: user.difficulty || "" }, + defaultValues: { + id: user.id, + displayId: user.displayId ?? 0, + difficulty: user.difficulty ?? Difficulty.EASY, + }, }) React.useEffect(() => { if (open) { - form.reset({ id: user.id, displayId: Number(user.displayId), difficulty: user.difficulty || "" }) + form.reset({ + id: user.id, + displayId: user.displayId ?? 0, + difficulty: user.difficulty ?? Difficulty.EASY, + }) } }, [open, user, form]) - async function onSubmit(formData: any) { + async function onSubmit(formData: Partial) { try { setIsLoading(true) - const submitData = { - ...formData, - displayId: Number(formData.displayId), - } - if (config.userType === 'admin') await updateAdmin(submitData.id, submitData) - else if (config.userType === 'teacher') await updateTeacher(submitData.id, submitData) - else if (config.userType === 'guest') await updateGuest(submitData.id, submitData) - else if (config.userType === 'problem') await updateProblem(submitData.id, submitData) + const submitData: Partial = { ...formData, displayId: Number(formData.displayId) } + await updateProblem(submitData.id!, submitData) onOpenChange(false) toast.success('修改成功', { duration: 1500 }) } catch { @@ -636,12 +648,11 @@ export function UserTable({ config, data }: UserTableProps) { setIsLoading(false) } } - return ( - {config.actions.edit.label} + {props.config.actions.edit.label} 修改信息 @@ -666,16 +677,16 @@ export function UserTable({ config, data }: UserTableProps) {
{form.formState.errors.difficulty?.message && ( @@ -697,16 +708,14 @@ export function UserTable({ config, data }: UserTableProps) { } // 用ref保证获取最新data - const dataRef = React.useRef(data) - React.useEffect(() => { dataRef.current = data }, [data]) - - const router = useRouter() + const dataRef = React.useRef(props.data) + React.useEffect(() => { dataRef.current = props.data }, [props.data]) return (
- {config.title} + {props.config.title}
@@ -751,25 +760,32 @@ export function UserTable({ config, data }: UserTableProps) { })} - {isProblem && config.actions.add && ( + {isProblem && props.config.actions.add && ( )} - {!isProblem && config.actions.add && ( + {!isProblem && props.config.actions.add && ( )}
-
@@ -848,16 +863,15 @@ export function UserTable({ config, data }: UserTableProps) {
-
- +
共 {table.getFilteredRowModel().rows.length} 条记录 -
+

每页显示

- + ))} + +
第 {table.getState().pagination.pageIndex + 1} 页,共{" "} {table.getPageCount()} 页 -
+
- {/* 添加用户对话框 */} - {isProblem && config.actions.add ? ( + {isProblem && props.config.actions.add ? ( - ) : !isProblem && config.actions.add ? ( + ) : !isProblem && props.config.actions.add ? ( ) : null} - {/* 编辑用户对话框 */} {isProblem && editingUser ? ( - + ) : editingUser ? ( - + ) : null} - {/* 删除确认对话框 */} @@ -974,23 +985,16 @@ export function UserTable({ config, data }: UserTableProps) { variant="destructive" onClick={async () => { try { - if (deleteBatch) { + if (deleteBatch) { const selectedRows = table.getFilteredSelectedRowModel().rows for (const row of selectedRows) { if (isProblem) { - await deleteProblem(row.original.id) + await deleteProblem((row.original as Problem).id) } else { - await deleteAdmin(row.original.id) + await deleteAdmin((row.original as User).id) } } toast.success(`成功删除 ${selectedRows.length} 条记录`, { duration: 1500 }) - } else if (deleteTargetId) { - if (isProblem) { - await deleteProblem(deleteTargetId) - } else { - await deleteAdmin(deleteTargetId) - } - toast.success('删除成功', { duration: 1500 }) } setDeleteDialogOpen(false) router.refresh() @@ -1004,6 +1008,35 @@ export function UserTable({ config, data }: UserTableProps) { + + + + 确认删除 + +
确定要删除该条数据吗?此操作不可撤销。
+ + + + +
+
) } \ No newline at end of file diff --git a/tailwind.config.ts b/tailwind.config.ts index 25ee55d..f87afdb 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -63,8 +63,6 @@ export default { 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', border: 'hsl(var(--sidebar-border))', ring: 'hsl(var(--sidebar-ring))', - 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', - 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))' } }, borderRadius: {