"use client" import * as React from "react" import { ColumnDef, ColumnFiltersState, ColumnSizingState, flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, SortingState, useReactTable, VisibilityState, } from "@tanstack/react-table" import { IconChevronDown, IconChevronLeft, IconChevronRight, IconChevronsLeft, IconChevronsRight, IconLayoutColumns, IconPlus, IconTrash, IconSearch, IconLoader2, } from "@tabler/icons-react" import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import type { Target } from "@/types/target.types" interface TargetsDataTableProps { data: Target[] columns: ColumnDef[] onAddNew?: () => void onAddHover?: () => void onBulkDelete?: () => void onSelectionChange?: (selectedRows: Target[]) => void searchPlaceholder?: string searchColumn?: string searchValue?: string onSearch?: (value: string) => void isSearching?: boolean addButtonText?: string // 分页相关属性 pagination?: { pageIndex: number, pageSize: number } onPaginationChange?: (pagination: { pageIndex: number, pageSize: number }) => void totalCount?: number manualPagination?: boolean } /** * 目标数据表格组件 * 专门用于显示和管理目标数据的表格 * 包含搜索、分页、列显示控制等功能 */ export function TargetsDataTable({ data = [], columns, onAddNew, onAddHover, onBulkDelete, onSelectionChange, searchPlaceholder = "搜索目标名称...", searchColumn = "name", searchValue, onSearch, isSearching = false, addButtonText = "添加目标", pagination: externalPagination, onPaginationChange, totalCount, manualPagination = false, }: TargetsDataTableProps) { const [rowSelection, setRowSelection] = React.useState>({}) const [columnVisibility, setColumnVisibility] = React.useState({}) const [columnFilters, setColumnFilters] = React.useState([]) const [sorting, setSorting] = React.useState([]) const [columnSizing, setColumnSizing] = React.useState({}) // 使用外部分页状态或内部状态 const [internalPagination, setInternalPagination] = React.useState<{ pageIndex: number, pageSize: number }>({ pageIndex: 0, pageSize: 10, }) // 本地搜索输入状态(只在回车或点击按钮时触发搜索) const [localSearchValue, setLocalSearchValue] = React.useState(searchValue ?? "") React.useEffect(() => { setLocalSearchValue(searchValue ?? "") }, [searchValue]) const handleSearchSubmit = () => { if (onSearch) { onSearch(localSearchValue) } else { table.getColumn(searchColumn)?.setFilterValue(localSearchValue) } } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleSearchSubmit() } } const pagination = externalPagination || internalPagination // 处理分页状态变化 const handlePaginationChange = React.useCallback((updaterOrValue: any) => { if (onPaginationChange) { // 如果是函数,先计算新值 const newPagination = typeof updaterOrValue === 'function' ? updaterOrValue(pagination) : updaterOrValue onPaginationChange(newPagination) } else { // 使用内部状态 setInternalPagination(updaterOrValue) } }, [onPaginationChange, pagination]) const validData = React.useMemo(() => { const filtered = (data || []).filter(item => item && typeof item.id !== 'undefined' && item.id !== null) return filtered }, [data]) const table = useReactTable({ data: validData, columns, state: { sorting, columnVisibility, rowSelection, columnFilters, pagination, columnSizing, }, enableColumnResizing: true, columnResizeMode: 'onChange', onColumnSizingChange: setColumnSizing, getRowId: (row) => row.id.toString(), enableRowSelection: true, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, onPaginationChange: handlePaginationChange, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: manualPagination ? undefined : getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), // 手动分页配置 manualPagination, pageCount: manualPagination && totalCount ? Math.ceil(totalCount / pagination.pageSize) : undefined, }) React.useEffect(() => { if (onSelectionChange) { const selectedRows = table.getFilteredSelectedRowModel().rows.map(row => row.original) onSelectionChange(selectedRows) } }, [rowSelection, onSelectionChange, table]) return (
{/* 工具栏 */}
{/* 搜索框 */}
setLocalSearchValue(e.target.value)} onKeyDown={handleKeyDown} className="h-8 max-w-sm" />
{/* 右侧操作按钮 */}
{/* 列显示控制 */} {table .getAllColumns() .filter( (column) => typeof column.accessorFn !== "undefined" && column.getCanHide() ) .map((column) => { return ( column.toggleVisibility(!!value)} > {column.id === "id" && "ID"} {column.id === "name" && "目标名称"} {column.id === "type" && "类型"} {column.id === "organizations" && "所属组织"} {column.id === "domainCount" && "域名数"} {column.id === "endpointCount" && "URL 数"} {!["id", "name", "type", "organizations", "domainCount", "endpointCount"].includes(column.id) && column.id} ) })} {/* 批量删除按钮 */} {onBulkDelete && ( )} {/* 添加新目标按钮 */} {onAddNew && ( )}
{/* 表格容器 */}
{/* 表头 */} {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )} {header.column.getCanResize() && (
header.column.resetSize()} className="absolute -right-2.5 top-0 h-full w-8 cursor-col-resize select-none touch-none bg-transparent flex justify-center" >
)} ) })} ))} {/* 表体 */} {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender( cell.column.columnDef.cell, cell.getContext() )} ))} )) ) : ( 暂无数据 )}
{/* 分页控制 */}
{/* 选中行信息 */}
已选择 {table.getFilteredSelectedRowModel().rows.length} 个,共{" "} {manualPagination && totalCount ? totalCount : table.getFilteredRowModel().rows.length} 个
{/* 分页控制器 */}
{/* 每页显示数量选择 */}
{/* 页码信息 */}
第 {table.getState().pagination.pageIndex + 1} 页,共{" "} {manualPagination && totalCount ? Math.ceil(totalCount / pagination.pageSize) : table.getPageCount()} 页
{/* 分页按钮 */}
) }