背景
需要为List组件自定义全选功能,如下图所示:
- 全选checkbox需要与下面每一项的checkbox联动;
- 当从第一页翻页到第二页的时候,第一页已选的内容保持,可以对第二页勾选,同时保证全选checkbox的状态是正确的。
实现
组件一 SelectAllCheckbox.tsx:
import { Checkbox } from 'antd';
import { t } from 'i18next';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import TableSelectedDes from '../TableSelectedDes';// rowId必填,跟selectedRowKeys一致
export type CheckBoxItem = { rowId: number } & Record<string, any>;interface Props {data: CheckBoxItem[]; // 当前页的数据limit?: number; // 当前页的page size,默认为20callbackSelectedRowKeys: (values: number[]) => void;
}// 根据当前页的数据,判断当前页已经被选中的项
export const getCurrentCheckedList = (allKeys: number[], currentPageData: CheckBoxItem[]) => {const current: number[] = [];allKeys?.forEach((checkedId) => {currentPageData?.forEach((c: CheckBoxItem) => {if (c.rowId === checkedId) {current.push(checkedId);}});});return current;
};function SelectAllCheckbox(props: Props, ref: any) {const { data, limit = 20, callbackSelectedRowKeys } = props;const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);const [indeterminate, setIndeterminate] = useState(false);const [checkAll, setCheckAll] = useState(false);// 单个checkbox被点击的时候const handleClickItemCheckbox = (isChecked: boolean, clickedId: number) => {let keys;if (isChecked) {keys = selectedRowKeys.filter((key) => key !== clickedId);} else {keys = [...selectedRowKeys, clickedId];}setSelectedRowKeys(keys);const current = getCurrentCheckedList(keys, data);const curLimit = data?.length < limit ? data?.length : limit;setCheckAll(current?.length === curLimit);setIndeterminate(!!current.length && current.length < curLimit);};// 全选checkbox被点击const handleClickAllCheckbox = () => {if (!checkAll) {setSelectedRowKeys(Array.from(new Set([...selectedRowKeys, ...(data?.map((item: CheckBoxItem) => item.rowId) || [])])),);} else {// 比如取消选择第二页的数据,但是还需要保留第一页已选择的数据setSelectedRowKeys(selectedRowKeys.filter((key) => !data?.map((item) => item.rowId)?.includes(key)));}setCheckAll(!checkAll);setIndeterminate(false);};// 取消选择const cancelSelectedAll = () => {setSelectedRowKeys([]);setIndeterminate(false);setCheckAll(false);};// 当切换页数的时候,可能需要重置当前页的全选checkbox状态const handleSetCheckAllStatus = (list: CheckBoxItem[], curLimit?: number) => {const neeLimit = curLimit || limit;const currentPageCheckedList = getCurrentCheckedList(selectedRowKeys, list);setCheckAll(currentPageCheckedList?.length === neeLimit);setIndeterminate(!!currentPageCheckedList.length && currentPageCheckedList.length < neeLimit);};// 父组件可能存在批量操作之类的,比如批量删除所选项之后,需要重新更新selectedRowKeysconst handleUpdateSelectedRowKeys = (keys: number[]) => {setSelectedRowKeys(keys);};useImperativeHandle(ref, () => ({handleClickItemCheckbox,handleSetCheckAllStatus,handleUpdateSelectedRowKeys,}));useEffect(() => {callbackSelectedRowKeys(selectedRowKeys);}, [selectedRowKeys]);return (<><Checkboxstyle={{ marginRight: 5, alignSelf: 'center' }}indeterminate={indeterminate}checked={checkAll}onChange={handleClickAllCheckbox}>{t('shark-select-all')}</Checkbox>{selectedRowKeys?.length > 0 && (<TableSelectedDes selectedRowKeys={selectedRowKeys} cancelSelected={cancelSelectedAll} />)}</>);
}/** 全选Checkbox组件* 1. 支持全选、取消选择* 2. 支持数据翻页时,checkbox正常联动*/
export default forwardRef(SelectAllCheckbox);
组件二 TableSelectedDes.tsx:
import { Button, Space } from 'antd';
import { t } from 'i18next';type Props = {selectedRowKeys: React.Key[];cancelSelected: () => void;
};export default function TableSelectedDes(props: Props) {const { selectedRowKeys = [], cancelSelected } = props;return (<Space size="middle"><p style={{ marginBottom: 0, fontSize: 12 }}>{t('shark-choosed')} {selectedRowKeys?.length} {t('shark-items')}</p><Button style={{ marginLeft: -22, fontSize: 12 }} type="link" onClick={cancelSelected}>{t('shark-deselect')}</Button></Space>);
}
使用
// 请求数据const getMessageData = async () => {try {// const res = await xxx;if (!res.errmsg) {const newData = res.result.data?.map((item: MessageItem) => ({ ...item, rowId: item.id })); // 关键点,设置rowIdsetData(newData);setTotal(res.result.total);// 关键点// 当翻页时,初始化【全选】选项listBatchRowRef.current?.handleSetCheckAllStatus(newData);}} catch (e) {console.log(e);}};const handleClickItemCheckbox = (isChecked: boolean, clickedId: number) => {listBatchRowRef.current?.handleClickItemCheckbox(isChecked, clickedId);};const renderListItem = (item: MessageItem) => {const { sender, create_time, title, message_status } = item;const time = moment(create_time).format('YYYY-MM-DD HH:mm:ss');const description = (<Row>{time}<span style={{ marginLeft: 10 }}>{sender}</span></Row>);const isChecked = selectedRowKeys?.includes(item.rowId);return (<List.Itemactions={[<Button type="link" style={{ padding: 0 }} onClick={() => handleDetail(item)}>{t('shark-detail')}</Button>,<Button type="link" danger style={{ padding: 0 }} onClick={() => handleDelete(item)}>{t('shark-delete')}</Button>,]}>// 单个项的checkbox框<Checkboxstyle={{ marginRight: 10 }}onChange={() => handleClickItemCheckbox(isChecked, item.rowId)} // 关键点,单个项的点击checked={isChecked}onClick={(e) => e.stopPropagation()} // 阻止点击复选框时,促使折叠面板展开/折叠/><List.Item.Metatitle={<span style={{ color: message_status === MessageType.READ ? 'rgba(0, 0, 0, 0.45)' : '#000' }}>{title}</span>}description={description}/></List.Item>);};// 引用组件
<SelectAllCheckboxdata={data as unknown as CheckBoxItem[]}callbackSelectedRowKeys={setSelectedRowKeys}ref={selectAllCheckboxRef}
/>{/* 消息列表 */}
<ListitemLayout="horizontal"dataSource={data}rowKey="id"pagination={{onChange: (page) => setSearchParams({ ...searchParams, page }),pageSize: 20,current: searchParams.page,total,showTotal: () => `Total ${total} items`,}}loading={loading}className="message-tab-list-container"renderItem={renderListItem}
/>