nex_design/docs/components/ListTable.md

635 lines
16 KiB
Markdown
Raw Normal View History

2025-11-05 06:17:47 +00:00
# ListTable 组件
## 组件说明
2025-11-18 09:57:08 +00:00
列表表格组件,基于 Ant Design Table 组件封装,提供统一的表格样式、行选择、分页、滚动和行点击等功能。**支持跨页全选功能**。
2025-11-05 06:17:47 +00:00
## 组件位置
```
src/components/ListTable/ListTable.jsx
src/components/ListTable/ListTable.css
```
## 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| columns | Array<ColumnConfig> | 是 | - | 表格列配置数组 |
| dataSource | Array<Object> | 是 | - | 表格数据源 |
| rowKey | string | 否 | 'id' | 行数据的唯一标识字段名 |
| selectedRowKeys | Array<string\|number> | 否 | [] | 选中行的 key 数组 |
| onSelectionChange | function(keys: Array) | 否 | - | 行选择变化回调 |
2025-11-18 09:57:08 +00:00
| **isAllPagesSelected** | boolean | 否 | false | 是否跨页全选所有数据 |
| **totalCount** | number | 否 | - | 总数据量(用于跨页全选显示) |
| **onSelectAllPages** | function() | 否 | - | 选择所有页回调 |
| **onClearSelection** | function() | 否 | - | 清除选择回调 |
2025-11-05 06:17:47 +00:00
| 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,
}
```
2025-11-18 09:57:08 +00:00
**注意**:当传入 `onSelectAllPages``onClearSelection` 时,组件会自动在 `showTotal` 位置显示选择信息和跨页全选操作:
- **无选中**:已选择 0 项
- **部分选中**:已选择 3 项 [选择全部 100 项] [清除]
- **跨页全选**:已选择 100 项 [清除选择]
2025-11-05 06:17:47 +00:00
## 使用示例
### 基础用法
```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) => (
<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}
/>
)
}
```
### 带行选择
```jsx
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>
)
}
```
### 带行点击和高亮
```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 (
<ListTable
columns={columns}
dataSource={dataSource}
onRowClick={handleRowClick}
selectedRow={selectedUser}
/>
)
}
```
### 自定义分页
```jsx
<ListTable
columns={columns}
dataSource={dataSource}
pagination={{
pageSize: 20,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `总计 ${total} 条记录`,
pageSizeOptions: ['10', '20', '50', '100'],
}}
/>
```
### 禁用分页
```jsx
<ListTable
columns={columns}
dataSource={dataSource}
pagination={false}
/>
```
### 带加载状态
```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 (
<ListTable
columns={columns}
dataSource={dataSource}
loading={loading}
/>
)
}
```
### 横向滚动(列较多时)
```jsx
<ListTable
columns={columns}
dataSource={dataSource}
scroll={{ x: 1600 }} // 内容宽度超过容器时出现横向滚动
/>
```
### 固定列
```jsx
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 }}
/>
```
2025-11-18 09:57:08 +00:00
### 跨页全选(重要功能)
支持选择所有页的数据,而不仅仅是当前页。
```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 (
<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)
},
}}
/>
)
}
```
**跨页选择的关键点**
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 (
<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>
)
}
```
### 完整示例(不使用跨页全选)
2025-11-05 06:17:47 +00:00
```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) => <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` - 选中行的类名
2025-11-18 09:57:08 +00:00
- `.table-selection-info` - 分页器中的选择信息容器
- `.selection-count` - 选择数量文本
- `.count-highlight` - 高亮数字
- `.selection-action` - 操作链接(选择全部、清除)
2025-11-05 06:17:47 +00:00
2025-11-13 10:11:08 +00:00
### 固定高度设计
ListTable 组件采用固定表格体高度设计,确保页面布局的稳定性:
- **尺寸**:使用 Ant Design `size="middle"` 属性
- **行高**47pxAnt Design middle 尺寸默认值)
- **表格体高度**470px固定高度内部滚动
- **显示行数**10 行470px ÷ 47px = 10
- **分页器高度**56pxAnt Design 默认)
- **容器内边距**16px × 2
**设计说明**
- 组件使用 Ant Design 的标准 `size="middle"` 属性,不自定义行高
- 表格体固定 470px 高度,恰好显示 10 行数据
- 超过 10 行的内容通过表格体内部滚动查看
- 当数据不足 10 行时,表格体仍保持 470px 高度,确保布局稳定
- 分页器上边距 16px与表格体保持适当间距
2025-11-05 06:17:47 +00:00
## 使用场景
1. **用户列表** - 显示和管理用户数据
2. **设备列表** - 显示和管理设备信息
3. **订单列表** - 显示订单数据
4. **任何需要表格展示的数据列表**
## 注意事项
1. `columns` 配置中的 `key` 必须唯一
2. `dataSource` 中的每条数据必须有 `rowKey` 指定的唯一标识字段(默认为 `id`
3. 操作列中的点击事件需要使用 `e.stopPropagation()` 阻止事件冒泡,避免触发行点击
4. 当列数较多时,建议设置合适的 `scroll.x` 值并固定首尾列
5. `selectedRow` 用于高亮显示,`selectedRowKeys` 用于多选
2025-11-18 09:57:08 +00:00
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)
- 🎉 初始版本
- ✨ 基础表格功能
- ✨ 行选择、分页、滚动支持