mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-07-04 09:20:53 +00:00
refactor(user-management): 重构用户管理功能
- 合并 adminActions、teacherActions 和 guestActions 到统一的 userActions - 创建通用的用户配置和页面组件 - 优化用户表单验证逻辑 - 调整布局和页面结构
This commit is contained in:
parent
03ccd285be
commit
7cdf5fe6b8
@ -1,34 +0,0 @@
|
|||||||
'use server'
|
|
||||||
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: Omit<User, 'id'|'createdAt'|'updatedAt'> & { password?: string }) {
|
|
||||||
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: string, data: Partial<Omit<User, 'id'|'createdAt'|'updatedAt'>>) {
|
|
||||||
let updateData = { ...data }
|
|
||||||
|
|
||||||
// 如果包含密码字段且不为空,则进行加密
|
|
||||||
if (data.password && data.password.trim() !== '') {
|
|
||||||
updateData.password = await bcrypt.hash(data.password, 10)
|
|
||||||
} else {
|
|
||||||
// 如果密码为空,则从更新数据中移除密码字段,保持原密码不变
|
|
||||||
delete updateData.password
|
|
||||||
}
|
|
||||||
|
|
||||||
await prisma.user.update({ where: { id }, data: updateData })
|
|
||||||
revalidatePath('/usermanagement/admin')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteAdmin(id: string) {
|
|
||||||
await prisma.user.delete({ where: { id } })
|
|
||||||
revalidatePath('/usermanagement/admin')
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
'use server'
|
|
||||||
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: Omit<User, 'id'|'createdAt'|'updatedAt'> & { password?: string }) {
|
|
||||||
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: string, data: Partial<Omit<User, 'id'|'createdAt'|'updatedAt'>>) {
|
|
||||||
let updateData = { ...data }
|
|
||||||
|
|
||||||
// 如果包含密码字段且不为空,则进行加密
|
|
||||||
if (data.password && data.password.trim() !== '') {
|
|
||||||
updateData.password = await bcrypt.hash(data.password, 10)
|
|
||||||
} else {
|
|
||||||
// 如果密码为空,则从更新数据中移除密码字段,保持原密码不变
|
|
||||||
delete updateData.password
|
|
||||||
}
|
|
||||||
|
|
||||||
await prisma.user.update({ where: { id }, data: updateData })
|
|
||||||
revalidatePath('/usermanagement/guest')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteGuest(id: string) {
|
|
||||||
await prisma.user.delete({ where: { id } })
|
|
||||||
revalidatePath('/usermanagement/guest')
|
|
||||||
}
|
|
@ -8,11 +8,6 @@ export async function createProblem(data: Omit<Problem, 'id'|'createdAt'|'update
|
|||||||
revalidatePath('/usermanagement/problem')
|
revalidatePath('/usermanagement/problem')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateProblem(id: string, data: Partial<Omit<Problem, 'id'|'createdAt'|'updatedAt'>>) {
|
|
||||||
await prisma.problem.update({ where: { id }, data })
|
|
||||||
revalidatePath('/usermanagement/problem')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteProblem(id: string) {
|
export async function deleteProblem(id: string) {
|
||||||
await prisma.problem.delete({ where: { id } })
|
await prisma.problem.delete({ where: { id } })
|
||||||
revalidatePath('/usermanagement/problem')
|
revalidatePath('/usermanagement/problem')
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
'use server'
|
|
||||||
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: Omit<User, 'id'|'createdAt'|'updatedAt'> & { password?: string }) {
|
|
||||||
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: string, data: Partial<Omit<User, 'id'|'createdAt'|'updatedAt'>>) {
|
|
||||||
let updateData = { ...data }
|
|
||||||
|
|
||||||
// 如果包含密码字段且不为空,则进行加密
|
|
||||||
if (data.password && data.password.trim() !== '') {
|
|
||||||
updateData.password = await bcrypt.hash(data.password, 10)
|
|
||||||
} else {
|
|
||||||
// 如果密码为空,则从更新数据中移除密码字段,保持原密码不变
|
|
||||||
delete updateData.password
|
|
||||||
}
|
|
||||||
|
|
||||||
await prisma.user.update({ where: { id }, data: updateData })
|
|
||||||
revalidatePath('/usermanagement/teacher')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteTeacher(id: string) {
|
|
||||||
await prisma.user.delete({ where: { id } })
|
|
||||||
revalidatePath('/usermanagement/teacher')
|
|
||||||
}
|
|
46
src/app/(app)/usermanagement/_actions/userActions.ts
Normal file
46
src/app/(app)/usermanagement/_actions/userActions.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
'use server'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
import { revalidatePath } from 'next/cache'
|
||||||
|
import bcrypt from 'bcryptjs'
|
||||||
|
import type { User } from '@/generated/client'
|
||||||
|
import { Role } from '@/generated/client'
|
||||||
|
|
||||||
|
type UserType = 'admin' | 'teacher' | 'guest'
|
||||||
|
|
||||||
|
export async function createUser(
|
||||||
|
userType: UserType,
|
||||||
|
data: Omit<User, 'id'|'createdAt'|'updatedAt'> & { password?: string }
|
||||||
|
) {
|
||||||
|
let password = data.password
|
||||||
|
if (password) {
|
||||||
|
password = await bcrypt.hash(password, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = userType.toUpperCase() as Role
|
||||||
|
await prisma.user.create({ data: { ...data, password, role } })
|
||||||
|
revalidatePath(`/usermanagement/${userType}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateUser(
|
||||||
|
userType: UserType,
|
||||||
|
id: string,
|
||||||
|
data: Partial<Omit<User, 'id'|'createdAt'|'updatedAt'>>
|
||||||
|
) {
|
||||||
|
const updateData = { ...data }
|
||||||
|
|
||||||
|
// 如果包含密码字段且不为空,则进行加密
|
||||||
|
if (data.password && data.password.trim() !== '') {
|
||||||
|
updateData.password = await bcrypt.hash(data.password, 10)
|
||||||
|
} else {
|
||||||
|
// 如果密码为空,则从更新数据中移除密码字段,保持原密码不变
|
||||||
|
delete updateData.password
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.user.update({ where: { id }, data: updateData })
|
||||||
|
revalidatePath(`/usermanagement/${userType}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteUser(userType: UserType, id: string) {
|
||||||
|
await prisma.user.delete({ where: { id } })
|
||||||
|
revalidatePath(`/usermanagement/${userType}`)
|
||||||
|
}
|
10
src/app/(app)/usermanagement/_components/GenericLayout.tsx
Normal file
10
src/app/(app)/usermanagement/_components/GenericLayout.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import ProtectedLayout from "./ProtectedLayout";
|
||||||
|
|
||||||
|
interface GenericLayoutProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
allowedRoles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GenericLayout({ children, allowedRoles }: GenericLayoutProps) {
|
||||||
|
return <ProtectedLayout allowedRoles={allowedRoles}>{children}</ProtectedLayout>;
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import ProtectedLayout from "../_components/ProtectedLayout";
|
import GenericLayout from "../_components/GenericLayout";
|
||||||
|
|
||||||
export default function AdminLayout({ children }: { children: React.ReactNode }) {
|
export default function AdminLayout({ children }: { children: React.ReactNode }) {
|
||||||
return <ProtectedLayout allowedRoles={["ADMIN"]}>{children}</ProtectedLayout>;
|
return <GenericLayout allowedRoles={["ADMIN"]}>{children}</GenericLayout>;
|
||||||
}
|
}
|
@ -1,9 +1,6 @@
|
|||||||
import { UserTable } from '@/features/user-management/components/user-table'
|
import GenericPage from '@/features/user-management/components/generic-page'
|
||||||
import { adminConfig } from '@/features/user-management/config/admin'
|
import { adminConfig } from '@/features/user-management/config/admin'
|
||||||
import prisma from '@/lib/prisma'
|
|
||||||
import type { User } from '@/generated/client'
|
|
||||||
|
|
||||||
export default async function AdminPage() {
|
export default function AdminPage() {
|
||||||
const data: User[] = await prisma.user.findMany({ where: { role: 'ADMIN' } })
|
return <GenericPage userType="admin" config={adminConfig} />
|
||||||
return <UserTable config={adminConfig} data={data} />
|
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import ProtectedLayout from "../_components/ProtectedLayout";
|
import GenericLayout from "../_components/GenericLayout";
|
||||||
|
|
||||||
export default function GuestLayout({ children }: { children: React.ReactNode }) {
|
export default function GuestLayout({ children }: { children: React.ReactNode }) {
|
||||||
return <ProtectedLayout allowedRoles={["ADMIN", "TEACHER"]}>{children}</ProtectedLayout>;
|
return <GenericLayout allowedRoles={["ADMIN", "TEACHER"]}>{children}</GenericLayout>;
|
||||||
}
|
}
|
@ -1,9 +1,6 @@
|
|||||||
import { UserTable } from '@/features/user-management/components/user-table'
|
import GenericPage from '@/features/user-management/components/generic-page'
|
||||||
import { guestConfig } from '@/features/user-management/config/guest'
|
import { guestConfig } from '@/features/user-management/config/guest'
|
||||||
import prisma from '@/lib/prisma'
|
|
||||||
import type { User } from '@/generated/client'
|
|
||||||
|
|
||||||
export default async function GuestPage() {
|
export default function GuestPage() {
|
||||||
const data: User[] = await prisma.user.findMany({ where: { role: 'GUEST' } })
|
return <GenericPage userType="guest" config={guestConfig} />
|
||||||
return <UserTable config={guestConfig} data={data} />
|
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import ProtectedLayout from "../_components/ProtectedLayout";
|
import GenericLayout from "../_components/GenericLayout";
|
||||||
|
|
||||||
export default function ProblemLayout({ children }: { children: React.ReactNode }) {
|
export default function ProblemLayout({ children }: { children: React.ReactNode }) {
|
||||||
return <ProtectedLayout allowedRoles={["ADMIN", "TEACHER"]}>{children}</ProtectedLayout>;
|
return <GenericLayout allowedRoles={["ADMIN", "TEACHER"]}>{children}</GenericLayout>;
|
||||||
}
|
}
|
@ -1,9 +1,6 @@
|
|||||||
import { UserTable } from '@/features/user-management/components/user-table'
|
import GenericPage from '@/features/user-management/components/generic-page'
|
||||||
import { problemConfig } from '@/features/user-management/config/problem'
|
import { problemConfig } from '@/features/user-management/config/problem'
|
||||||
import prisma from '@/lib/prisma'
|
|
||||||
import type { Problem } from '@/generated/client'
|
|
||||||
|
|
||||||
export default async function ProblemPage() {
|
export default function ProblemPage() {
|
||||||
const data: Problem[] = await prisma.problem.findMany({})
|
return <GenericPage userType="problem" config={problemConfig} />
|
||||||
return <UserTable config={problemConfig} data={data} />
|
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import ProtectedLayout from "../_components/ProtectedLayout";
|
import GenericLayout from "../_components/GenericLayout";
|
||||||
|
|
||||||
export default function TeacherLayout({ children }: { children: React.ReactNode }) {
|
export default function TeacherLayout({ children }: { children: React.ReactNode }) {
|
||||||
return <ProtectedLayout allowedRoles={["ADMIN"]}>{children}</ProtectedLayout>;
|
return <GenericLayout allowedRoles={["ADMIN"]}>{children}</GenericLayout>;
|
||||||
}
|
}
|
@ -1,9 +1,6 @@
|
|||||||
import { UserTable } from '@/features/user-management/components/user-table'
|
import GenericPage from '@/features/user-management/components/generic-page'
|
||||||
import { teacherConfig } from '@/features/user-management/config/teacher'
|
import { teacherConfig } from '@/features/user-management/config/teacher'
|
||||||
import prisma from '@/lib/prisma'
|
|
||||||
import type { User } from '@/generated/client'
|
|
||||||
|
|
||||||
export default async function TeacherPage() {
|
export default function TeacherPage() {
|
||||||
const data: User[] = await prisma.user.findMany({ where: { role: 'TEACHER' } })
|
return <GenericPage userType="teacher" config={teacherConfig} />
|
||||||
return <UserTable config={teacherConfig} data={data} />
|
|
||||||
}
|
}
|
21
src/features/user-management/components/generic-page.tsx
Normal file
21
src/features/user-management/components/generic-page.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { UserTable } from './user-table'
|
||||||
|
import { UserConfig } from './user-table'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
import type { User, Problem } from '@/generated/client'
|
||||||
|
import { Role } from '@/generated/client'
|
||||||
|
|
||||||
|
interface GenericPageProps {
|
||||||
|
userType: 'admin' | 'teacher' | 'guest' | 'problem'
|
||||||
|
config: UserConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function GenericPage({ userType, config }: GenericPageProps) {
|
||||||
|
if (userType === 'problem') {
|
||||||
|
const data: Problem[] = await prisma.problem.findMany({})
|
||||||
|
return <UserTable config={config} data={data} />
|
||||||
|
} else {
|
||||||
|
const role = userType.toUpperCase() as Role
|
||||||
|
const data: User[] = await prisma.user.findMany({ where: { role } })
|
||||||
|
return <UserTable config={config} data={data} />
|
||||||
|
}
|
||||||
|
}
|
@ -69,9 +69,7 @@ import {
|
|||||||
Tabs,
|
Tabs,
|
||||||
} from "@/components/ui/tabs"
|
} from "@/components/ui/tabs"
|
||||||
|
|
||||||
import { createAdmin, updateAdmin, deleteAdmin } from '@/app/(app)/usermanagement/_actions/adminActions'
|
import { createUser, updateUser, deleteUser } from '@/app/(app)/usermanagement/_actions/userActions'
|
||||||
import { createTeacher, updateTeacher } from '@/app/(app)/usermanagement/_actions/teacherActions'
|
|
||||||
import { createGuest, updateGuest } from '@/app/(app)/usermanagement/_actions/guestActions'
|
|
||||||
import { createProblem, deleteProblem } from '@/app/(app)/usermanagement/_actions/problemActions'
|
import { createProblem, deleteProblem } from '@/app/(app)/usermanagement/_actions/problemActions'
|
||||||
import type { User, Problem } from '@/generated/client'
|
import type { User, Problem } from '@/generated/client'
|
||||||
import { Difficulty, Role } from '@/generated/client'
|
import { Difficulty, Role } from '@/generated/client'
|
||||||
@ -128,7 +126,7 @@ type AddUserForm = Omit<UserForm, 'id'>
|
|||||||
const addUserSchema = z.object({
|
const addUserSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
password: z.string(),
|
password: z.string().min(1, "密码不能为空").min(8, "密码长度至少8位"),
|
||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
image: z.string().nullable(),
|
image: z.string().nullable(),
|
||||||
emailVerified: z.date().nullable(),
|
emailVerified: z.date().nullable(),
|
||||||
@ -312,6 +310,13 @@ export function UserTable(props: UserTableProps) {
|
|||||||
async function onSubmit(data: AddUserForm) {
|
async function onSubmit(data: AddUserForm) {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
|
||||||
|
// 验证必填字段
|
||||||
|
if (!data.password || data.password.trim() === '') {
|
||||||
|
toast.error('密码不能为空', { duration: 1500 })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const submitData = {
|
const submitData = {
|
||||||
...data,
|
...data,
|
||||||
image: data.image ?? null,
|
image: data.image ?? null,
|
||||||
@ -321,9 +326,9 @@ export function UserTable(props: UserTableProps) {
|
|||||||
if (!submitData.name) submitData.name = ''
|
if (!submitData.name) submitData.name = ''
|
||||||
if (!submitData.createdAt) submitData.createdAt = new Date().toISOString()
|
if (!submitData.createdAt) submitData.createdAt = new Date().toISOString()
|
||||||
else submitData.createdAt = new Date(submitData.createdAt).toISOString()
|
else submitData.createdAt = new Date(submitData.createdAt).toISOString()
|
||||||
if (props.config.userType === 'admin') await createAdmin(submitData)
|
if (props.config.userType === 'admin') await createUser('admin', submitData)
|
||||||
else if (props.config.userType === 'teacher') await createTeacher(submitData)
|
else if (props.config.userType === 'teacher') await createUser('teacher', submitData)
|
||||||
else if (props.config.userType === 'guest') await createGuest(submitData)
|
else if (props.config.userType === 'guest') await createUser('guest', submitData)
|
||||||
onOpenChange(false)
|
onOpenChange(false)
|
||||||
toast.success('添加成功', { duration: 1500 })
|
toast.success('添加成功', { duration: 1500 })
|
||||||
router.refresh()
|
router.refresh()
|
||||||
@ -526,9 +531,9 @@ export function UserTable(props: UserTableProps) {
|
|||||||
role: data.role ?? Role.GUEST,
|
role: data.role ?? Role.GUEST,
|
||||||
}
|
}
|
||||||
const id = typeof submitData.id === 'string' ? submitData.id : ''
|
const id = typeof submitData.id === 'string' ? submitData.id : ''
|
||||||
if (props.config.userType === 'admin') await updateAdmin(id, submitData)
|
if (props.config.userType === 'admin') await updateUser('admin', id, submitData)
|
||||||
else if (props.config.userType === 'teacher') await updateTeacher(id, submitData)
|
else if (props.config.userType === 'teacher') await updateUser('teacher', id, submitData)
|
||||||
else if (props.config.userType === 'guest') await updateGuest(id, submitData)
|
else if (props.config.userType === 'guest') await updateUser('guest', id, submitData)
|
||||||
onOpenChange(false)
|
onOpenChange(false)
|
||||||
toast.success('修改成功', { duration: 1500 })
|
toast.success('修改成功', { duration: 1500 })
|
||||||
} catch {
|
} catch {
|
||||||
@ -898,7 +903,7 @@ export function UserTable(props: UserTableProps) {
|
|||||||
if (isProblem) {
|
if (isProblem) {
|
||||||
await deleteProblem((row.original as Problem).id)
|
await deleteProblem((row.original as Problem).id)
|
||||||
} else {
|
} else {
|
||||||
await deleteAdmin((row.original as User).id)
|
await deleteUser(props.config.userType as 'admin' | 'teacher' | 'guest', (row.original as User).id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toast.success(`成功删除 ${selectedRows.length} 条记录`, { duration: 1500 })
|
toast.success(`成功删除 ${selectedRows.length} 条记录`, { duration: 1500 })
|
||||||
@ -930,7 +935,7 @@ export function UserTable(props: UserTableProps) {
|
|||||||
if (isProblem) {
|
if (isProblem) {
|
||||||
await deleteProblem((pendingDeleteItem as Problem).id)
|
await deleteProblem((pendingDeleteItem as Problem).id)
|
||||||
} else {
|
} else {
|
||||||
await deleteAdmin((pendingDeleteItem as User).id)
|
await deleteUser(props.config.userType as 'admin' | 'teacher' | 'guest', (pendingDeleteItem as User).id)
|
||||||
}
|
}
|
||||||
toast.success('删除成功', { duration: 1500 })
|
toast.success('删除成功', { duration: 1500 })
|
||||||
router.refresh()
|
router.refresh()
|
||||||
|
@ -1,126 +1,23 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
import { createUserConfig, baseUserSchema, baseAddUserSchema, baseEditUserSchema } from './base-config'
|
||||||
|
|
||||||
// 管理员数据校验 schema
|
// 管理员数据校验 schema
|
||||||
export const adminSchema = z.object({
|
export const adminSchema = baseUserSchema
|
||||||
id: z.string(),
|
|
||||||
name: z.string().optional(),
|
|
||||||
email: z.string(),
|
|
||||||
password: z.string().optional(),
|
|
||||||
role: z.string().optional(),
|
|
||||||
createdAt: z.string(),
|
|
||||||
updatedAt: z.string().optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export type Admin = z.infer<typeof adminSchema>
|
export type Admin = z.infer<typeof adminSchema>
|
||||||
|
|
||||||
// 添加管理员表单校验 schema
|
// 添加管理员表单校验 schema
|
||||||
export const addAdminSchema = z.object({
|
export const addAdminSchema = baseAddUserSchema
|
||||||
name: z.string().min(1, "姓名为必填项"),
|
export type AddAdminFormData = z.infer<typeof addAdminSchema>
|
||||||
email: z.string().email("请输入有效的邮箱地址"),
|
|
||||||
password: z.string().min(8, "密码长度8-32位").max(32, "密码长度8-32位"),
|
|
||||||
createdAt: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// 编辑管理员表单校验 schema
|
// 编辑管理员表单校验 schema
|
||||||
export const editAdminSchema = z.object({
|
export const editAdminSchema = baseEditUserSchema
|
||||||
id: z.string(),
|
|
||||||
name: z.string().min(1, "姓名为必填项"),
|
|
||||||
email: z.string().email("请输入有效的邮箱地址"),
|
|
||||||
password: z.string().min(8, "密码长度8-32位").max(32, "密码长度8-32位"),
|
|
||||||
createdAt: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export type AddAdminFormData = z.infer<typeof addAdminSchema>
|
|
||||||
export type EditAdminFormData = z.infer<typeof editAdminSchema>
|
export type EditAdminFormData = z.infer<typeof editAdminSchema>
|
||||||
|
|
||||||
// 管理员配置
|
// 管理员配置
|
||||||
export const adminConfig = {
|
export const adminConfig = createUserConfig(
|
||||||
userType: "admin",
|
"admin",
|
||||||
title: "管理员列表",
|
"管理员列表",
|
||||||
apiPath: "/api/user",
|
"添加管理员",
|
||||||
|
"请输入管理员姓名",
|
||||||
// 表格列配置
|
"请输入管理员邮箱"
|
||||||
columns: [
|
)
|
||||||
{
|
|
||||||
key: "id",
|
|
||||||
label: "ID",
|
|
||||||
sortable: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
label: "姓名",
|
|
||||||
sortable: true,
|
|
||||||
searchable: true,
|
|
||||||
placeholder: "搜索姓名",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "email",
|
|
||||||
label: "邮箱",
|
|
||||||
sortable: true,
|
|
||||||
searchable: true,
|
|
||||||
placeholder: "搜索邮箱",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "createdAt",
|
|
||||||
label: "创建时间",
|
|
||||||
sortable: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
// 表单字段配置
|
|
||||||
formFields: [
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
label: "姓名",
|
|
||||||
type: "text",
|
|
||||||
placeholder: "请输入管理员姓名",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "email",
|
|
||||||
label: "邮箱",
|
|
||||||
type: "email",
|
|
||||||
placeholder: "请输入管理员邮箱",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "password",
|
|
||||||
label: "密码",
|
|
||||||
type: "password",
|
|
||||||
placeholder: "请输入8-32位密码",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "createdAt",
|
|
||||||
label: "创建时间",
|
|
||||||
type: "datetime-local",
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
// 操作按钮配置
|
|
||||||
actions: {
|
|
||||||
add: {
|
|
||||||
label: "添加管理员",
|
|
||||||
icon: "PlusIcon",
|
|
||||||
},
|
|
||||||
edit: {
|
|
||||||
label: "编辑",
|
|
||||||
icon: "PencilIcon",
|
|
||||||
},
|
|
||||||
delete: {
|
|
||||||
label: "删除",
|
|
||||||
icon: "TrashIcon",
|
|
||||||
},
|
|
||||||
batchDelete: {
|
|
||||||
label: "批量删除",
|
|
||||||
icon: "TrashIcon",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// 分页配置
|
|
||||||
pagination: {
|
|
||||||
pageSizes: [10, 50, 100, 500],
|
|
||||||
defaultPageSize: 10,
|
|
||||||
},
|
|
||||||
}
|
|
86
src/features/user-management/config/base-config.ts
Normal file
86
src/features/user-management/config/base-config.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
// 基础用户 schema
|
||||||
|
export const baseUserSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
email: z.string(),
|
||||||
|
password: z.string().optional(),
|
||||||
|
role: z.string().optional(),
|
||||||
|
createdAt: z.string(),
|
||||||
|
updatedAt: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// 基础添加用户 schema
|
||||||
|
export const baseAddUserSchema = z.object({
|
||||||
|
name: z.string().min(1, "姓名为必填项"),
|
||||||
|
email: z.string().email("请输入有效的邮箱地址"),
|
||||||
|
password: z.string().min(8, "密码长度8-32位").max(32, "密码长度8-32位"),
|
||||||
|
createdAt: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// 基础编辑用户 schema
|
||||||
|
export const baseEditUserSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string().min(1, "姓名为必填项"),
|
||||||
|
email: z.string().email("请输入有效的邮箱地址"),
|
||||||
|
password: z.string().min(8, "密码长度8-32位").max(32, "密码长度8-32位"),
|
||||||
|
createdAt: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// 基础表格列配置
|
||||||
|
export const baseColumns = [
|
||||||
|
{ key: "id", label: "ID", sortable: true },
|
||||||
|
{ key: "name", label: "姓名", sortable: true, searchable: true, placeholder: "搜索姓名" },
|
||||||
|
{ key: "email", label: "邮箱", sortable: true, searchable: true, placeholder: "搜索邮箱" },
|
||||||
|
{ key: "createdAt", label: "创建时间", sortable: true },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 基础表单字段配置
|
||||||
|
export const baseFormFields = [
|
||||||
|
{ key: "name", label: "姓名", type: "text", placeholder: "请输入姓名", required: true },
|
||||||
|
{ key: "email", label: "邮箱", type: "email", placeholder: "请输入邮箱", required: true },
|
||||||
|
{ key: "password", label: "密码", type: "password", placeholder: "请输入8-32位密码", required: true },
|
||||||
|
{ key: "createdAt", label: "创建时间", type: "datetime-local", required: false },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 基础操作配置
|
||||||
|
export const baseActions = {
|
||||||
|
add: { label: "添加", icon: "PlusIcon" },
|
||||||
|
edit: { label: "编辑", icon: "PencilIcon" },
|
||||||
|
delete: { label: "删除", icon: "TrashIcon" },
|
||||||
|
batchDelete: { label: "批量删除", icon: "TrashIcon" },
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础分页配置
|
||||||
|
export const basePagination = {
|
||||||
|
pageSizes: [10, 50, 100, 500],
|
||||||
|
defaultPageSize: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建用户配置的工厂函数
|
||||||
|
export function createUserConfig(
|
||||||
|
userType: string,
|
||||||
|
title: string,
|
||||||
|
addLabel: string,
|
||||||
|
namePlaceholder: string,
|
||||||
|
emailPlaceholder: string
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
userType,
|
||||||
|
title,
|
||||||
|
apiPath: "/api/user",
|
||||||
|
columns: baseColumns,
|
||||||
|
formFields: baseFormFields.map(field => ({
|
||||||
|
...field,
|
||||||
|
placeholder: field.key === 'name' ? namePlaceholder :
|
||||||
|
field.key === 'email' ? emailPlaceholder :
|
||||||
|
field.placeholder
|
||||||
|
})),
|
||||||
|
actions: {
|
||||||
|
...baseActions,
|
||||||
|
add: { ...baseActions.add, label: addLabel }
|
||||||
|
},
|
||||||
|
pagination: basePagination,
|
||||||
|
}
|
||||||
|
}
|
@ -1,51 +1,19 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { createUserConfig, baseUserSchema, baseAddUserSchema, baseEditUserSchema } from './base-config'
|
||||||
|
|
||||||
export const guestSchema = z.object({
|
export const guestSchema = baseUserSchema;
|
||||||
id: z.string(),
|
export type Guest = z.infer<typeof guestSchema>;
|
||||||
name: z.string().optional(),
|
|
||||||
email: z.string(),
|
|
||||||
password: z.string().optional(),
|
|
||||||
role: z.string().optional(),
|
|
||||||
createdAt: z.string(),
|
|
||||||
updatedAt: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addGuestSchema = z.object({
|
export const addGuestSchema = baseAddUserSchema;
|
||||||
name: z.string().min(1, "姓名为必填项"),
|
export type AddGuestFormData = z.infer<typeof addGuestSchema>;
|
||||||
email: z.string().email("请输入有效的邮箱地址"),
|
|
||||||
password: z.string().min(8, "密码长度8-32位").max(32, "密码长度8-32位"),
|
|
||||||
createdAt: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const editGuestSchema = z.object({
|
export const editGuestSchema = baseEditUserSchema;
|
||||||
id: z.string(),
|
export type EditGuestFormData = z.infer<typeof editGuestSchema>;
|
||||||
name: z.string().min(1, "姓名为必填项"),
|
|
||||||
email: z.string().email("请输入有效的邮箱地址"),
|
|
||||||
password: z.string().min(8, "密码长度8-32位").max(32, "密码长度8-32位"),
|
|
||||||
createdAt: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const guestConfig = {
|
export const guestConfig = createUserConfig(
|
||||||
userType: "guest",
|
"guest",
|
||||||
title: "客户列表",
|
"客户列表",
|
||||||
apiPath: "/api/user",
|
"添加客户",
|
||||||
columns: [
|
"请输入客户姓名",
|
||||||
{ key: "id", label: "ID", sortable: true },
|
"请输入客户邮箱"
|
||||||
{ key: "name", label: "姓名", sortable: true, searchable: true, placeholder: "搜索姓名" },
|
);
|
||||||
{ key: "email", label: "邮箱", sortable: true, searchable: true, placeholder: "搜索邮箱" },
|
|
||||||
{ key: "createdAt", label: "创建时间", sortable: true },
|
|
||||||
],
|
|
||||||
formFields: [
|
|
||||||
{ key: "name", label: "姓名", type: "text", placeholder: "请输入客户姓名", required: true },
|
|
||||||
{ key: "email", label: "邮箱", type: "email", placeholder: "请输入客户邮箱", required: true },
|
|
||||||
{ key: "password", label: "密码", type: "password", placeholder: "请输入8-32位密码", required: true },
|
|
||||||
{ key: "createdAt", label: "创建时间", type: "datetime-local", required: false },
|
|
||||||
],
|
|
||||||
actions: {
|
|
||||||
add: { label: "添加客户", icon: "PlusIcon" },
|
|
||||||
edit: { label: "编辑", icon: "PencilIcon" },
|
|
||||||
delete: { label: "删除", icon: "TrashIcon" },
|
|
||||||
batchDelete: { label: "批量删除", icon: "TrashIcon" },
|
|
||||||
},
|
|
||||||
pagination: { pageSizes: [10, 50, 100, 500], defaultPageSize: 10 },
|
|
||||||
};
|
|
@ -1,51 +1,19 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { createUserConfig, baseUserSchema, baseAddUserSchema, baseEditUserSchema } from './base-config'
|
||||||
|
|
||||||
export const teacherSchema = z.object({
|
export const teacherSchema = baseUserSchema;
|
||||||
id: z.string(),
|
export type Teacher = z.infer<typeof teacherSchema>;
|
||||||
name: z.string().optional(),
|
|
||||||
email: z.string(),
|
|
||||||
password: z.string().optional(),
|
|
||||||
role: z.string().optional(),
|
|
||||||
createdAt: z.string(),
|
|
||||||
updatedAt: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addTeacherSchema = z.object({
|
export const addTeacherSchema = baseAddUserSchema;
|
||||||
name: z.string().min(1, "姓名为必填项"),
|
export type AddTeacherFormData = z.infer<typeof addTeacherSchema>;
|
||||||
email: z.string().email("请输入有效的邮箱地址"),
|
|
||||||
password: z.string().min(8, "密码长度8-32位").max(32, "密码长度8-32位"),
|
|
||||||
createdAt: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const editTeacherSchema = z.object({
|
export const editTeacherSchema = baseEditUserSchema;
|
||||||
id: z.string(),
|
export type EditTeacherFormData = z.infer<typeof editTeacherSchema>;
|
||||||
name: z.string().min(1, "姓名为必填项"),
|
|
||||||
email: z.string().email("请输入有效的邮箱地址"),
|
|
||||||
password: z.string().min(8, "密码长度8-32位").max(32, "密码长度8-32位"),
|
|
||||||
createdAt: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const teacherConfig = {
|
export const teacherConfig = createUserConfig(
|
||||||
userType: "teacher",
|
"teacher",
|
||||||
title: "教师列表",
|
"教师列表",
|
||||||
apiPath: "/api/user",
|
"添加教师",
|
||||||
columns: [
|
"请输入教师姓名",
|
||||||
{ key: "id", label: "ID", sortable: true },
|
"请输入教师邮箱"
|
||||||
{ key: "name", label: "姓名", sortable: true, searchable: true, placeholder: "搜索姓名" },
|
);
|
||||||
{ key: "email", label: "邮箱", sortable: true, searchable: true, placeholder: "搜索邮箱" },
|
|
||||||
{ key: "createdAt", label: "创建时间", sortable: true },
|
|
||||||
],
|
|
||||||
formFields: [
|
|
||||||
{ key: "name", label: "姓名", type: "text", placeholder: "请输入教师姓名", required: true },
|
|
||||||
{ key: "email", label: "邮箱", type: "email", placeholder: "请输入教师邮箱", required: true },
|
|
||||||
{ key: "password", label: "密码", type: "password", placeholder: "请输入8-32位密码", required: true },
|
|
||||||
{ key: "createdAt", label: "创建时间", type: "datetime-local", required: false },
|
|
||||||
],
|
|
||||||
actions: {
|
|
||||||
add: { label: "添加教师", icon: "PlusIcon" },
|
|
||||||
edit: { label: "编辑", icon: "PencilIcon" },
|
|
||||||
delete: { label: "删除", icon: "TrashIcon" },
|
|
||||||
batchDelete: { label: "批量删除", icon: "TrashIcon" },
|
|
||||||
},
|
|
||||||
pagination: { pageSizes: [10, 50, 100, 500], defaultPageSize: 10 },
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user