# 通用表格组件 GenericDataTable 使用文档 ## 概述 `GenericDataTable` 是一个基于 Ant Design Table 的通用数据表格组件,支持泛型数据类型、列配置、列宽调整、行交互等功能。 ## 📁 文件位置 - 组件:`src/components/GenericDataTable.tsx` - 使用示例: - `src/pages/patient/OutputList.tsx` - 传输队列表格 - `src/pages/patient/worklist.tsx` - 工作列表表格(待改造) ## 🎯 核心特性 ### 1. 泛型支持 - 支持任意数据类型 - TypeScript 类型安全 - 自动推断列的数据类型 ### 2. 列配置系统 - 显示/隐藏列 - 列宽调整 - 列排序 - 与 `columnConfigService` 集成 ### 3. 列宽调整 - 支持鼠标拖拽调整列宽 - 最小宽度 50px - 使用 react-resizable ### 4. 行交互 - 单击选择 - 双击操作 - 触摸屏双击支持 - 行高亮显示 ### 5. 其他特性 - 加载状态 - 自定义渲染 - 响应式布局 ## 💡 使用示例 ### 基础用法 ```typescript import { GenericDataTable, ColumnDefinition } from '@/components/GenericDataTable'; interface MyData { id: string; name: string; status: string; } const columns: ColumnDefinition[] = [ { title: '名称', dataIndex: 'name', width: 150, }, { title: '状态', dataIndex: 'status', width: 100, }, ]; function MyTable() { const [selectedIds, setSelectedIds] = useState([]); const data: MyData[] = [ { id: '1', name: '张三', status: '正常' }, { id: '2', name: '李四', status: '异常' }, ]; return ( dataSource={data} rowKey="id" columnDefinitions={columns} selectedIds={selectedIds} onRowClick={(record) => setSelectedIds([record.id])} /> ); } ``` ### 带国际化的列定义 ```typescript import { FormattedMessage } from 'react-intl'; const columns: ColumnDefinition[] = [ { title: ( ), dataIndex: 'patient_name', width: 150, }, { title: ( ), dataIndex: 'status', width: 120, }, ]; ``` ### 自定义渲染 ```typescript const columns: ColumnDefinition[] = [ { title: '重试次数', dataIndex: 'retry_count', width: 100, render: (value) => value ?? 0, // 显示 0 而不是 undefined }, { title: '状态', dataIndex: 'status', width: 100, render: (value) => { const statusMap = { 'ARRIVED': '已到达', 'SENDING': '发送中', 'FAILED': '失败', 'SUCCESS': '成功', }; return statusMap[value] || value; }, }, ]; ``` ### 集成列配置系统 ```typescript import { columnConfigService } from '@/config/tableColumns'; import { ColumnConfig } from '@/config/tableColumns/types/columnConfig'; function MyTable() { const [columnConfig, setColumnConfig] = useState([]); // 加载列配置 useEffect(() => { columnConfigService .getColumnConfig('output') .then((config) => setColumnConfig(config.columns)) .catch((error) => { console.error('Failed to load column config:', error); setColumnConfig([]); }); }, []); return ( dataSource={data} rowKey="task_id" columnDefinitions={columns} columnConfig={columnConfig} // 传入列配置 selectedIds={selectedIds} onRowClick={handleRowClick} /> ); } ``` ### 完整示例:传输队列表格 ```typescript import React, { useState, useEffect } from 'react'; import { GenericDataTable, ColumnDefinition } from '@/components/GenericDataTable'; import GenericPagination from '@/components/GenericPagination'; import { useAppDispatch, useAppSelector } from '@/states/store'; import { fetchSendQueueThunk, sendJobSelectionSlice, sendJobPaginationSlice, } from '@/states/output/sendJob/slices/sendJobSlice'; import { SendJob } from '@/domain/output/sendJob'; import { FormattedMessage } from 'react-intl'; // 定义列 const sendJobColumns: ColumnDefinition[] = [ { title: , dataIndex: 'patient_name', width: 150, }, { title: , dataIndex: 'status', width: 120, }, ]; function OutputList() { const dispatch = useAppDispatch(); const { data: sendJobs } = useAppSelector((state) => state.sendJobEntities); const { loading } = useAppSelector((state) => state.sendJobUI); const { page, pageSize } = useAppSelector((state) => state.sendJobPagination); const selectedIds = useAppSelector((state) => state.sendJobSelection.selectedIds); // 加载数据 useEffect(() => { dispatch(fetchSendQueueThunk({ page, pageSize, filters: {} })); }, [dispatch, page, pageSize]); // 行点击 const handleRowClick = (record: SendJob) => { dispatch(sendJobSelectionSlice.actions.setSelectedIds([record.task_id])); }; return (
dataSource={sendJobs} rowKey="task_id" columnDefinitions={sendJobColumns} selectedIds={selectedIds} onRowClick={handleRowClick} loading={loading} className="px-4" />
state.sendJobPagination} entitiesSelector={(state) => state.sendJobEntities} paginationActions={sendJobPaginationSlice.actions} className="border-t" />
); } ``` ## 📝 API 参考 ### GenericDataTableProps | 属性 | 类型 | 必填 | 默认值 | 描述 | |------|------|------|--------|------| | dataSource | T[] | ✅ | - | 数据源 | | rowKey | keyof T \| ((record: T) => string) | ✅ | - | 行键,用于唯一标识每一行 | | columnDefinitions | ColumnDefinition[] | ✅ | - | 列定义数组 | | columnConfig | ColumnConfig[] | ❌ | [] | 列配置(显示/隐藏、排序等) | | selectedIds | string[] | ✅ | - | 选中的行 ID 列表 | | onRowClick | (record: T) => void | ❌ | - | 行点击事件 | | onRowDoubleClick | (record: T) => void | ❌ | - | 行双击事件 | | className | string | ❌ | '' | 自定义类名 | | loading | boolean | ❌ | false | 加载状态 | ### ColumnDefinition | 属性 | 类型 | 必填 | 默认值 | 描述 | |------|------|------|--------|------| | title | React.ReactNode | ✅ | - | 列标题 | | dataIndex | keyof T | ✅ | - | 数据索引 | | width | number | ❌ | 150 | 默认宽度(px) | | render | (value: any, record: T, index: number) => React.ReactNode | ❌ | - | 自定义渲染函数 | ## 🔧 高级用法 ### 1. 动态行键 ```typescript dataSource={data} rowKey={(record) => `${record.type}_${record.id}`} // 组合键 // ... /> ``` ### 2. 条件渲染 ```typescript const columns: ColumnDefinition[] = [ { title: '状态', dataIndex: 'status', render: (value, record) => { if (value === 'FAILED') { return 失败; } if (value === 'SUCCESS') { return 成功; } return value; }, }, ]; ``` ### 3. 多选支持 ```typescript const [selectedIds, setSelectedIds] = useState([]); const handleRowClick = (record: MyData) => { setSelectedIds((prev) => { if (prev.includes(record.id)) { // 取消选择 return prev.filter((id) => id !== record.id); } else { // 添加选择 return [...prev, record.id]; } }); }; // ... selectedIds={selectedIds} onRowClick={handleRowClick} /> ``` ## ⚠️ 注意事项 ### 1. 泛型类型约束 数据类型 `T` 必须是对象类型: ```typescript T extends Record ``` ### 2. rowKey 必须返回唯一值 ```typescript // ✅ 正确 rowKey="id" // 确保 id 唯一 // ✅ 正确 rowKey={(record) => record.task_id} // ❌ 错误 - 可能重复 rowKey={(record) => record.status} ``` ### 3. 列配置优先级 如果提供了 `columnConfig`,则: 1. 只显示 `visible: true` 的列 2. 按 `order` 排序 3. 使用 `width` 覆盖默认宽度 如果没有 `columnConfig`,显示所有列。 ### 4. 性能优化 对于大数据集,考虑: - 使用虚拟滚动 - 后端分页 - 懒加载 ## 🎨 样式定制 ### 1. 自定义类名 ```typescript ``` ### 2. 选中行样式 当前使用 `bg-yellow-500` 和 `hover:bg-yellow-800`,可以通过修改组件源码自定义: ```typescript // 在 GenericDataTable.tsx 中 rowClassName={(record) => { const rowId = getRowKey(record); return selectedIds.includes(rowId) ? 'bg-blue-500 hover:bg-blue-700' // 自定义颜色 : ''; }} ``` ## 🔄 与现有组件对比 ### WorklistTable vs GenericDataTable | 特性 | WorklistTable | GenericDataTable | |------|--------------|------------------| | 数据类型 | 固定 Task | 泛型 T | | 列定义 | 硬编码 | 传入参数 | | 复用性 | 低 | 高 | | 类型安全 | Task 类型 | 完全类型安全 | | 列配置 | 支持 | 支持 | | 列宽调整 | 支持 | 支持 | | 行交互 | 支持 | 支持 | ## 📚 相关文档 - [传输队列状态管理](./传输队列状态管理实现.md) - [表格列配置功能](./表格列配置功能.md) - [Ant Design Table](https://ant.design/components/table-cn/) - [react-resizable](https://github.com/react-grid-layout/react-resizable) ## 🚀 未来改进 1. **虚拟滚动** - 支持大数据集 2. **列拖拽排序** - 用户自定义列顺序 3. **列固定** - 支持左右固定列 4. **行展开** - 支持嵌套数据 5. **导出功能** - CSV/Excel 导出 6. **搜索过滤** - 列内搜索 ## 🐛 已知问题 暂无 ## 📝 更新日志 ### v1.0.0 (2025-10-11) - ✨ 初始版本 - ✅ 泛型支持 - ✅ 列配置系统 - ✅ 列宽调整 - ✅ 行交互 - ✅ 在 OutputList 中应用