nex_design/docs/components/CrossPageSelection.md

591 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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&lt;Action&gt; | [] |
| selectionInfo | 选中信息对象 | SelectionInfo | null |
| onSelectAllPages | 选择所有页回调 | function | - |
| onClearSelection | 清除选择回调 | function | - |
#### SelectionInfo 对象
```typescript
{
count: number, // 选中数量
total: number, // 总数据量
isAllPagesSelected: boolean // 是否跨页全选
}
```
#### Action 配置
```typescript
{
key: string, // 唯一标识
label: string, // 按钮文本
type?: string, // 按钮类型default | primary | dashed | link | text
icon?: ReactNode, // 图标
disabled?: boolean, // 是否禁用
danger?: boolean, // 危险按钮样式
onClick: function // 点击回调
}
```
#### 使用示例
```jsx
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
```jsx
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)
},
}}
/>
```
#### 分页器样式
```css
/* 分页器中的选择信息样式 */
.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;
}
```
## 🚀 完整使用示例
### 状态管理
```jsx
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])
// ... 处理函数
}
```
### 核心处理函数
```jsx
// 选择变化处理(重要:支持跨页选择)
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()
},
})
}
```
### 页面结构
```jsx
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`),需要手动维护跨页的选中状态:
```jsx
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)
}
```
**工作原理**
1. `currentPageKeys` - 当前页所有数据的 key 列表
2. `otherPagesSelectedKeys` - 从已选项中过滤出不在当前页的 keys即其他页的选择
3. `newSelectedKeys` - 合并其他页的选择 + 当前页的新选择
4. 这样就保持了所有页的选中状态
### 1. 批量操作提示
执行批量操作时,明确提示操作范围:
```jsx
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保持选择状态**(推荐)
```jsx
// 切换分页时,保持已选择的数据
const handlePageChange = (page, size) => {
setCurrentPage(page)
setPageSize(size)
// 不清除 selectedRowKeys保持选择状态
}
```
**策略 B清除选择状态**
```jsx
// 切换分页时,清除选择
const handlePageChange = (page, size) => {
setCurrentPage(page)
setPageSize(size)
handleClearSelection() // 清除选择
}
```
### 3. 后端 API 对接
跨页全选时,后端 API 应支持两种方式:
```jsx
// 方式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
```jsx
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)
}
}
```
## 📝 注意事项
1. **全选状态的一致性**
- 手动勾选/取消勾选时,应取消跨页全选状态
- 确保 `selectedRowKeys``isAllPagesSelected` 状态同步
2. **筛选和搜索**
- 执行筛选或搜索后,应清除选择状态
- 或者保持选择,但提示用户"选择的数据可能不在当前筛选结果中"
3. **数据刷新**
- 刷新数据后,应重新评估选择状态的有效性
- 建议清除选择,或检查已选数据是否仍然存在
4. **权限控制**
- 批量操作应检查权限
- 跨页全选时,应提示可能操作大量数据
## 🎨 样式定制
### ListActionBar 批量操作区样式定制
```css
/* 修改选中信息区域背景色 */
.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](./ListTable.md) - 列表表格组件
- [ListActionBar](./ListActionBar.md) - 列表<E58897><E8A1A8><EFBFBD>作栏组件
- [PageTitleBar](./PageTitleBar.md) - 页面标题栏组件
## 📚 参考资料
- [Ant Design Table 组件](https://ant.design/components/table-cn/)
- [Ant Design Pagination 组件](https://ant.design/components/pagination-cn/)
---
**更新日志**
- v1.2.0 (2025-11-18)
- ♻️ 重构设计:将全选功能集成到操作栏,分页器只显示分页信息
- ✨ 更清晰的职责分离:操作相关在操作栏,分页相关在分页器
- 📝 更新文档和使用示例
- v1.1.0 (2025-11-18)
- ♻️ 重构设计:将全选功能集成到分页器,移除独立的 SelectionAlert 组件
- ✨ 更简洁的交互:选择信息、全选操作、分页控制集中在一个区域
- 📝 更新文档和使用示例
- v1.0.0 (2025-11-18)
- ✨ 新增 SelectionAlert 组件
- ✨ 扩展 ListActionBar 支持批量操作
- ✨ 扩展 ListTable 支持跨页全选状态
- 📝 完善文档和使用示例