refactor(user-management): 重构用户管理功能

- 合并 adminActions、teacherActions 和 guestActions 到统一的 userActions
- 创建通用的用户配置和页面组件
- 优化用户表单验证逻辑
- 调整布局和页面结构
This commit is contained in:
liguang 2025-06-21 12:22:56 +08:00
parent 03ccd285be
commit 7cdf5fe6b8
20 changed files with 240 additions and 358 deletions

View File

@ -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')
}

View File

@ -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')
}

View File

@ -8,11 +8,6 @@ export async function createProblem(data: Omit<Problem, 'id'|'createdAt'|'update
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) {
await prisma.problem.delete({ where: { id } })
revalidatePath('/usermanagement/problem')

View File

@ -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')
}

View 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}`)
}

View 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>;
}

View File

@ -1,5 +1,5 @@
import ProtectedLayout from "../_components/ProtectedLayout";
import GenericLayout from "../_components/GenericLayout";
export default function AdminLayout({ children }: { children: React.ReactNode }) {
return <ProtectedLayout allowedRoles={["ADMIN"]}>{children}</ProtectedLayout>;
return <GenericLayout allowedRoles={["ADMIN"]}>{children}</GenericLayout>;
}

View File

@ -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 prisma from '@/lib/prisma'
import type { User } from '@/generated/client'
export default async function AdminPage() {
const data: User[] = await prisma.user.findMany({ where: { role: 'ADMIN' } })
return <UserTable config={adminConfig} data={data} />
export default function AdminPage() {
return <GenericPage userType="admin" config={adminConfig} />
}

View File

@ -1,5 +1,5 @@
import ProtectedLayout from "../_components/ProtectedLayout";
import GenericLayout from "../_components/GenericLayout";
export default function GuestLayout({ children }: { children: React.ReactNode }) {
return <ProtectedLayout allowedRoles={["ADMIN", "TEACHER"]}>{children}</ProtectedLayout>;
return <GenericLayout allowedRoles={["ADMIN", "TEACHER"]}>{children}</GenericLayout>;
}

View File

@ -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 prisma from '@/lib/prisma'
import type { User } from '@/generated/client'
export default async function GuestPage() {
const data: User[] = await prisma.user.findMany({ where: { role: 'GUEST' } })
return <UserTable config={guestConfig} data={data} />
export default function GuestPage() {
return <GenericPage userType="guest" config={guestConfig} />
}

View File

@ -1,5 +1,5 @@
import ProtectedLayout from "../_components/ProtectedLayout";
import GenericLayout from "../_components/GenericLayout";
export default function ProblemLayout({ children }: { children: React.ReactNode }) {
return <ProtectedLayout allowedRoles={["ADMIN", "TEACHER"]}>{children}</ProtectedLayout>;
return <GenericLayout allowedRoles={["ADMIN", "TEACHER"]}>{children}</GenericLayout>;
}

View File

@ -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 prisma from '@/lib/prisma'
import type { Problem } from '@/generated/client'
export default async function ProblemPage() {
const data: Problem[] = await prisma.problem.findMany({})
return <UserTable config={problemConfig} data={data} />
export default function ProblemPage() {
return <GenericPage userType="problem" config={problemConfig} />
}

View File

@ -1,5 +1,5 @@
import ProtectedLayout from "../_components/ProtectedLayout";
import GenericLayout from "../_components/GenericLayout";
export default function TeacherLayout({ children }: { children: React.ReactNode }) {
return <ProtectedLayout allowedRoles={["ADMIN"]}>{children}</ProtectedLayout>;
return <GenericLayout allowedRoles={["ADMIN"]}>{children}</GenericLayout>;
}

View File

@ -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 prisma from '@/lib/prisma'
import type { User } from '@/generated/client'
export default async function TeacherPage() {
const data: User[] = await prisma.user.findMany({ where: { role: 'TEACHER' } })
return <UserTable config={teacherConfig} data={data} />
export default function TeacherPage() {
return <GenericPage userType="teacher" config={teacherConfig} />
}

View 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} />
}
}

View File

@ -69,9 +69,7 @@ import {
Tabs,
} from "@/components/ui/tabs"
import { createAdmin, updateAdmin, deleteAdmin } from '@/app/(app)/usermanagement/_actions/adminActions'
import { createTeacher, updateTeacher } from '@/app/(app)/usermanagement/_actions/teacherActions'
import { createGuest, updateGuest } from '@/app/(app)/usermanagement/_actions/guestActions'
import { createUser, updateUser, deleteUser } from '@/app/(app)/usermanagement/_actions/userActions'
import { createProblem, deleteProblem } from '@/app/(app)/usermanagement/_actions/problemActions'
import type { User, Problem } from '@/generated/client'
import { Difficulty, Role } from '@/generated/client'
@ -128,7 +126,7 @@ type AddUserForm = Omit<UserForm, 'id'>
const addUserSchema = z.object({
name: z.string(),
email: z.string().email(),
password: z.string(),
password: z.string().min(1, "密码不能为空").min(8, "密码长度至少8位"),
createdAt: z.string(),
image: z.string().nullable(),
emailVerified: z.date().nullable(),
@ -312,6 +310,13 @@ export function UserTable(props: UserTableProps) {
async function onSubmit(data: AddUserForm) {
try {
setIsLoading(true)
// 验证必填字段
if (!data.password || data.password.trim() === '') {
toast.error('密码不能为空', { duration: 1500 })
return
}
const submitData = {
...data,
image: data.image ?? null,
@ -321,9 +326,9 @@ export function UserTable(props: UserTableProps) {
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)
if (props.config.userType === 'admin') await createUser('admin', submitData)
else if (props.config.userType === 'teacher') await createUser('teacher', submitData)
else if (props.config.userType === 'guest') await createUser('guest', submitData)
onOpenChange(false)
toast.success('添加成功', { duration: 1500 })
router.refresh()
@ -526,9 +531,9 @@ export function UserTable(props: UserTableProps) {
role: data.role ?? Role.GUEST,
}
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)
if (props.config.userType === 'admin') await updateUser('admin', id, submitData)
else if (props.config.userType === 'teacher') await updateUser('teacher', id, submitData)
else if (props.config.userType === 'guest') await updateUser('guest', id, submitData)
onOpenChange(false)
toast.success('修改成功', { duration: 1500 })
} catch {
@ -898,7 +903,7 @@ export function UserTable(props: UserTableProps) {
if (isProblem) {
await deleteProblem((row.original as Problem).id)
} 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 })
@ -930,7 +935,7 @@ export function UserTable(props: UserTableProps) {
if (isProblem) {
await deleteProblem((pendingDeleteItem as Problem).id)
} else {
await deleteAdmin((pendingDeleteItem as User).id)
await deleteUser(props.config.userType as 'admin' | 'teacher' | 'guest', (pendingDeleteItem as User).id)
}
toast.success('删除成功', { duration: 1500 })
router.refresh()

View File

@ -1,126 +1,23 @@
import { z } from "zod"
import { createUserConfig, baseUserSchema, baseAddUserSchema, baseEditUserSchema } from './base-config'
// 管理员数据校验 schema
export const adminSchema = 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(),
})
export const adminSchema = baseUserSchema
export type Admin = z.infer<typeof adminSchema>
// 添加管理员表单校验 schema
export const addAdminSchema = 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(),
})
export const addAdminSchema = baseAddUserSchema
export type AddAdminFormData = z.infer<typeof addAdminSchema>
// 编辑管理员表单校验 schema
export const editAdminSchema = 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 type AddAdminFormData = z.infer<typeof addAdminSchema>
export const editAdminSchema = baseEditUserSchema
export type EditAdminFormData = z.infer<typeof editAdminSchema>
// 管理员配置
export const adminConfig = {
userType: "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,
},
}
export const adminConfig = createUserConfig(
"admin",
"管理员列表",
"添加管理员",
"请输入管理员姓名",
"请输入管理员邮箱"
)

View 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,
}
}

View File

@ -1,51 +1,19 @@
import { z } from "zod";
import { createUserConfig, baseUserSchema, baseAddUserSchema, baseEditUserSchema } from './base-config'
export const guestSchema = 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(),
});
export const guestSchema = baseUserSchema;
export type Guest = z.infer<typeof guestSchema>;
export const addGuestSchema = 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(),
});
export const addGuestSchema = baseAddUserSchema;
export type AddGuestFormData = z.infer<typeof addGuestSchema>;
export const editGuestSchema = 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 editGuestSchema = baseEditUserSchema;
export type EditGuestFormData = z.infer<typeof editGuestSchema>;
export const guestConfig = {
userType: "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 },
};
export const guestConfig = createUserConfig(
"guest",
"客户列表",
"添加客户",
"请输入客户姓名",
"请输入客户邮箱"
);

View File

@ -1,51 +1,19 @@
import { z } from "zod";
import { createUserConfig, baseUserSchema, baseAddUserSchema, baseEditUserSchema } from './base-config'
export const teacherSchema = 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(),
});
export const teacherSchema = baseUserSchema;
export type Teacher = z.infer<typeof teacherSchema>;
export const addTeacherSchema = 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(),
});
export const addTeacherSchema = baseAddUserSchema;
export type AddTeacherFormData = z.infer<typeof addTeacherSchema>;
export const editTeacherSchema = 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 editTeacherSchema = baseEditUserSchema;
export type EditTeacherFormData = z.infer<typeof editTeacherSchema>;
export const teacherConfig = {
userType: "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 },
};
export const teacherConfig = createUserConfig(
"teacher",
"教师列表",
"添加教师",
"请输入教师姓名",
"请输入教师邮箱"
);