react加antd封装表格单、多选组件,支持跨页选择缓存

发布于:2025-03-16 ⋅ 阅读:(23) ⋅ 点赞:(0)

页面效果

子组件

import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { Modal, Input, Table, Pagination, Avatar, Select } from 'antd';
import { UserOutlined } from '@ant-design/icons';
import type { TableProps, PaginationProps } from 'antd';

// 用户接口
interface User {
    id: string;
    name: string;
    avatar: string;
}


// 组件属性接口
interface UserSelectorProps<T> {
    open: boolean;
    mode: 'single' | 'multiple';
    dataSource: T[];
    total: number;
    onCancel: () => void;
    onOk: (selectedItems: T[]) => void;
    onSearch: (searchText: string) => void;
    onPageChange: (page: number, pageSize: number) => void;
}

// 暴露给父组件的接口
export interface UserSelectorRef<T> {
    setSelectedItems: (items: T[]) => void;
}

// 主函数
const UserSelector = forwardRef<UserSelectorRef<User>, UserSelectorProps<User>>(
    (
        {
            open,
            onCancel,
            onOk,
            mode,
            dataSource,
            total,
            onSearch,
            onPageChange,
        },
        ref,
    ) => {
         当前选中项
        const [selectedItems, setSelectedItems] = useState<User[]>([]);

        // 暴露方法给父组件
        useImperativeHandle(ref, () => ({
            setSelectedItems: (items: User[]) => {
                setSelectedItems(items);
            },
        }));

        / 搜索框
        const [searchText, setSearchText] = useState('');

        // 处理搜索框变化
        const handleSearch = (value: string) => {
            setSearchText(value);
            onSearch(value);
        };

         对话框关闭时重置选中和搜索框
        useEffect(() => {
            if (!open) {
                setSelectedItems([]);
                setSearchText('');
            }
        }, [open]);


         分页
        const [currentPage, setCurrentPage] = useState(1);
        const [pageSize, setPageSize] = useState(5);

        // 分页改变
        const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => {
            setCurrentPage(page);
            setPageSize(pageSize);
            onPageChange(page, pageSize);
        };

        / 表格
        // 列配置对象
        const columns: TableProps<User>['columns'] = [
            {
                key: 'avatar',
                render: (_, record) => (
                    <div style={{ display: 'flex', alignItems: 'center' }}>
                        {record.avatar ? (<Avatar
                            src={record.avatar}
                        />) : (<Avatar
                            icon={<UserOutlined />} // 如果头像为空,显示默认图标
                        />)}


                        <span style={{ marginLeft: 8 }}>{record.name}</span>
                    </div>
                ),
            },
        ];

        // 表格行点击事件
        const handleRowClick = (record: User) => {
            if (mode === 'single') {
                onOk([record]);
                onCancel()
            } else {
                // 判断该行是否已经选择
                const isSelected = selectedItems.some(item => item.id === record.id);
                if (isSelected) {
                    // 已选择,进行删除操作
                    setSelectedItems(selectedItems.filter(item => item.id !== record.id));
                } else {
                    // 未选择,进行添加
                    setSelectedItems([...selectedItems, record]);
                }
            }
        };

        // 表格全选,全取消
        const handleSelectAll = (selected: boolean) => {
            if (selected) {
                // 全选时,将当前页未选择的用户追加到已选用户列表
                const newSelectedItems = [
                    ...selectedItems,
                    ...dataSource.filter(
                        item => !selectedItems.some(selectedItem => selectedItem.id === item.id),
                    ),
                ];
                setSelectedItems(newSelectedItems);
            } else {
                // 取消全选时,移除当前页的用户
                const newSelectedItems = selectedItems.filter(
                    item => !dataSource.some(row => row.id === item.id),
                );
                setSelectedItems(newSelectedItems);
            }
        };

         多选框

        // 监听多选框的改变
        const handleSelectChange = (values: string[]) => {
            const newSelectedItems = selectedItems.filter(item => values.includes(item.id));
            setSelectedItems(newSelectedItems);
        };

        return (
            <Modal
                title="Select"
                open={open}
                onCancel={onCancel}
                onOk={() => onOk(selectedItems)}
                width={600}
            >
                <Input.Search
                    size="large"
                    allowClear
                    placeholder="search"
                    value={searchText}
                    onChange={e => handleSearch(e.target.value)}
                    style={{ marginBottom: 16 }}
                />
                {mode === 'multiple' && (
                    <div style={{ display: 'flex', alignItems: 'center', marginBottom: 16 }}>
                        <Select
                            size="large"
                            mode="multiple"
                            allowClear
                            value={selectedItems.map(item => item.id)}
                            onChange={handleSelectChange}
                            style={{ width: '100%' }}
                            placeholder="select"
                            optionLabelProp="label"
                            options={selectedItems.map(item => ({
                                value: item.id,
                                label: (
                                    <div style={{ display: 'flex', alignItems: 'center', }}>
                                        {item.avatar ? (<Avatar
                                            src={item.avatar}
                                        />) : (<Avatar
                                            icon={<UserOutlined />} // 如果头像为空,显示默认图标
                                        />)}
                                        {item.name}
                                    </div>
                                ),
                            }))}
                        />
                    </div>
                )}
                <Table
                    dataSource={dataSource}
                    columns={columns}
                    rowKey="id"
                    pagination={false}
                    onRow={record => ({
                        onClick: () => handleRowClick(record),
                    })}
                    rowSelection={
                        mode === 'multiple'
                            ? {
                                selectedRowKeys: selectedItems.map(item => item.id),
                                onSelect: (record, selected, selectedRows, nativeEvent) => {
                                    handleRowClick(record)
                                },
                                onSelectAll: (selected, selectedRows, changeRows) => {
                                    handleSelectAll(selected);
                                },
                            }
                            : undefined
                    }
                />
                <Pagination
                    {
                    ...{
                        pageSize: pageSize,
                        current: currentPage,
                        showTotal: (total, range) => `${range[0]}-${range[1]} of ${total} items`,
                        showSizeChanger: true,
                        pageSizeOptions: ['5', '10', '20', '30'],
                        onChange: handlePageChange,
                        total: total
                    }
                    }

                    style={{ marginTop: 16 }}
                />
            </Modal>
        );
    },
);

export default UserSelector;

父组件

import React, { useState, useRef } from 'react';
import UserSelector, { UserSelectorRef } from './UserSelector2';
import { Button, Avatar } from 'antd';

interface User {
  id: string;
  name: string;
  avatar: string;
}


const App: React.FC = () => {
  const [open, setOpen] = useState(false);
  const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
  const userSelectorRef = useRef<UserSelectorRef<User>>(null);

  const [mockUsers, setMockUsers] = useState<User[]>([
    { id: '1', name: '用户1', avatar: '1' },
    { id: '2', name: '用户2', avatar: 'https://example.com/avatar2.png' },
    { id: '3', name: '用户3', avatar: '1' },
    { id: '4', name: '用户4', avatar: 'https://example.com/avatar2.png' },
    { id: '5', name: '用户5', avatar: '' },
  ]);

  const handleSearch = (searchText: string) => {
    console.log('Search:', searchText);
  };

  const handlePageChange = (page: number, pageSize: number) => {

    if (page === 1) {
      setMockUsers([
        { id: '1', name: '用户1', avatar: '1' },
        { id: '2', name: '用户2', avatar: 'https://example.com/avatar2.png' },
        { id: '3', name: '用户3', avatar: '1' },
        { id: '4', name: '用户4', avatar: 'https://example.com/avatar2.png' },
        { id: '5', name: '用户5', avatar: '1' },
      ])
    } else {
      setMockUsers([
        { id: '6', name: '用户6', avatar: 'https://example.com/avatar2.png' },
        { id: '7', name: '用户7', avatar: '1' },
        { id: '8', name: '用户8', avatar: 'https://example.com/avatar2.png' },
        { id: '9', name: '用户9', avatar: '1' },
        { id: '10', name: '用户10', avatar: 'https://example.com/avatar2.png' },
      ])
    }
  };

  const handleSetSelectedUsers = () => {
    // 父组件调用子组件的方法,设置选中用户
    if (userSelectorRef.current) {
      userSelectorRef.current.setSelectedItems([
        { id: '1', name: '用户1', avatar: 'https://example.com/avatar1.png' },
      ]);
    }
  };

  return (
    <div>
      <Button type="primary" onClick={() => setOpen(true)} style={{ marginRight: 8 }}>
        选择用户
      </Button>
      <Button onClick={handleSetSelectedUsers}>设置选中用户</Button>
      <UserSelector
        ref={userSelectorRef}
        open={open} // 使用 open 替代 visible
        onCancel={() => setOpen(false)}
        onOk={setSelectedUsers}
        mode="multiple"
        dataSource={mockUsers}
        total={10}
        onSearch={handleSearch}
        onPageChange={handlePageChange}
      />
      <div>
        <h3>已选用户:</h3>
        {selectedUsers.map(user => (
          <div key={user.id}>
            <Avatar src={user.avatar} />
            <span>{user.name}</span>
          </div>
        ))}
      </div>
    </div>
  );
};

export default App;