# ListTable 组件 ## 组件说明 列表表格组件,基于 Ant Design Table 组件封装,提供统一的表格样式、行选择、分页、滚动和行点击等功能。**支持跨页全选功能**。 ## 组件位置 ``` src/components/ListTable/ListTable.jsx src/components/ListTable/ListTable.css ``` ## 参数说明 | 参数名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | columns | Array | 是 | - | 表格列配置数组 | | dataSource | Array | 是 | - | 表格数据源 | | rowKey | string | 否 | 'id' | 行数据的唯一标识字段名 | | selectedRowKeys | Array | 否 | [] | 选中行的 key 数组 | | onSelectionChange | function(keys: Array) | 否 | - | 行选择变化回调 | | **isAllPagesSelected** | boolean | 否 | false | 是否跨页全选所有数据 | | **totalCount** | number | 否 | - | 总数据量(用于跨页全选显示) | | **onSelectAllPages** | function() | 否 | - | 选择所有页回调 | | **onClearSelection** | function() | 否 | - | 清除选择回调 | | pagination | Object\|false | 否 | 默认配置 | 分页配置,false 表示不分页 | | scroll | Object | 否 | { x: 1200 } | 表格滚动配置 | | onRowClick | function(record: Object) | 否 | - | 行点击回调 | | selectedRow | Object | 否 | - | 当前选中的行数据对象 | | loading | boolean | 否 | false | 表格加载状态 | | className | string | 否 | '' | 自定义类名 | ### ColumnConfig 列配置 继承自 Ant Design Table 的列配置,常用属性: | 属性名 | 类型 | 说明 | |--------|------|------| | title | string\|ReactNode | 列标题 | | dataIndex | string | 数据字段名 | | key | string | 列唯一标识 | | width | number | 列宽度 | | align | 'left'\|'center'\|'right' | 对齐方式 | | fixed | 'left'\|'right' | 固定列 | | render | function(value, record, index) | 自定义渲染函数 | ### 默认分页配置 ```javascript { pageSize: 10, showSizeChanger: true, showQuickJumper: true, } ``` **注意**:当传入 `onSelectAllPages` 和 `onClearSelection` 时,组件会自动在 `showTotal` 位置显示选择信息和跨页全选操作: - **无选中**:已选择 0 项 - **部分选中**:已选择 3 项 [选择全部 100 项] [清除] - **跨页全选**:已选择 100 项 [清除选择] ## 使用示例 ### 基础用法 ```jsx import ListTable from '../components/ListTable/ListTable' function MyPage() { const columns = [ { title: '序号', dataIndex: 'id', key: 'id', width: 80, align: 'center', }, { title: '用户名', dataIndex: 'userName', key: 'userName', width: 150, }, { title: '姓名', dataIndex: 'name', key: 'name', width: 120, }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (status) => ( {status === 'enabled' ? '启用' : '停用'} ), }, ] const dataSource = [ { id: 1, userName: 'admin', name: '管理员', status: 'enabled' }, { id: 2, userName: 'user', name: '张三', status: 'disabled' }, ] return ( ) } ``` ### 带行选择 ```jsx import { useState } from 'react' import ListTable from '../components/ListTable/ListTable' function UserListPage() { const [selectedRowKeys, setSelectedRowKeys] = useState([]) return (
{/* 显示选中的数量 */}
已选择 {selectedRowKeys.length} 项
) } ``` ### 带行点击和高亮 ```jsx import { useState } from 'react' import ListTable from '../components/ListTable/ListTable' function UserListPage() { const [selectedUser, setSelectedUser] = useState(null) const handleRowClick = (record) => { setSelectedUser(record) // 打开详情抽屉等操作 setShowDetailDrawer(true) } return ( ) } ``` ### 自定义分页 ```jsx `总计 ${total} 条记录`, pageSizeOptions: ['10', '20', '50', '100'], }} /> ``` ### 禁用分页 ```jsx ``` ### 带加载状态 ```jsx import { useState, useEffect } from 'react' function UserListPage() { const [loading, setLoading] = useState(false) const [dataSource, setDataSource] = useState([]) const fetchData = async () => { setLoading(true) try { const data = await api.fetchUsers() setDataSource(data) } finally { setLoading(false) } } useEffect(() => { fetchData() }, []) return ( ) } ``` ### 横向滚动(列较多时) ```jsx ``` ### 固定列 ```jsx const columns = [ { title: '序号', dataIndex: 'id', key: 'id', width: 80, fixed: 'left', // 固定在左侧 }, // ... 其他列 { title: '操作', key: 'action', width: 200, fixed: 'right', // 固定在右侧 render: (_, record) => ( ), }, ] ``` ### 跨页全选(重要功能) 支持选择所有页的数据,而不仅仅是当前页。 ```jsx import { useState, useMemo } from 'react' import ListTable from '../components/ListTable/ListTable' function UserListPage() { const [allData] = useState([...]) // 所有数据 const [currentPage, setCurrentPage] = useState(1) const [pageSize, setPageSize] = useState(10) // 选择状态 const [selectedRowKeys, setSelectedRowKeys] = useState([]) const [isAllPagesSelected, setIsAllPagesSelected] = useState(false) // 当前页数据 const currentPageData = useMemo(() => { const start = (currentPage - 1) * pageSize const end = start + pageSize return allData.slice(start, end) }, [allData, currentPage, pageSize]) // 选择变化处理(重要:支持跨页选择) const handleSelectionChange = (keys) => { // keys 是当前页面操作后的选中项 // 需要合并其他页的选中项,以保持跨页选择状态 const currentPageKeys = currentPageData.map(item => item.id) const otherPagesSelectedKeys = selectedRowKeys.filter( key => !currentPageKeys.includes(key) ) const newSelectedKeys = [...otherPagesSelectedKeys, ...keys] setSelectedRowKeys(newSelectedKeys) setIsAllPagesSelected(false) } // 选择所有页 const handleSelectAllPages = () => { setIsAllPagesSelected(true) setSelectedRowKeys(allData.map(item => item.id)) } // 清除选择 const handleClearSelection = () => { setSelectedRowKeys([]) setIsAllPagesSelected(false) } return ( { setCurrentPage(page) setPageSize(size) }, }} /> ) } ``` **跨页选择的关键点**: 1. **状态管理**:需要维护 `selectedRowKeys` 和 `isAllPagesSelected` 两个状态 2. **选择合并**:在 `onSelectionChange` 中合并其他页的选择 3. **全选操作**:将所有数据的 key 设置到 `selectedRowKeys` 4. **传递回调**:传入 `onSelectAllPages` 和 `onClearSelection` **工作原理**: ```javascript // 第 1 页选中 row1, row2 selectedRowKeys = ['row1', 'row2'] // 切换到第 2 页,选中 row11 // handleSelectionChange 自动合并 selectedRowKeys = ['row1', 'row2', 'row11'] // 点击"选择全部" isAllPagesSelected = true selectedRowKeys = ['row1', 'row2', ..., 'row100'] ``` ### 完整示例(包含跨页全选) ```jsx import { useState, useMemo } from 'react' import { Button, Modal, message } from 'antd' import ListTable from '../components/ListTable/ListTable' import ListActionBar from '../components/ListActionBar/ListActionBar' import { DeleteOutlined, ExportOutlined } from '@ant-design/icons' function UserListPage() { const [allUsers] = useState([...]) // 所有用户数据 const [currentPage, setCurrentPage] = useState(1) const [pageSize, setPageSize] = useState(10) const [selectedRowKeys, setSelectedRowKeys] = useState([]) const [isAllPagesSelected, setIsAllPagesSelected] = useState(false) const currentPageData = useMemo(() => { const start = (currentPage - 1) * pageSize return allUsers.slice(start, start + pageSize) }, [allUsers, currentPage, pageSize]) const handleSelectionChange = (keys) => { const currentPageKeys = currentPageData.map(item => item.id) const otherPagesSelectedKeys = selectedRowKeys.filter( key => !currentPageKeys.includes(key) ) setSelectedRowKeys([...otherPagesSelectedKeys, ...keys]) setIsAllPagesSelected(false) } const handleSelectAllPages = () => { setIsAllPagesSelected(true) setSelectedRowKeys(allUsers.map(item => item.id)) message.success(`已选择全部 ${allUsers.length} 条数据`) } const handleClearSelection = () => { setSelectedRowKeys([]) setIsAllPagesSelected(false) } const handleBatchDelete = () => { Modal.confirm({ title: '确认删除', content: isAllPagesSelected ? `确定要删除全部 ${allUsers.length} 条数据吗?` : `确定要删除选中的 ${selectedRowKeys.length} 条数据吗?`, onOk: () => { // 执行删除 message.success('删除成功') handleClearSelection() }, }) } return (
{/* 操作栏 */} , danger: true, disabled: selectedRowKeys.length === 0, onClick: handleBatchDelete, }, ]} search={...} /> {/* 表格 */} { setCurrentPage(page) setPageSize(size) }, }} />
) } ``` ### 完整示例(不使用跨页全选) ```jsx import { useState } from 'react' import ListTable from '../components/ListTable/ListTable' import { Tag, Button, Space } from 'antd' import { EditOutlined, DeleteOutlined } from '@ant-design/icons' function UserListPage() { const [selectedRowKeys, setSelectedRowKeys] = useState([]) const [selectedUser, setSelectedUser] = useState(null) const [showDetailDrawer, setShowDetailDrawer] = useState(false) const columns = [ { title: '序号', dataIndex: 'id', key: 'id', width: 80, align: 'center', }, { title: '用户名', dataIndex: 'userName', key: 'userName', width: 150, }, { title: '姓名', dataIndex: 'name', key: 'name', width: 120, }, { title: '用户分组', dataIndex: 'group', key: 'group', width: 150, render: (text) => {text}, }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, align: 'center', render: (status) => ( {status === 'enabled' ? '启用' : '停用'} ), }, { title: '操作', key: 'action', width: 180, fixed: 'right', render: (_, record) => ( e.stopPropagation()}> ), }, ] const handleRowClick = (record) => { setSelectedUser(record) setShowDetailDrawer(true) } return ( ) } ``` ## 样式定制 组件提供以下 CSS 类名供自定义样式: - `.list-table-container` - 表格容器 - `.row-selected` - 选中行的类名 - `.table-selection-info` - 分页器中的选择信息容器 - `.selection-count` - 选择数量文本 - `.count-highlight` - 高亮数字 - `.selection-action` - 操作链接(选择全部、清除) ### 固定高度设计 ListTable 组件采用固定表格体高度设计,确保页面布局的稳定性: - **尺寸**:使用 Ant Design `size="middle"` 属性 - **行高**:47px(Ant Design middle 尺寸默认值) - **表格体高度**:470px(固定高度,内部滚动) - **显示行数**:10 行(470px ÷ 47px = 10) - **分页器高度**:56px(Ant Design 默认) - **容器内边距**:16px × 2 **设计说明**: - 组件使用 Ant Design 的标准 `size="middle"` 属性,不自定义行高 - 表格体固定 470px 高度,恰好显示 10 行数据 - 超过 10 行的内容通过表格体内部滚动查看 - 当数据不足 10 行时,表格体仍保持 470px 高度,确保布局稳定 - 分页器上边距 16px,与表格体保持适当间距 ## 使用场景 1. **用户列表** - 显示和管理用户数据 2. **设备列表** - 显示和管理设备信息 3. **订单列表** - 显示订单数据 4. **任何需要表格展示的数据列表** ## 注意事项 1. `columns` 配置中的 `key` 必须唯一 2. `dataSource` 中的每条数据必须有 `rowKey` 指定的唯一标识字段(默认为 `id`) 3. 操作列中的点击事件需要使用 `e.stopPropagation()` 阻止事件冒泡,避免触发行点击 4. 当列数较多时,建议设置合适的 `scroll.x` 值并固定首尾列 5. `selectedRow` 用于高亮显示,`selectedRowKeys` 用于多选 6. 使用 `render` 函数时,要注意性能,避免在渲染函数中进行复杂计算 7. **跨页全选**:必须在 `onSelectionChange` 中合并其他页的选择,否则切换页面会丢失选择状态 8. **跨页全选**:传入 `onSelectAllPages` 和 `onClearSelection` 后,组件会自动显示选择信息和全选操作 9. **跨页全选**:`totalCount` 用于显示总数量,如果不传则使用 `pagination.total` ## 相关组件 - [ListActionBar](./ListActionBar.md) - 列表操作栏组件(支持批量操作) - [CrossPageSelection](./CrossPageSelection.md) - 跨页全选功能完整文档 --- **更新日志** - v1.1.0 (2025-11-18) - ✨ 新增跨页全选功能支持 - ✨ 自动在分页器显示选择信息和全选操作 - 📝 完善文档和使用示例 - v1.0.0 (2025-11-15) - 🎉 初始版本 - ✨ 基础表格功能 - ✨ 行选择、分页、滚动支持