16 KiB
16 KiB
ListTable 组件
组件说明
列表表格组件,基于 Ant Design Table 组件封装,提供统一的表格样式、行选择、分页、滚动和行点击等功能。支持跨页全选功能。
组件位置
src/components/ListTable/ListTable.jsx
src/components/ListTable/ListTable.css
参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| columns | Array | 是 | - | 表格列配置数组 |
| dataSource | Array | 是 | - | 表格数据源 |
| rowKey | string | 否 | 'id' | 行数据的唯一标识字段名 |
| selectedRowKeys | Array<string|number> | 否 | [] | 选中行的 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) | 自定义渲染函数 |
默认分页配置
{
pageSize: 10,
showSizeChanger: true,
showQuickJumper: true,
}
注意:当传入 onSelectAllPages 和 onClearSelection 时,组件会自动在 showTotal 位置显示选择信息和跨页全选操作:
- 无选中:已选择 0 项
- 部分选中:已选择 3 项 [选择全部 100 项] [清除]
- 跨页全选:已选择 100 项 [清除选择]
使用示例
基础用法
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) => (
<Tag color={status === 'enabled' ? 'green' : 'default'}>
{status === 'enabled' ? '启用' : '停用'}
</Tag>
),
},
]
const dataSource = [
{ id: 1, userName: 'admin', name: '管理员', status: 'enabled' },
{ id: 2, userName: 'user', name: '张三', status: 'disabled' },
]
return (
<ListTable
columns={columns}
dataSource={dataSource}
/>
)
}
带行选择
import { useState } from 'react'
import ListTable from '../components/ListTable/ListTable'
function UserListPage() {
const [selectedRowKeys, setSelectedRowKeys] = useState([])
return (
<div>
{/* 显示选中的数量 */}
<div>已选择 {selectedRowKeys.length} 项</div>
<ListTable
columns={columns}
dataSource={dataSource}
selectedRowKeys={selectedRowKeys}
onSelectionChange={setSelectedRowKeys}
/>
</div>
)
}
带行点击和高亮
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 (
<ListTable
columns={columns}
dataSource={dataSource}
onRowClick={handleRowClick}
selectedRow={selectedUser}
/>
)
}
自定义分页
<ListTable
columns={columns}
dataSource={dataSource}
pagination={{
pageSize: 20,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `总计 ${total} 条记录`,
pageSizeOptions: ['10', '20', '50', '100'],
}}
/>
禁用分页
<ListTable
columns={columns}
dataSource={dataSource}
pagination={false}
/>
带加载状态
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 (
<ListTable
columns={columns}
dataSource={dataSource}
loading={loading}
/>
)
}
横向滚动(列较多时)
<ListTable
columns={columns}
dataSource={dataSource}
scroll={{ x: 1600 }} // 内容宽度超过容器时出现横向滚动
/>
固定列
const columns = [
{
title: '序号',
dataIndex: 'id',
key: 'id',
width: 80,
fixed: 'left', // 固定在左侧
},
// ... 其他列
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right', // 固定在右侧
render: (_, record) => (
<Space>
<Button type="link" size="small">编辑</Button>
<Button type="link" size="small" danger>删除</Button>
</Space>
),
},
]
<ListTable
columns={columns}
dataSource={dataSource}
scroll={{ x: 1600 }}
/>
跨页全选(重要功能)
支持选择所有页的数据,而不仅仅是当前页。
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 (
<ListTable
columns={columns}
dataSource={currentPageData}
selectedRowKeys={selectedRowKeys}
onSelectionChange={handleSelectionChange}
// 跨页全选相关 props
isAllPagesSelected={isAllPagesSelected}
totalCount={allData.length}
onSelectAllPages={handleSelectAllPages}
onClearSelection={handleClearSelection}
pagination={{
current: currentPage,
pageSize: pageSize,
total: allData.length,
onChange: (page, size) => {
setCurrentPage(page)
setPageSize(size)
},
}}
/>
)
}
跨页选择的关键点:
- 状态管理:需要维护
selectedRowKeys和isAllPagesSelected两个状态 - 选择合并:在
onSelectionChange中合并其他页的选择 - 全选操作:将所有数据的 key 设置到
selectedRowKeys - 传递回调:传入
onSelectAllPages和onClearSelection
工作原理:
// 第 1 页选中 row1, row2
selectedRowKeys = ['row1', 'row2']
// 切换到第 2 页,选中 row11
// handleSelectionChange 自动合并
selectedRowKeys = ['row1', 'row2', 'row11']
// 点击"选择全部"
isAllPagesSelected = true
selectedRowKeys = ['row1', 'row2', ..., 'row100']
完整示例(包含跨页全选)
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 (
<div>
{/* 操作栏 */}
<ListActionBar
actions={[...]}
batchActions={[
{
key: 'delete',
label: '批量删除',
icon: <DeleteOutlined />,
danger: true,
disabled: selectedRowKeys.length === 0,
onClick: handleBatchDelete,
},
]}
search={...}
/>
{/* 表格 */}
<ListTable
columns={columns}
dataSource={currentPageData}
selectedRowKeys={selectedRowKeys}
onSelectionChange={handleSelectionChange}
isAllPagesSelected={isAllPagesSelected}
totalCount={allUsers.length}
onSelectAllPages={handleSelectAllPages}
onClearSelection={handleClearSelection}
pagination={{
current: currentPage,
pageSize: pageSize,
total: allUsers.length,
onChange: (page, size) => {
setCurrentPage(page)
setPageSize(size)
},
}}
/>
</div>
)
}
完整示例(不使用跨页全选)
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) => <Tag color="blue">{text}</Tag>,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100,
align: 'center',
render: (status) => (
<Tag color={status === 'enabled' ? 'green' : 'default'}>
{status === 'enabled' ? '启用' : '停用'}
</Tag>
),
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
render: (_, record) => (
<Space size="small" onClick={(e) => e.stopPropagation()}>
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
编辑
</Button>
<Button
type="link"
size="small"
icon={<DeleteOutlined />}
danger
onClick={() => handleDelete(record)}
>
删除
</Button>
</Space>
),
},
]
const handleRowClick = (record) => {
setSelectedUser(record)
setShowDetailDrawer(true)
}
return (
<ListTable
columns={columns}
dataSource={filteredUsers}
selectedRowKeys={selectedRowKeys}
onSelectionChange={setSelectedRowKeys}
onRowClick={handleRowClick}
selectedRow={selectedUser}
scroll={{ x: 1400 }}
/>
)
}
样式定制
组件提供以下 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,与表格体保持适当间距
使用场景
- 用户列表 - 显示和管理用户数据
- 设备列表 - 显示和管理设备信息
- 订单列表 - 显示订单数据
- 任何需要表格展示的数据列表
注意事项
columns配置中的key必须唯一dataSource中的每条数据必须有rowKey指定的唯一标识字段(默认为id)- 操作列中的点击事件需要使用
e.stopPropagation()阻止事件冒泡,避免触发行点击 - 当列数较多时,建议设置合适的
scroll.x值并固定首尾列 selectedRow用于高亮显示,selectedRowKeys用于多选- 使用
render函数时,要注意性能,避免在渲染函数中进行复杂计算 - 跨页全选:必须在
onSelectionChange中合并其他页的选择,否则切换页面会丢失选择状态 - 跨页全选:传入
onSelectAllPages和onClearSelection后,组件会自动显示选择信息和全选操作 - 跨页全选:
totalCount用于显示总数量,如果不传则使用pagination.total
相关组件
- ListActionBar - 列表操作栏组件(支持批量操作)
- CrossPageSelection - 跨页全选功能完整文档
更新日志
-
v1.1.0 (2025-11-18)
- ✨ 新增跨页全选功能支持
- ✨ 自动在分页器显示选择信息和全选操作
- 📝 完善文档和使用示例
-
v1.0.0 (2025-11-15)
- 🎉 初始版本
- ✨ 基础表格功能
- ✨ 行选择、分页、滚动支持