目录前言一、 设计用户数据模型1. 更新 Prisma Schema2. 执行数据库迁移3. 添加种子数据二、 创建用户列表 API三、 安装所需组件四、 创建用户列表组件五、 创建用户管理页面六、 配置导航菜单七、 运行效果总结前言在上一章中我们完成了部门管理的完整 CRUD 功能。本章我们将进入用户管理模块这是企业管理系统的核心部分。用户管理涉及员工信息的维护、部门归属的管理、以及复杂的查询筛选功能。与部门管理不同用户管理需要处理更多的字段类型文本、电话、日期、枚举等和更复杂的查询条件。本章目标设计用户数据模型关联部门实现用户列表查询支持搜索、筛选、排序搭建用户管理页面布局配置前端导航菜单一、 设计用户数据模型1. 更新 Prisma Schema打开prisma/schema.prisma添加 User 模型如果之前没有完整定义// prisma/schema.prisma model User { id String id default(uuid()) name String // 姓名 employeeId String? unique // 工号可选唯一 phone String? // 手机号 email String? unique // 邮箱可选唯一 avatar String? // 头像URL // 状态1-在职, 2-离职, 3-休假 status Int default(1) // 入职时间 hireDate DateTime? map(hire_date) // 部门关联 departmentId String? map(department_id) department Department? relation(fields: [departmentId], references: [id]) // 角色关联多对多 roles Role[] // 账号信息用于登录 password String? createdAt DateTime default(now()) map(created_at) updatedAt DateTime updatedAt map(updated_at) map(users) index([departmentId]) index([status]) } // 确保 Department 模型有 users 关联 model Department { id String id default(uuid()) name String code String? unique description String? sortOrder Int default(0) isActive Boolean default(true) parentId String? parent Department? relation(DepartmentChildren, fields: [parentId], references: [id], onDelete: SetNull) children Department[] relation(DepartmentChildren) users User[] // 关联用户 createdAt DateTime default(now()) updatedAt DateTime updatedAt map(departments) }关键点解析employeeId和email标记为unique确保唯一性status使用整数表示1-在职, 2-离职, 3-休假departmentId建立外键关联并添加索引优化查询index([status])为状态字段添加索引方便按状态筛选2. 执行数据库迁移# 生成迁移文件npx prisma migrate dev--nameadd_user_model# 重新生成 Prisma Clientnpx prisma generate3. 添加种子数据在prisma/seed.ts中添加用户数据// 在 seed.ts 中添加用户数据asyncfunctionmain(){// ... 之前的部门创建代码 ...// 5. 创建示例用户constusers[{name:张三,employeeId:EMP001,phone:13800138001,email:zhangsanexample.com,status:1,departmentId:headquarters.id,},{name:李四,employeeId:EMP002,phone:13800138002,email:lisiexample.com,status:1,departmentId:salesDept.id,},{name:王五,employeeId:EMP003,phone:13800138003,email:wangwuexample.com,status:2,// 离职departmentId:techDept.id,},]for(constuserofusers){awaitprisma.user.upsert({where:{email:user.email},update:{},create:{...user,password:hashedPassword,hireDate:newDate(2023-01-01),},})}console.log(用户数据创建完成)}执行种子脚本npx prisma db seed二、 创建用户列表 API创建src/app/api/users/route.ts支持查询、搜索、筛选、排序// src/app/api/users/route.tsimport{NextResponse}fromnext/serverimportprismafrom/lib/prismaimport{getServerSession}fromnext-auth/nextimport{authOptions}from../auth/[...nextauth]/route// GET /api/users - 获取用户列表支持搜索、筛选、排序exportasyncfunctionGET(request:Request){try{constsessionawaitgetServerSession(authOptions)if(!session){returnNextResponse.json({error:未授权},{status:401})}// 解析查询参数const{searchParams}newURL(request.url)constkeywordsearchParams.get(keyword)||// 搜索关键词constdepartmentIdsearchParams.get(departmentId)||// 部门筛选conststatussearchParams.get(status)||// 状态筛选constsortFieldsearchParams.get(sortField)||createdAt// 排序字段constsortOrdersearchParams.get(sortOrder)||desc// 排序方向// 构建查询条件constwhere:any{}// 关键词搜索姓名、工号、邮箱、手机号if(keyword){where.OR[{name:{contains:keyword,mode:insensitive}},{employeeId:{contains:keyword,mode:insensitive}},{email:{contains:keyword,mode:insensitive}},{phone:{contains:keyword}},]}// 部门筛选if(departmentId){where.departmentIddepartmentId}// 状态筛选if(status){where.statusparseInt(status)}// 构建排序条件constorderBy:any{}orderBy[sortField]sortOrder// 查询用户列表constusersawaitprisma.user.findMany({where,orderBy,include:{department:{select:{id:true,name:true,},},},})// 格式化返回数据constformattedUsersusers.map((user)({id:user.id,name:user.name,employeeId:user.employeeId,phone:user.phone,email:user.email,avatar:user.avatar,status:user.status,hireDate:user.hireDate?.toISOString()||null,departmentId:user.departmentId,departmentName:user.department?.name||-,createdAt:user.createdAt.toISOString(),}))returnNextResponse.json(formattedUsers)}catch(error){console.error(获取用户列表失败:,error)returnNextResponse.json({error:获取用户列表失败},{status:500})}}关键点解析使用searchParams获取 URL 查询参数where.OR实现多字段模糊搜索mode: insensitive实现不区分大小写的搜索include: { department: {...} }关联查询部门信息返回格式化后的数据包含departmentName便于展示三、 安装所需组件# 安装 shadcn 组件npx shadcnlatestaddtable npx shadcnlatestaddbutton npx shadcnlatestaddinput npx shadcnlatestaddbadge npx shadcnlatestaddselectn# 安装图标库npminstalllucide-react四、 创建用户列表组件创建src/components/user/UserList.tsx// src/components/user/UserList.tsx use client import { useState, useEffect } from react import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from /components/ui/table import { Button } from /components/ui/button import { Input } from /components/ui/input import { Badge } from /components/ui/badge import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from /components/ui/select import { Search, Plus, MoreHorizontal, Pencil, Trash2 } from lucide-react // 用户数据类型 interface User { id: string name: string employeeId: string | null phone: string | null email: string | null avatar: string | null status: number hireDate: string | null departmentId: string | null departmentName: string createdAt: string } // 状态映射 const statusMap: Recordnumber, { label: string; variant: default | secondary | destructive | outline } { 1: { label: 在职, variant: default }, 2: { label: 离职, variant: secondary }, 3: { label: 休假, variant: outline }, } export function UserList() { const [users, setUsers] useStateUser[]([]) const [loading, setLoading] useState(true) // 查询条件 const [keyword, setKeyword] useState() const [status, setStatus] useStatestring(all) const [departmentId, setDepartmentId] useStatestring(all) // 获取用户列表 const fetchUsers async () { setLoading(true) try { // 构建查询参数 const params new URLSearchParams() if (keyword) params.append(keyword, keyword) if (status status ! all) params.append(status, status) if (departmentId departmentId ! all) params.append(departmentId, departmentId) const response await fetch(/api/users?${params.toString()}) if (!response.ok) throw new Error(获取用户列表失败) const data await response.json() setUsers(data) } catch (error) { console.error(加载用户数据失败:, error) } finally { setLoading(false) } } // 初始加载和条件变化时重新获取 useEffect(() { fetchUsers() }, [status, departmentId]) // 下拉选择变化时自动查询 // 搜索按钮点击 const handleSearch () { fetchUsers() } // 重置查询 const handleReset () { setKeyword() setStatus(all) setDepartmentId(all) fetchUsers() } if (loading) { return div classNamep-6加载中.../div } return ( div classNamebg-white rounded-lg shadow-sm border border-gray-200 {/* 标题栏 */} div classNameflex items-center justify-between p-6 border-b border-gray-200 div h1 classNametext-xl font-bold text-gray-900用户管理/h1 p classNametext-sm text-gray-500 mt-1 管理企业员工信息支持搜索、筛选、排序 /p /div Button classNamebg-blue-600 hover:bg-blue-700 Plus classNamew-4 h-4 mr-2 / 新增用户 /Button /div {/* 查询条件 */} div classNamep-6 border-b border-gray-200 bg-gray-50 div classNameflex flex-wrap gap-4 {/* 关键词搜索 */} div classNameflex-1 min-w-[200px] div classNamerelative Search classNameabsolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4 / Input placeholder搜索姓名、工号、邮箱、手机号 value{keyword} onChange{(e) setKeyword(e.target.value)} classNamepl-10 onKeyDown{(e) e.key Enter handleSearch()} / /div /div {/* 状态筛选 */} Select value{status} onValueChange{setStatus} SelectTrigger classNamew-[150px] SelectValue placeholder全部状态 / /SelectTrigger SelectContent SelectItem valueall全部状态/SelectItem SelectItem value1在职/SelectItem SelectItem value2离职/SelectItem SelectItem value3休假/SelectItem /SelectContent /Select {/* 部门筛选简化版实际应从部门API获取 */} Select value{departmentId} onValueChange{setDepartmentId} SelectTrigger classNamew-[180px] SelectValue placeholder全部部门 / /SelectTrigger SelectContent SelectItem valueall全部部门/SelectItem {/* 部门选项应从API动态获取 */} /SelectContent /Select {/* 操作按钮 */} div classNameflex gap-2 Button variantoutline onClick{handleSearch} 查询 /Button Button variantghost onClick{handleReset} 重置 /Button /div /div /div {/* 数据表格 */} div classNamep-0 Table TableHeader TableRow classNamebg-gray-50 TableHead姓名/TableHead TableHead工号/TableHead TableHead部门/TableHead TableHead手机号/TableHead TableHead邮箱/TableHead TableHead状态/TableHead TableHead入职时间/TableHead TableHead classNametext-right操作/TableHead /TableRow /TableHeader TableBody {users.length 0 ? ( TableRow TableCell colSpan{8} classNametext-center py-10 text-gray-500 暂无用户数据 /TableCell /TableRow ) : ( users.map((user) { const statusInfo statusMap[user.status] || { label: 未知, variant: outline } return ( TableRow key{user.id} classNamehover:bg-gray-50 TableCell classNamefont-medium{user.name}/TableCell TableCell classNametext-gray-500{user.employeeId || -}/TableCell TableCell classNametext-gray-500{user.departmentName}/TableCell TableCell classNametext-gray-500{user.phone || -}/TableCell TableCell classNametext-gray-500{user.email || -}/TableCell TableCell Badge variant{statusInfo.variant}{statusInfo.label}/Badge /TableCell TableCell classNametext-gray-500 {user.hireDate ? new Date(user.hireDate).toLocaleDateString(zh-CN) : -} /TableCell TableCell classNametext-right div classNameflex justify-end gap-2 Button variantghost sizeicon Pencil classNamew-4 h-4 / /Button Button variantghost sizeicon classNametext-red-600 Trash2 classNamew-4 h-4 / /Button /div /TableCell /TableRow ) }) )} /TableBody /Table /div /div ) }关键点解析使用useState管理查询条件状态URLSearchParams构建查询参数Select组件实现下拉筛选Badge组件展示状态标签支持回车键触发搜索五、 创建用户管理页面创建src/app/(admin)/users/page.tsx// src/app/(admin)/users/page.tsx use client import { UserList } from /components/user/UserList export default function UsersPage() { return ( div classNamep-6 UserList / /div ) }六、 配置导航菜单更新src/app/(admin)/layout.tsx添加用户管理菜单// src/app/(admin)/layout.tsx // ... 其他导入 ... const adminMenus [ { name: 首页, path: /admin/dashboard, icon: Home }, { name: 部门管理, path: /departments, icon: Building }, { name: 用户管理, path: /users, icon: Users }, // 新增 { name: 角色权限, path: /roles, icon: Shield }, { name: 系统设置, path: /settings, icon: Settings }, ] // ... 其余代码保持不变 ...七、 运行效果启动项目npmrun dev登录后进入管理后台点击左侧用户管理菜单可以看到用户列表表格在搜索框输入关键词点击查询或按回车使用状态下拉筛选在职/离职/休假员工表格会实时显示筛选结果总结本章我们完成了用户管理的基础部分数据模型设计User 模型包含员工基本信息和部门关联数据库迁移执行 Prisma migrate 创建表结构用户列表 API支持搜索、筛选、排序的查询接口用户列表组件带查询条件的表格展示导航菜单配置添加用户管理入口下一章我们将实现用户管理的完整 CRUD用户新增含部门下拉选择用户编辑数据回显用户删除带确认头像上传功能更复杂的表单验证敬请期待