import React, { useState, useMemo } from 'react'; import { ChevronUp, ChevronDown, Filter } from 'lucide-react'; import { SearchInput } from './InteractiveInput'; import { InteractiveButton } from './InteractiveButton'; export interface Column { key: keyof T | string; title: string; width?: string; sortable?: boolean; filterable?: boolean; render?: (value: any, record: T, index: number) => React.ReactNode; align?: 'left' | 'center' | 'right'; } export interface TableAction { key: string; label: string; icon?: React.ReactNode; onClick: (record: T) => void; variant?: 'primary' | 'secondary' | 'danger' | 'success' | 'ghost' | 'outline'; disabled?: (record: T) => boolean; } interface DataTableProps { data: T[]; columns: Column[]; actions?: TableAction[]; loading?: boolean; searchable?: boolean; searchPlaceholder?: string; filterable?: boolean; sortable?: boolean; pagination?: boolean; pageSize?: number; emptyText?: string; className?: string; rowKey?: keyof T | ((record: T) => string); onRowClick?: (record: T) => void; selectedRows?: T[]; onSelectionChange?: (selectedRows: T[]) => void; bulkActions?: TableAction[]; } /** * 增强的数据表格组件 * 支持搜索、排序、筛选、分页等功能 */ export function DataTable>({ data, columns, actions = [], loading = false, searchable = true, searchPlaceholder = '搜索...', filterable = false, sortable = true, pagination = true, pageSize = 10, emptyText = '暂无数据', className = '', rowKey = 'id', onRowClick, selectedRows = [], onSelectionChange, bulkActions = [], }: DataTableProps) { const [searchQuery, setSearchQuery] = useState(''); const [sortConfig, setSortConfig] = useState<{ key: string; direction: 'asc' | 'desc'; } | null>(null); const [currentPage, setCurrentPage] = useState(1); // 获取行的唯一键 const getRowKey = (record: T, index: number): string => { if (typeof rowKey === 'function') { return rowKey(record); } return record[rowKey] || index.toString(); }; // 搜索过滤 const searchedData = useMemo(() => { if (!searchQuery.trim()) return data; return data.filter(record => { return columns.some(column => { const value = record[column.key as keyof T]; return String(value).toLowerCase().includes(searchQuery.toLowerCase()); }); }); }, [data, searchQuery, columns]); // 排序 const sortedData = useMemo(() => { if (!sortConfig) return searchedData; return [...searchedData].sort((a, b) => { const aValue = a[sortConfig.key]; const bValue = b[sortConfig.key]; if (aValue < bValue) { return sortConfig.direction === 'asc' ? -1 : 1; } if (aValue > bValue) { return sortConfig.direction === 'asc' ? 1 : -1; } return 0; }); }, [searchedData, sortConfig]); // 分页 const paginatedData = useMemo(() => { if (!pagination) return sortedData; const startIndex = (currentPage - 1) * pageSize; return sortedData.slice(startIndex, startIndex + pageSize); }, [sortedData, currentPage, pageSize, pagination]); const totalPages = Math.ceil(sortedData.length / pageSize); // 排序处理 const handleSort = (columnKey: string) => { if (!sortable) return; setSortConfig(current => { if (current?.key === columnKey) { if (current.direction === 'asc') { return { key: columnKey, direction: 'desc' }; } else { return null; // 取消排序 } } return { key: columnKey, direction: 'asc' }; }); }; // 选择处理 const handleRowSelection = (record: T, selected: boolean) => { if (!onSelectionChange) return; const recordKey = getRowKey(record, 0); if (selected) { onSelectionChange([...selectedRows, record]); } else { onSelectionChange(selectedRows.filter(row => getRowKey(row, 0) !== recordKey)); } }; const handleSelectAll = (selected: boolean) => { if (!onSelectionChange) return; if (selected) { onSelectionChange(paginatedData); } else { onSelectionChange([]); } }; const isRowSelected = (record: T): boolean => { const recordKey = getRowKey(record, 0); return selectedRows.some(row => getRowKey(row, 0) === recordKey); }; const allSelected = paginatedData.length > 0 && paginatedData.every(record => isRowSelected(record)); const someSelected = selectedRows.length > 0 && !allSelected; return (
{/* 表格头部工具栏 */} {(searchable || filterable || bulkActions.length > 0) && (
{/* 搜索 */} {searchable && (
)} {/* 筛选 */} {filterable && ( } > 筛选 )}
{/* 批量操作 */} {bulkActions.length > 0 && selectedRows.length > 0 && (
已选择 {selectedRows.length} 项 {bulkActions.map(action => ( action.onClick(selectedRows)} icon={action.icon} > {action.label} ))}
)}
)} {/* 表格内容 */}
{/* 表头 */} {/* 选择列 */} {onSelectionChange && ( )} {/* 数据列 */} {columns.map(column => ( ))} {/* 操作列 */} {actions.length > 0 && ( )} {/* 表体 */} {loading ? ( // 加载状态 Array.from({ length: pageSize }).map((_, index) => ( {onSelectionChange && } {columns.map(column => ( ))} {actions.length > 0 && ( )} )) ) : paginatedData.length === 0 ? ( // 空状态 ) : ( // 数据行 paginatedData.map((record, index) => ( onRowClick?.(record)} > {/* 选择列 */} {onSelectionChange && ( )} {/* 数据列 */} {columns.map(column => ( ))} {/* 操作列 */} {actions.length > 0 && ( )} )) )}
{ if (input) input.indeterminate = someSelected; }} onChange={(e) => handleSelectAll(e.target.checked)} className="rounded border-gray-300 text-primary-600 focus:ring-primary-500" /> column.sortable !== false && handleSort(String(column.key))} >
{column.title} {column.sortable !== false && sortable && (
)}
操作
0 ? 1 : 0)} className="px-4 py-12 text-center text-gray-500" > {emptyText}
e.stopPropagation()}> handleRowSelection(record, e.target.checked)} className="rounded border-gray-300 text-primary-600 focus:ring-primary-500" /> {column.render ? column.render(record[column.key as keyof T], record, index) : String(record[column.key as keyof T] || '') } e.stopPropagation()}>
{actions.map(action => ( ))}
{/* 分页 */} {pagination && totalPages > 1 && (
显示 {(currentPage - 1) * pageSize + 1} 到{' '} {Math.min(currentPage * pageSize, sortedData.length)} 项,共 {sortedData.length} 项
setCurrentPage(p => Math.max(1, p - 1))} disabled={currentPage === 1} > 上一页 第 {currentPage} 页,共 {totalPages} 页 setCurrentPage(p => Math.min(totalPages, p + 1))} disabled={currentPage === totalPages} > 下一页
)}
); }