16 KiB
16 KiB
CrossPageSelection - 跨页全选功能
📖 概述
跨页全选是列表页面中的常见需求,当数据量较大且分页显示时,用户可能需要选择所有数据而不仅仅是当前页的数据。本功能提供了一套完整的跨页全选解决方案,包括:
- ListActionBar 扩展 - 选择信息显示和全选操作
- ListTable 扩展 - 跨页全选状态支持
🎯 设计方案
设计理念
将选择信息和全选操作集成到操作栏(ListActionBar),分页器只显示分页信息,保持功能职责清晰分离。
交互流程
1. 用户勾选当前页或部分数据
↓
2. 操作栏显示:"已选择 5 项 [选择全部 100 项] [清除]"
↓
3. 用户点击"选择全部 100 项"
↓
4. 设置 isAllPagesSelected = true
↓
5. selectedRowKeys 包含所有数据的 key
↓
6. 操作栏更新:"已选择 100 项(全部页) [清除]"
↓
7. 执行批量操作(删除、导出等)
操作栏显示逻辑
| 选择状态 | 操作栏显示 |
|---|---|
| 无选中 | [新建] 按钮 |
| 选中部分(< 总数) | "已选择 5 项 [选择全部 100 项] [清除]" + 批量操作按钮 |
| 跨页全选 | "已选择 100 项(全部页) [清除]" + 批量操作按钮 |
📦 核心功能
1. ListActionBar - 操作栏扩展(核心)
新增功能
在原有 ListActionBar 的基础上,新增了批量操作区域:
- 当有选中项时,隐藏常规操作按钮,显示批量操作区域
- 显示选中信息(数量、是否跨页全选)
- 提供快捷的"选择全部"和"清除"链接
- 显示批量操作按钮(批量编辑、批量删除等)
新增 Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| batchActions | 批量操作按钮配置数组 | Array<Action> | [] |
| selectionInfo | 选中信息对象 | SelectionInfo | null |
| onSelectAllPages | 选择所有页回调 | function | - |
| onClearSelection | 清除选择回调 | function | - |
SelectionInfo 对象
{
count: number, // 选中数量
total: number, // 总数据量
isAllPagesSelected: boolean // 是否跨页全选
}
Action 配置
{
key: string, // 唯一标识
label: string, // 按钮文本
type?: string, // 按钮类型:default | primary | dashed | link | text
icon?: ReactNode, // 图标
disabled?: boolean, // 是否禁用
danger?: boolean, // 危险按钮样式
onClick: function // 点击回调
}
使用示例
import ListActionBar from '../components/ListActionBar/ListActionBar'
import { DeleteOutlined, ExportOutlined, EditOutlined } from '@ant-design/icons'
<ListActionBar
// 常规操作按钮(无选中时显示)
actions={[
{
key: 'add',
label: '新建',
type: 'primary',
icon: <PlusOutlined />,
onClick: handleAdd,
},
]}
// 批量操作按钮(有选中时显示)
batchActions={[
{
key: 'edit',
label: '批量编辑',
icon: <EditOutlined />,
onClick: handleBatchEdit,
},
{
key: 'export',
label: '批量导出',
icon: <ExportOutlined />,
onClick: handleBatchExport,
},
{
key: 'delete',
label: '批量删除',
icon: <DeleteOutlined />,
danger: true,
onClick: handleBatchDelete,
},
]}
// 选中信息
selectionInfo={
selectedRowKeys.length > 0
? {
count: selectedRowKeys.length,
total: totalCount,
isAllPagesSelected: isAllPagesSelected,
}
: null
}
onSelectAllPages={handleSelectAllPages}
onClearSelection={handleClearSelection}
// 其他原有配置...
search={{ ... }}
showRefresh
onRefresh={handleRefresh}
/>
2. ListTable - 表格扩展(分页器集成全选)
新增功能
支持跨页全选状态的显示:
- 当
isAllPagesSelected=true时,所有行显示为选中状态 - 自定义表头全选框,显示"全选"标识
- 禁用单个 checkbox,防止用户取消选择(需通过"清除选择"统一取消)
新增 Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| isAllPagesSelected | 是否跨页全选所有数据 | boolean | false |
| totalCount | 总数据量 | number | - |
使用示例(重点:自定义 showTotal)
import ListTable from '../components/ListTable/ListTable'
<ListTable
columns={columns}
dataSource={currentPageData}
rowKey="id"
selectedRowKeys={selectedRowKeys}
onSelectionChange={handleSelectionChange}
// 跨页全选相关
isAllPagesSelected={isAllPagesSelected}
totalCount={totalCount}
// 分页配置 - 关键:自<EFBFBD><EFBFBD><EFBFBD>义 showTotal
pagination={{
current: currentPage,
pageSize: pageSize,
total: totalCount,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => {
// 如果有选中项,显示选择信息和全选操作
if (selectedRowKeys.length > 0) {
if (isAllPagesSelected) {
return (
<span className="pagination-selection-info">
已选择全部 <strong>{total}</strong> 条数据
<a onClick={handleClearSelection} style={{ marginLeft: 8 }}>
清除选择
</a>
</span>
)
} else if (selectedRowKeys.length < total) {
return (
<span className="pagination-selection-info">
已选择 <strong>{selectedRowKeys.length}</strong> 条数据
<a onClick={handleSelectAllPages} style={{ marginLeft: 8 }}>
选择全部 {total} 条
</a>
<a onClick={handleClearSelection} style={{ marginLeft: 8 }}>
清除
</a>
</span>
)
} else {
return (
<span className="pagination-selection-info">
已选择全部 <strong>{total}</strong> 条数据
<a onClick={handleClearSelection} style={{ marginLeft: 8 }}>
清除选择
</a>
</span>
)
}
}
// 无选中项时,显示常规的分页信息
return `共 ${total} 条`
},
onChange: (page, size) => {
setCurrentPage(page)
setPageSize(size)
},
}}
/>
分页器样式
/* 分页器中的选择信息样式 */
.pagination-selection-info {
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
}
.pagination-selection-info strong {
color: #1677ff;
font-weight: 600;
margin: 0 4px;
}
.pagination-selection-info a {
color: #1677ff;
cursor: pointer;
text-decoration: none;
transition: all 0.2s;
}
.pagination-selection-info a:hover {
text-decoration: underline;
}
🚀 完整使用示例
状态管理
import { useState } from 'react'
function MyListPage() {
// 数据源(实际项目中通常从 API 获取)
const [dataSource] = useState([...]) // 所有数据
const [totalCount] = useState(dataSource.length)
// 分页状态
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 dataSource.slice(start, end)
}, [dataSource, 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(dataSource.map(item => item.id)) // 设置所有数据的 key
message.success(`已选择全部 ${totalCount} 条数据`)
}
// 清除选择
const handleClearSelection = () => {
setSelectedRowKeys([])
setIsAllPagesSelected(false)
message.info('已清除选择')
}
// 批量删除
const handleBatchDelete = () => {
Modal.confirm({
title: '确认删除',
content: isAllPagesSelected
? `确定要删除全部 ${totalCount} 条数据吗?`
: `确定要删除选中的 ${selectedRowKeys.length} 条数据吗?`,
onOk: () => {
// 执行删除逻辑
if (isAllPagesSelected) {
// 删除所有数据
} else {
// 删除选中的数据:selectedRowKeys
}
handleClearSelection()
},
})
}
页面结构
return (
<div className="my-list-page">
{/* 操作栏 - 显示选择信息和全选操作 */}
<ListActionBar
actions={[...]}
batchActions={[...]}
selectionInfo={
selectedRowKeys.length > 0
? {
count: selectedRowKeys.length,
total: totalCount,
isAllPagesSelected,
}
: null
}
onSelectAllPages={handleSelectAllPages}
onClearSelection={handleClearSelection}
search={...}
/>
{/* 列表表格 - 分页器只显示分页信息 */}
<ListTable
columns={columns}
dataSource={currentPageData}
selectedRowKeys={selectedRowKeys}
onSelectionChange={handleSelectionChange}
isAllPagesSelected={isAllPagesSelected}
totalCount={totalCount}
pagination={{
showTotal: (total) => `共 ${total} 条`,
}}
/>
</div>
)
💡 最佳实践
0. 跨页选择的实现要点(重要!)
由于 Table 组件只接收当前页数据(currentPageData),需要手动维护跨页的选中状态:
const handleSelectionChange = (keys) => {
// ❌ 错误做法:直接替换会丢失其他页的选择
// setSelectedRowKeys(keys)
// ✅ 正确做法:合并其他页的选择
const currentPageKeys = currentPageData.map(item => item.id)
const otherPagesSelectedKeys = selectedRowKeys.filter(key => !currentPageKeys.includes(key))
const newSelectedKeys = [...otherPagesSelectedKeys, ...keys]
setSelectedRowKeys(newSelectedKeys)
setIsAllPagesSelected(false)
}
工作原理:
currentPageKeys- 当前页所有数据的 key 列表otherPagesSelectedKeys- 从已选项中过滤出不在当前页的 keys(即其他页的选择)newSelectedKeys- 合并其他页的选择 + 当前页的新选择- 这样就保持了所有页的选中状态
1. 批量操作提示
执行批量操作时,明确提示操作范围:
const handleBatchDelete = () => {
const count = isAllPagesSelected ? totalCount : selectedRowKeys.length
const scope = isAllPagesSelected ? '全部' : '选中的'
Modal.confirm({
title: '确认删除',
content: `确定要删除${scope} ${count} 条数据吗?此操作不可恢复。`,
okText: '确定',
cancelText: '取消',
okButtonProps: { danger: true },
onOk: async () => {
try {
if (isAllPagesSelected) {
await deleteAll()
} else {
await deleteByIds(selectedRowKeys)
}
message.success('删除成功')
handleClearSelection()
refreshData()
} catch (error) {
message.error('删除失败:' + error.message)
}
},
})
}
2. 分页切换时的选择状态
根据业务需求,可以选择两种策略:
策略 A:保持选择状态(推荐)
// 切换分页时,保持已选择的数据
const handlePageChange = (page, size) => {
setCurrentPage(page)
setPageSize(size)
// 不清除 selectedRowKeys,保持选择状态
}
策略 B:清除选择状态
// 切换分页时,清除选择
const handlePageChange = (page, size) => {
setCurrentPage(page)
setPageSize(size)
handleClearSelection() // 清除选择
}
3. 后端 API 对接
跨页全选时,后端 API 应支持两种方式:
// 方式1:发送所有选中的 ID(推荐小数据量)
const handleBatchOperation = async () => {
await api.batchOperation({
ids: selectedRowKeys, // 所有选中的 ID
})
}
// 方式2:发送全选标识 + 筛选条件(推荐大数据量)
const handleBatchOperation = async () => {
await api.batchOperation({
selectAll: isAllPagesSelected,
filters: currentFilters, // 当前筛选条件
excludeIds: [], // 排除的 ID(如果有反选功能)
})
}
4. 性能优化
当数据量很大时(如超过 10000 条),避免一次性加载所有 ID:
const handleSelectAllPages = () => {
setIsAllPagesSelected(true)
// 不要: setSelectedRowKeys(dataSource.map(item => item.id))
// 太多 ID 会导致性能问题
// 推荐: 只标记状态,实际操作时由后端根据筛选条件处理
message.success(`已选择全部数据`)
}
const handleBatchDelete = async () => {
if (isAllPagesSelected) {
// 发送全选标识和筛选条件,由后端处理
await api.deleteAll({ filters: currentFilters })
} else {
// 发送具体的 ID 列表
await api.deleteByIds(selectedRowKeys)
}
}
📝 注意事项
-
全选状态的一致性
- 手动勾选/取消勾选时,应取消跨页全选状态
- 确保
selectedRowKeys和isAllPagesSelected状态同步
-
筛选和搜索
- 执行筛选或搜索后,应清除选择状态
- 或者保持选择,但提示用户"选择的数据可能不在当前筛选结果中"
-
数据刷新
- 刷新数据后,应重新评估选择状态的有效性
- 建议清除选择,或检查已选数据是否仍然存在
-
权限控制
- 批量操作应检查权限
- 跨页全选时,应提示可能操作大量数据
🎨 样式定制
ListActionBar 批量操作区样式定制
/* 修改选中信息区域背景色 */
.selection-info {
background: #f0f9ff;
border-color: #69b1ff;
}
/* 修改选中数量颜色 */
.selection-count strong {
color: #ff4d4f;
}
/* 修改全部页标识颜色 */
.all-pages-tag {
color: #ff4d4f;
}
/* 修改链接颜色 */
.select-all-link,
.clear-selection-link {
color: #52c41a;
}
🔗 相关组件
- ListTable - 列表表格组件
- ListActionBar - 列表<E58897><E8A1A8><EFBFBD>作栏组件
- PageTitleBar - 页面标题栏组件
📚 参考资料
更新日志
-
v1.2.0 (2025-11-18)
- ♻️ 重构设计:将全选功能集成到操作栏,分页器只显示分页信息
- ✨ 更清晰的职责分离:操作相关在操作栏,分页相关在分页器
- 📝 更新文档和使用示例
-
v1.1.0 (2025-11-18)
- ♻️ 重构设计:将全选功能集成到分页器,移除独立的 SelectionAlert 组件
- ✨ 更简洁的交互:选择信息、全选操作、分页控制集中在一个区域
- 📝 更新文档和使用示例
-
v1.0.0 (2025-11-18)
- ✨ 新增 SelectionAlert 组件
- ✨ 扩展 ListActionBar 支持批量操作
- ✨ 扩展 ListTable 支持跨页全选状态
- 📝 完善文档和使用示例