From 360399bdfb2a21f53b26c3289c28074083b67ca1 Mon Sep 17 00:00:00 2001 From: liguang <1590686939@qq.com> Date: Fri, 20 Jun 2025 15:29:50 +0800 Subject: [PATCH] =?UTF-8?q?refactor(user-management):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除原有的 user 和 problem API,改为使用 Prisma 直接操作数据库 - 新增 admin、teacher、guest 和 problem 的 CRUD 操作 - 优化用户表格组件,支持角色选择和难度选择 - 重构页面组件,使用 Prisma 查询数据 - 更新数据库迁移,增加 TEACHER 角色 --- .../20250619101509_beiyu/migration.sql | 19 ++ prisma/schema.prisma | 1 + src/api/problem.ts | 40 ---- src/api/user.ts | 52 ----- .../usermanagement/_actions/adminActions.ts | 23 ++ .../usermanagement/_actions/guestActions.ts | 23 ++ .../usermanagement/_actions/problemActions.ts | 18 ++ .../usermanagement/_actions/teacherActions.ts | 23 ++ src/app/(app)/usermanagement/admin/page.tsx | 9 +- .../{student => guest}/layout.tsx | 2 +- src/app/(app)/usermanagement/guest/page.tsx | 7 + src/app/(app)/usermanagement/problem/page.tsx | 9 +- src/app/(app)/usermanagement/student/page.tsx | 5 - src/app/(app)/usermanagement/teacher/page.tsx | 9 +- src/app/api/problem/route.ts | 96 -------- src/app/api/user/route.ts | 189 --------------- .../user-management/components/user-table.tsx | 219 +++++++++++++----- src/features/user-management/config/admin.ts | 6 +- .../config/{student.ts => guest.ts} | 21 +- .../user-management/config/problem.ts | 1 + .../user-management/config/teacher.ts | 3 +- src/features/user-management/index.tsx | 9 +- src/types/user.ts | 2 +- 23 files changed, 315 insertions(+), 471 deletions(-) create mode 100644 prisma/migrations/20250619101509_beiyu/migration.sql delete mode 100644 src/api/problem.ts delete mode 100644 src/api/user.ts create mode 100644 src/app/(app)/usermanagement/_actions/adminActions.ts create mode 100644 src/app/(app)/usermanagement/_actions/guestActions.ts create mode 100644 src/app/(app)/usermanagement/_actions/problemActions.ts create mode 100644 src/app/(app)/usermanagement/_actions/teacherActions.ts rename src/app/(app)/usermanagement/{student => guest}/layout.tsx (64%) create mode 100644 src/app/(app)/usermanagement/guest/page.tsx delete mode 100644 src/app/(app)/usermanagement/student/page.tsx delete mode 100644 src/app/api/problem/route.ts delete mode 100644 src/app/api/user/route.ts rename src/features/user-management/config/{student.ts => guest.ts} (79%) diff --git a/prisma/migrations/20250619101509_beiyu/migration.sql b/prisma/migrations/20250619101509_beiyu/migration.sql new file mode 100644 index 0000000..aeae9bf --- /dev/null +++ b/prisma/migrations/20250619101509_beiyu/migration.sql @@ -0,0 +1,19 @@ +/* + Warnings: + + - The values [GUEST] on the enum `Role` will be removed. If these variants are still used in the database, this will fail. + +*/ +-- AlterEnum +BEGIN; +CREATE TYPE "Role_new" AS ENUM ('ADMIN', 'TEACHER', 'GUEST'); +ALTER TABLE "User" ALTER COLUMN "role" DROP DEFAULT; +ALTER TABLE "User" ALTER COLUMN "role" TYPE "Role_new" USING ("role"::text::"Role_new"); +ALTER TYPE "Role" RENAME TO "Role_old"; +ALTER TYPE "Role_new" RENAME TO "Role"; +DROP TYPE "Role_old"; +ALTER TABLE "User" ALTER COLUMN "role" SET DEFAULT 'GUEST'; +COMMIT; + +-- AlterTable +ALTER TABLE "User" ALTER COLUMN "role" SET DEFAULT 'GUEST'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 17488cd..067bfb6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,6 +10,7 @@ generator client { enum Role { ADMIN + TEACHER GUEST } diff --git a/src/api/problem.ts b/src/api/problem.ts deleted file mode 100644 index 11cf54a..0000000 --- a/src/api/problem.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { Problem } from "@/types/problem"; - -// 获取所有题目 -export async function getProblems(): Promise { - const res = await fetch("/api/problem"); - if (!res.ok) throw new Error("获取题目失败"); - return res.json(); -} - -// 新建题目 -export async function createProblem(data: Partial): Promise { - const res = await fetch("/api/problem", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data), - }); - if (!res.ok) throw new Error("新建题目失败"); - return res.json(); -} - -// 编辑题目 -export async function updateProblem(data: Partial): Promise { - const res = await fetch("/api/problem", { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data), - }); - if (!res.ok) throw new Error("更新题目失败"); - return res.json(); -} - -// 删除题目 -export async function deleteProblem(id: string): Promise { - const res = await fetch("/api/problem", { - method: "DELETE", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ id }), - }); - if (!res.ok) throw new Error("删除题目失败"); -} \ No newline at end of file diff --git a/src/api/user.ts b/src/api/user.ts deleted file mode 100644 index f08cb61..0000000 --- a/src/api/user.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { UserBase } from "@/types/user"; - -// 获取所有用户 -export async function getUsers(userType: string): Promise { - const res = await fetch(`/api/user`); - if (!res.ok) { - const error = await res.json(); - throw new Error(error.error || "获取用户失败"); - } - return res.json(); -} - -// 新建用户 -export async function createUser(userType: string, data: Partial): Promise { - const res = await fetch(`/api/user`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data), - }); - if (!res.ok) { - const error = await res.json(); - throw new Error(error.error || "新建用户失败"); - } - return res.json(); -} - -// 更新用户 -export async function updateUser(userType: string, data: Partial): Promise { - const res = await fetch(`/api/user`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data), - }); - if (!res.ok) { - const error = await res.json(); - throw new Error(error.error || "更新用户失败"); - } - return res.json(); -} - -// 删除用户 -export async function deleteUser(userType: string, id: string): Promise { - const res = await fetch(`/api/user`, { - method: "DELETE", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ id }), - }); - if (!res.ok) { - const error = await res.json(); - throw new Error(error.error || "删除用户失败"); - } -} \ No newline at end of file diff --git a/src/app/(app)/usermanagement/_actions/adminActions.ts b/src/app/(app)/usermanagement/_actions/adminActions.ts new file mode 100644 index 0000000..9874ba1 --- /dev/null +++ b/src/app/(app)/usermanagement/_actions/adminActions.ts @@ -0,0 +1,23 @@ +'use server' +import prisma from '@/lib/prisma' +import { revalidatePath } from 'next/cache' +import bcrypt from 'bcryptjs' + +export async function createAdmin(data) { + let password = data.password + if (password) { + password = await bcrypt.hash(password, 10) + } + await prisma.user.create({ data: { ...data, password, role: 'ADMIN' } }) + revalidatePath('/usermanagement/admin') +} + +export async function updateAdmin(id, data) { + await prisma.user.update({ where: { id }, data }) + revalidatePath('/usermanagement/admin') +} + +export async function deleteAdmin(id) { + 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 new file mode 100644 index 0000000..cb1cdd8 --- /dev/null +++ b/src/app/(app)/usermanagement/_actions/guestActions.ts @@ -0,0 +1,23 @@ +'use server' +import prisma from '@/lib/prisma' +import { revalidatePath } from 'next/cache' +import bcrypt from 'bcryptjs' + +export async function createGuest(data) { + let password = data.password + if (password) { + password = await bcrypt.hash(password, 10) + } + await prisma.user.create({ data: { ...data, password, role: 'GUEST' } }) + revalidatePath('/usermanagement/guest') +} + +export async function updateGuest(id, data) { + await prisma.user.update({ where: { id }, data }) + revalidatePath('/usermanagement/guest') +} + +export async function deleteGuest(id) { + 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 new file mode 100644 index 0000000..6e682fa --- /dev/null +++ b/src/app/(app)/usermanagement/_actions/problemActions.ts @@ -0,0 +1,18 @@ +'use server' +import prisma from '@/lib/prisma' +import { revalidatePath } from 'next/cache' + +export async function createProblem(data) { + await prisma.problem.create({ data }) + revalidatePath('/usermanagement/problem') +} + +export async function updateProblem(id, data) { + await prisma.problem.update({ where: { id }, data }) + revalidatePath('/usermanagement/problem') +} + +export async function deleteProblem(id) { + 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 new file mode 100644 index 0000000..5326fce --- /dev/null +++ b/src/app/(app)/usermanagement/_actions/teacherActions.ts @@ -0,0 +1,23 @@ +'use server' +import prisma from '@/lib/prisma' +import { revalidatePath } from 'next/cache' +import bcrypt from 'bcryptjs' + +export async function createTeacher(data) { + let password = data.password + if (password) { + password = await bcrypt.hash(password, 10) + } + await prisma.user.create({ data: { ...data, password, role: 'TEACHER' } }) + revalidatePath('/usermanagement/teacher') +} + +export async function updateTeacher(id, data) { + await prisma.user.update({ where: { id }, data }) + revalidatePath('/usermanagement/teacher') +} + +export async function deleteTeacher(id) { + 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 fc631b5..d5e41dc 100644 --- a/src/app/(app)/usermanagement/admin/page.tsx +++ b/src/app/(app)/usermanagement/admin/page.tsx @@ -1,5 +1,8 @@ -import { UserManagement } from "@/features/user-management" +import { UserTable } from '@/features/user-management/components/user-table' +import { adminConfig } from '@/features/user-management/config/admin' +import prisma from '@/lib/prisma' -export default function Page() { - return +export default async function AdminPage() { + const data = await prisma.user.findMany({ where: { role: 'ADMIN' } }) + return } \ No newline at end of file diff --git a/src/app/(app)/usermanagement/student/layout.tsx b/src/app/(app)/usermanagement/guest/layout.tsx similarity index 64% rename from src/app/(app)/usermanagement/student/layout.tsx rename to src/app/(app)/usermanagement/guest/layout.tsx index f5a6a03..a29c947 100644 --- a/src/app/(app)/usermanagement/student/layout.tsx +++ b/src/app/(app)/usermanagement/guest/layout.tsx @@ -1,5 +1,5 @@ import ProtectedLayout from "../_components/ProtectedLayout"; -export default function StudentLayout({ children }: { children: React.ReactNode }) { +export default function GuestLayout({ children }: { children: React.ReactNode }) { return {children}; } \ No newline at end of file diff --git a/src/app/(app)/usermanagement/guest/page.tsx b/src/app/(app)/usermanagement/guest/page.tsx new file mode 100644 index 0000000..b64edbf --- /dev/null +++ b/src/app/(app)/usermanagement/guest/page.tsx @@ -0,0 +1,7 @@ +import { UserTable } from '@/features/user-management/components/user-table' +import { guestConfig } from '@/features/user-management/config/guest' +import prisma from '@/lib/prisma' +export default async function GuestPage() { + const data = await prisma.user.findMany({ where: { role: 'GUEST' as any } }) + 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 40af293..08bca0b 100644 --- a/src/app/(app)/usermanagement/problem/page.tsx +++ b/src/app/(app)/usermanagement/problem/page.tsx @@ -1,5 +1,8 @@ -import { UserManagement } from "@/features/user-management" +import { UserTable } from '@/features/user-management/components/user-table' +import { problemConfig } from '@/features/user-management/config/problem' +import prisma from '@/lib/prisma' -export default function Page() { - return +export default async function ProblemPage() { + const data = await prisma.problem.findMany({}) + return } \ No newline at end of file diff --git a/src/app/(app)/usermanagement/student/page.tsx b/src/app/(app)/usermanagement/student/page.tsx deleted file mode 100644 index 7b750d9..0000000 --- a/src/app/(app)/usermanagement/student/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { UserManagement } from "@/features/user-management" - -export default function Page() { - 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 3983daf..13d854d 100644 --- a/src/app/(app)/usermanagement/teacher/page.tsx +++ b/src/app/(app)/usermanagement/teacher/page.tsx @@ -1,5 +1,8 @@ -import { UserManagement } from "@/features/user-management" +import { UserTable } from '@/features/user-management/components/user-table' +import { teacherConfig } from '@/features/user-management/config/teacher' +import prisma from '@/lib/prisma' -export default function Page() { - return +export default async function TeacherPage() { + const data = await prisma.user.findMany({ where: { role: 'TEACHER' as any } }) + return } \ No newline at end of file diff --git a/src/app/api/problem/route.ts b/src/app/api/problem/route.ts deleted file mode 100644 index 75656c5..0000000 --- a/src/app/api/problem/route.ts +++ /dev/null @@ -1,96 +0,0 @@ -import prisma from "@/lib/prisma"; -import { NextRequest, NextResponse } from "next/server"; -import { auth } from "@/lib/auth"; - -// 获取所有题目 -export async function GET() { - try { - // 权限校验(可选:只允许管理员/教师) - const session = await auth(); - if (!session?.user?.id) { - return NextResponse.json({ error: "未授权" }, { status: 401 }); - } - // 可根据需要校验角色 - // const user = await prisma.user.findUnique({ where: { id: session.user.id }, select: { role: true } }); - // if (user?.role !== "ADMIN" && user?.role !== "TEACHER") { - // return NextResponse.json({ error: "权限不足" }, { status: 403 }); - // } - const problems = await prisma.problem.findMany({ - select: { - id: true, - displayId: true, - difficulty: true, - } - }); - return NextResponse.json(problems); - } catch (error) { - console.error("获取题目列表失败:", error); - return NextResponse.json({ error: "服务器错误" }, { status: 500 }); - } -} - -// 新建题目 -export async function POST(req: NextRequest) { - try { - const session = await auth(); - if (!session?.user?.id) { - return NextResponse.json({ error: "未授权" }, { status: 401 }); - } - // 只允许管理员/教师添加 - // const user = await prisma.user.findUnique({ where: { id: session.user.id }, select: { role: true } }); - // if (user?.role !== "ADMIN" && user?.role !== "TEACHER") { - // return NextResponse.json({ error: "权限不足" }, { status: 403 }); - // } - const data = await req.json(); - const newProblem = await prisma.problem.create({ data }); - return NextResponse.json(newProblem); - } catch (error) { - console.error("创建题目失败:", error); - return NextResponse.json({ error: "服务器错误" }, { status: 500 }); - } -} - -// 编辑题目 -export async function PUT(req: NextRequest) { - try { - const session = await auth(); - if (!session?.user?.id) { - return NextResponse.json({ error: "未授权" }, { status: 401 }); - } - // 只允许管理员/教师编辑 - // const user = await prisma.user.findUnique({ where: { id: session.user.id }, select: { role: true } }); - // if (user?.role !== "ADMIN" && user?.role !== "TEACHER") { - // return NextResponse.json({ error: "权限不足" }, { status: 403 }); - // } - const data = await req.json(); - const updatedProblem = await prisma.problem.update({ - where: { id: data.id }, - data, - }); - return NextResponse.json(updatedProblem); - } catch (error) { - console.error("更新题目失败:", error); - return NextResponse.json({ error: "服务器错误" }, { status: 500 }); - } -} - -// 删除题目 -export async function DELETE(req: NextRequest) { - try { - const session = await auth(); - if (!session?.user?.id) { - return NextResponse.json({ error: "未授权" }, { status: 401 }); - } - // 只允许管理员/教师删除 - // const user = await prisma.user.findUnique({ where: { id: session.user.id }, select: { role: true } }); - // if (user?.role !== "ADMIN" && user?.role !== "TEACHER") { - // return NextResponse.json({ error: "权限不足" }, { status: 403 }); - // } - const { id } = await req.json(); - await prisma.problem.delete({ where: { id } }); - return NextResponse.json({ success: true }); - } catch (error) { - console.error("删除题目失败:", error); - return NextResponse.json({ error: "服务器错误" }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts deleted file mode 100644 index 1dd5df6..0000000 --- a/src/app/api/user/route.ts +++ /dev/null @@ -1,189 +0,0 @@ -import prisma from "@/lib/prisma"; -import { NextRequest, NextResponse } from "next/server"; -import bcrypt from "bcryptjs"; -import { auth } from "@/lib/auth"; - -// 获取所有用户 -export async function GET() { - try { - // 验证管理员权限 - const session = await auth(); - if (!session?.user?.id) { - return NextResponse.json({ error: "未授权" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { id: session.user.id }, - select: { role: true } - }); - - if (user?.role !== "ADMIN") { - return NextResponse.json({ error: "权限不足" }, { status: 403 }); - } - - const users = await prisma.user.findMany({ - select: { - id: true, - name: true, - email: true, - role: true, - createdAt: true, - updatedAt: true, - password: true, // 包含密码字段用于处理 - } - }); - - // 在服务器端处理密码显示逻辑 - const processedUsers = users.map(user => ({ - ...user, - password: user.password ? "******" : "(无)", // 服务器端处理密码显示 - createdAt: user.createdAt instanceof Date ? user.createdAt.toLocaleString() : user.createdAt, // 服务器端处理日期格式 - })); - - return NextResponse.json(processedUsers); - } catch (error) { - console.error("获取用户列表失败:", error); - return NextResponse.json({ error: "服务器错误" }, { status: 500 }); - } -} - -// 新建用户 -export async function POST(req: NextRequest) { - try { - // 验证管理员权限 - const session = await auth(); - if (!session?.user?.id) { - return NextResponse.json({ error: "未授权" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { id: session.user.id }, - select: { role: true } - }); - - if (user?.role !== "ADMIN") { - return NextResponse.json({ error: "权限不足" }, { status: 403 }); - } - - const data = await req.json(); - - // 如果提供了密码,进行加密 - if (data.password) { - data.password = await bcrypt.hash(data.password, 10); - } - - const newUser = await prisma.user.create({ - data, - select: { - id: true, - name: true, - email: true, - role: true, - createdAt: true, - updatedAt: true, - password: true, - } - }); - - // 处理返回数据 - const processedUser = { - ...newUser, - password: newUser.password ? "******" : "(无)", - createdAt: newUser.createdAt instanceof Date ? newUser.createdAt.toLocaleString() : newUser.createdAt, - }; - - return NextResponse.json(processedUser); - } catch (error) { - console.error("创建用户失败:", error); - return NextResponse.json({ error: "服务器错误" }, { status: 500 }); - } -} - -// 编辑用户 -export async function PUT(req: NextRequest) { - try { - // 验证管理员权限 - const session = await auth(); - if (!session?.user?.id) { - return NextResponse.json({ error: "未授权" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { id: session.user.id }, - select: { role: true } - }); - - if (user?.role !== "ADMIN") { - return NextResponse.json({ error: "权限不足" }, { status: 403 }); - } - - const data = await req.json(); - - // 如果提供了密码且不为空,进行加密 - if (data.password && data.password.trim() !== '') { - data.password = await bcrypt.hash(data.password, 10); - } else { - // 如果密码为空,则不更新密码字段 - delete data.password; - } - - const updatedUser = await prisma.user.update({ - where: { id: data.id }, - data, - select: { - id: true, - name: true, - email: true, - role: true, - createdAt: true, - updatedAt: true, - password: true, - } - }); - - // 处理返回数据 - const processedUser = { - ...updatedUser, - password: updatedUser.password ? "******" : "(无)", - createdAt: updatedUser.createdAt instanceof Date ? updatedUser.createdAt.toLocaleString() : updatedUser.createdAt, - }; - - return NextResponse.json(processedUser); - } catch (error) { - console.error("更新用户失败:", error); - return NextResponse.json({ error: "服务器错误" }, { status: 500 }); - } -} - -// 删除用户 -export async function DELETE(req: NextRequest) { - try { - // 验证管理员权限 - const session = await auth(); - if (!session?.user?.id) { - return NextResponse.json({ error: "未授权" }, { status: 401 }); - } - - const user = await prisma.user.findUnique({ - where: { id: session.user.id }, - select: { role: true } - }); - - if (user?.role !== "ADMIN") { - return NextResponse.json({ error: "权限不足" }, { status: 403 }); - } - - const { id } = await req.json(); - - // 防止删除自己 - if (id === session.user.id) { - return NextResponse.json({ error: "不能删除自己的账户" }, { status: 400 }); - } - - await prisma.user.delete({ where: { id } }); - return NextResponse.json({ success: true }); - } catch (error) { - console.error("删除用户失败:", error); - return NextResponse.json({ error: "服务器错误" }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/features/user-management/components/user-table.tsx b/src/features/user-management/components/user-table.tsx index d17aa07..185dd5d 100644 --- a/src/features/user-management/components/user-table.tsx +++ b/src/features/user-management/components/user-table.tsx @@ -30,6 +30,7 @@ import { useState, useEffect } from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { z } from "zod" +import { useRouter } from "next/navigation" import { Dialog, @@ -68,8 +69,10 @@ import { Tabs, } from "@/components/ui/tabs" -import * as userApi from "@/api/user" -import * as problemApi from "@/api/problem" +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 { createProblem, updateProblem, deleteProblem } from '@/app/(app)/usermanagement/_actions/problemActions' // 通用用户类型 export interface UserConfig { @@ -89,6 +92,7 @@ export interface UserConfig { type: string placeholder?: string required?: boolean + options?: Array<{ value: string; label: string }> }> actions: { add: { label: string; icon: string } @@ -112,7 +116,7 @@ const addUserSchema = z.object({ name: z.string().optional(), email: z.string().email("请输入有效的邮箱地址"), password: z.string().optional(), - createdAt: z.string(), + createdAt: z.string().optional(), }) const editUserSchema = z.object({ @@ -120,6 +124,7 @@ const editUserSchema = z.object({ name: z.string().optional(), email: z.string().email("请输入有效的邮箱地址"), password: z.string().optional(), + role: z.string().optional(), createdAt: z.string(), }) @@ -134,8 +139,7 @@ const editProblemSchema = z.object({ difficulty: z.string(), }) -export function UserTable({ config, data: initialData }: UserTableProps) { - const [data, setData] = useState(initialData) +export function UserTable({ config, data }: UserTableProps) { const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) const [editingUser, setEditingUser] = useState(null) @@ -212,6 +216,14 @@ export function UserTable({ config, data: initialData }: UserTableProps) { }, cell: ({ row }) => { const value = row.getValue(col.key) + if (col.key === 'createdAt' || col.key === 'updatedAt') { + if (value instanceof Date) { + return value.toLocaleString() + } + if (typeof value === 'string' && !isNaN(Date.parse(value))) { + return new Date(value).toLocaleString() + } + } return value }, enableSorting: col.sortable !== false, @@ -288,19 +300,6 @@ export function UserTable({ config, data: initialData }: UserTableProps) { getFacetedUniqueValues: getFacetedUniqueValues(), }) - // 数据加载与API对接 - useEffect(() => { - if (isProblem) { - problemApi.getProblems() - .then(setData) - .catch(() => toast.error('获取数据失败', { duration: 1500 })) - } else { - userApi.getUsers(config.userType) - .then(setData) - .catch(() => toast.error('获取数据失败', { duration: 1500 })) - } - }, [config.userType]) - // 生成唯一ID function generateUniqueId(existingIds: string[]): string { let id: string @@ -315,28 +314,41 @@ export function UserTable({ config, data: initialData }: UserTableProps) { const [isLoading, setIsLoading] = useState(false) const form = useForm({ resolver: zodResolver(addUserSchema), - defaultValues: { name: "", email: "", password: "", createdAt: new Date().toISOString().slice(0, 16) }, + defaultValues: { name: "", email: "", password: "", createdAt: "" }, }) React.useEffect(() => { if (open) { - form.reset({ name: "", email: "", password: "", createdAt: new Date().toISOString().slice(0, 16) }) + form.reset({ name: "", email: "", password: "", createdAt: "" }) } }, [open, form]) async function onSubmit(formData: any) { try { setIsLoading(true) - const existingIds = dataRef.current.map(item => item.id) - const id = generateUniqueId(existingIds) const submitData = { ...formData, - id, - createdAt: formData.createdAt ? new Date(formData.createdAt).toISOString() : new Date().toISOString(), + // 移除手动生成的 id,让数据库自动生成 + // 移除 createdAt,让数据库自动设置 } - await userApi.createUser(config.userType, submitData) - userApi.getUsers(config.userType).then(setData) + // 清理空字段 + 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) onOpenChange(false) toast.success('添加成功', { duration: 1500 }) - } catch { + router.refresh() + } catch (error) { + console.error('添加失败:', error) toast.error('添加失败', { duration: 1500 }) } finally { setIsLoading(false) @@ -359,13 +371,29 @@ export function UserTable({ config, data: initialData }: UserTableProps) { - + {field.type === 'select' && field.options ? ( + + ) : ( + + )} {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} @@ -400,18 +428,20 @@ export function UserTable({ config, data: initialData }: UserTableProps) { async function onSubmit(formData: any) { try { setIsLoading(true) - const existingIds = dataRef.current.map(item => item.id) - const id = generateUniqueId(existingIds) const submitData = { ...formData, displayId: Number(formData.displayId), - id, + // 移除手动生成的 id,让数据库自动生成 } - await problemApi.createProblem(submitData) - problemApi.getProblems().then(setData) + 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) onOpenChange(false) toast.success('添加成功', { duration: 1500 }) - } catch { + router.refresh() + } catch (error) { + console.error('添加失败:', error) toast.error('添加失败', { duration: 1500 }) } finally { setIsLoading(false) @@ -465,11 +495,11 @@ export function UserTable({ config, data: initialData }: UserTableProps) { const [isLoading, setIsLoading] = useState(false) const form = useForm({ resolver: zodResolver(editUserSchema), - defaultValues: { id: user.id, name: user.name || "", email: user.email || "", password: "", createdAt: user.createdAt ? new Date(user.createdAt).toISOString().slice(0, 16) : new Date().toISOString().slice(0, 16) }, + 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) }, }) React.useEffect(() => { if (open) { - form.reset({ id: user.id, name: user.name || "", email: user.email || "", password: "", createdAt: user.createdAt ? new Date(user.createdAt).toISOString().slice(0, 16) : new Date().toISOString().slice(0, 16) }) + 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) }) } }, [open, user, form]) async function onSubmit(formData: any) { @@ -482,8 +512,10 @@ export function UserTable({ config, data: initialData }: UserTableProps) { if (!submitData.password) { delete submitData.password; } - await userApi.updateUser(config.userType, submitData) - userApi.getUsers(config.userType).then(setData) + 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) onOpenChange(false) toast.success('修改成功', { duration: 1500 }) } catch { @@ -524,6 +556,43 @@ export function UserTable({ config, data: initialData }: UserTableProps) { )} ))} + + {/* 编辑时显示角色选择 */} + {config.userType !== 'problem' && ( +

+ + + {form.formState.errors.role?.message && ( +

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

+ )} +
+ )} + )} + {!isProblem && config.actions.add && (