feat: 增加DEFAULT_USER_PASSWORD,作为创建用户时的默认密码,添加p-limit库,添加DB_PARALLEL_LIMIT = 32环境变量作为“数据库批次操作默认并发数” 限制只有超级管理员才能创建超级管理员用户 删除用户时可以级联删除SelectionLog 添加zustand全局状态管理 一对多的院系管理功能 ,支持增删改查院系管理员信息、用户可以在header中切换管理的院系

This commit is contained in:
2025-11-18 20:07:42 +08:00
parent 7f3190a223
commit 2a80a44972
31 changed files with 1651 additions and 96 deletions

View File

@@ -0,0 +1,247 @@
'use client'
import { ColumnDef } from '@tanstack/react-table'
import { Button } from '@/components/ui/button'
import { Edit, Trash2, MoreHorizontal } from 'lucide-react'
import { formatDate } from '@/lib/format'
import { Checkbox } from '@/components/ui/checkbox'
import { DataTableColumnHeader } from '@/components/data-table/column-header'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import type { DeptAdmin } from '@/server/routers/dept-admin'
// 操作回调类型
export type DeptAdminActions = {
onEdit: (id: number) => void
onDelete: (id: number) => void
}
// 列定义选项类型
export type DeptAdminColumnsOptions = {
depts?: Array<{ code: string; name: string; fullName: string }>
}
// 创建院系管理员表格列定义
export const createDeptAdminColumns = (
actions: DeptAdminActions,
options: DeptAdminColumnsOptions = {}
): ColumnDef<DeptAdmin>[] => [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) =>
table.toggleAllPageRowsSelected(!!value)
}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
size: 32,
enableSorting: false,
enableHiding: false,
},
{
id: 'id',
accessorKey: 'id',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="ID" />
),
cell: ({ row }) => <div className="font-medium">{row.original.id}</div>,
meta: {
label: 'ID',
},
},
{
id: 'uid',
accessorKey: 'uid',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="用户ID" />
),
cell: ({ row }) => <div className="font-medium">{row.original.uid}</div>,
enableColumnFilter: true,
meta: {
label: '用户ID',
filter: {
placeholder: '请输入用户ID',
variant: 'text',
}
},
},
{
id: 'userName',
accessorKey: 'user.name',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="姓名" />
),
cell: ({ row }) => <div>{row.original.user?.name || '-'}</div>,
enableColumnFilter: true,
meta: {
label: '姓名',
filter: {
placeholder: '请输入姓名',
variant: 'text',
}
},
},
{
id: 'deptCode',
accessorKey: 'deptCode',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="院系" />
),
cell: ({ row }) => (
<div>
<div className="font-medium">{row.original.dept?.name || '-'}</div>
<div className="text-xs text-muted-foreground">{row.original.deptCode}</div>
</div>
),
enableColumnFilter: true,
meta: {
label: '院系',
filter: {
variant: 'multiSelect',
options: options.depts?.map(dept => ({
id: dept.code,
name: dept.fullName,
})) || [],
}
},
},
{
id: 'adminEmail',
accessorKey: 'adminEmail',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="邮箱" />
),
cell: ({ row }) => <div>{row.original.adminEmail || '-'}</div>,
enableColumnFilter: true,
meta: {
label: '邮箱',
filter: {
placeholder: '请输入邮箱',
variant: 'text',
}
},
},
{
id: 'adminLinePhone',
accessorKey: 'adminLinePhone',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="座机" />
),
cell: ({ row }) => <div>{row.original.adminLinePhone || '-'}</div>,
enableColumnFilter: true,
meta: {
label: '座机',
filter: {
placeholder: '请输入座机',
variant: 'text',
}
},
},
{
id: 'adminMobilePhone',
accessorKey: 'adminMobilePhone',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="手机" />
),
cell: ({ row }) => <div>{row.original.adminMobilePhone || '-'}</div>,
enableColumnFilter: true,
meta: {
label: '手机',
filter: {
placeholder: '请输入手机',
variant: 'text',
}
},
},
{
id: 'note',
accessorKey: 'note',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="备注" />
),
cell: ({ row }) => (
<div className="max-w-[200px] truncate" title={row.original.note || ''}>
{row.original.note || '-'}
</div>
),
enableSorting: false,
meta: {
label: '备注',
},
},
{
id: 'createdAt',
accessorKey: 'createdAt',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="创建时间" />
),
cell: ({ row }) => {
return <div>{formatDate(row.original.createdAt) || '-'}</div>
},
meta: {
label: '创建时间',
}
},
{
id: 'updatedAt',
accessorKey: 'updatedAt',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="更新时间" />
),
cell: ({ row }) => {
return <div>{formatDate(row.original.updatedAt) || '-'}</div>
},
meta: {
label: '更新时间',
}
},
{
id: 'actions',
cell: ({ row }) => {
const deptAdmin = row.original
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-6 w-6 md:w-9">
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only"></span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => actions.onEdit(deptAdmin.id)}>
<Edit className="h-4 w-4" />
</DropdownMenuItem>
<DropdownMenuItem
variant="destructive"
onClick={() => actions.onDelete(deptAdmin.id)}
>
<Trash2 className="h-4 w-4" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
},
size: 32,
enableSorting: false,
enableHiding: false,
},
]