在表格类的功能开发时,让用户自定义显示隐藏对应的列功能是比较常见的。

如图:选择下拉框中的列名 实时隐藏对应的列,并支持行为习惯的存储:
image.png

使用方法: 这里是个业务页面引用的例子
ProductList.tsx

    const tableSelectRef = useRef<HTMLDivElement>();
    const tableComponent = useMemo(() => {

        return (
            <BasicTable
                columns={ columns }
                fetchDataSource={ fetchDataSource }
                sticky={{
                    offsetHeader: 70
                }}
                showColumns={{ key: TYPE.EUserSetting.XXXX_SHOWCOLUMNS_CLEDATA,
                    selectContainerDom: tableSelectRef.current! }}
                freshCurrentTableFlag={ refreshCurrentTableFlag }
                bordered={ true }
                rowKey={ (record) => record.id }
            >
            </BasicTable>
        );
    }, [ columns, fetchDataSource, refreshCurrentTableFlag]);

这块的tableSelectRef是上面下拉框的一个占位,把这个占位的DOM元素传递下去
公共的表格组件,对antd表格的二次封装,同时也额外对自己维护的动态列进行了处理(这部分可以略过),和antd表格差不多,大致思路也是一样的。
CompTable .tsx

export interface IShowColumns {
    key: TYPE.EUserSetting;
    selectContainerDom: HTMLElement | null;
    width?: number;
    excludeColumns?: string[];
}
export const CompTable = function<T extends object>(
    props: ITableProps<T>,
): JSX.Element {
const showColumnsProps = props.showColumns;

    const [showColumnsFields, setShowColumnsFields] = useState<string[]>([]);
    const tableColumns = useMemo(() => {
        if(showColumnsFields.length === 0) { //初始为空时全选初始的列
            return filterColumns;
        }
        const columns = filterColumns.filter(col=>{
            if(showColumnsProps?.excludeColumns?.includes(col.alias ?? col.dataIndex as string)) {
                //excludeColumns排除在可隐藏列之外的列必定显示,不出现在下拉框中
                return true;
            }
            return  showColumnsFields.includes(col.alias ?? col.dataIndex as string);
        });
        return columns;
    }, [showColumnsFields, showColumnsProps, filterColumns]);

    const initialColumnsConfig = useMemo(()=>{
        let columnsConfig: Array<ITableParseColumns<T>> = [];
        let groupColumns: Array<ITableParseColumns<T>>  = [];
        let groupColumnsIndex: number = 0;
        let isExistGroupColumns: boolean = false;
        if (typeof columns === 'function') {
            columnsConfig = columns({}, []);
        }
        else {
            columnsConfig = [...columns];
        }
        columnsConfig.forEach((col: ITableParseColumns<T>, idx: number) => {
            if(col.isGroupColumn) { //是否动态列
                groupColumnsIndex = idx;
                isExistGroupColumns = true;
                groupColumns = col.getGroupColumns?.(new Array(col.groupLength).fill('')) ?? [];
            }
        });
        if(isExistGroupColumns) {
            columnsConfig.splice(groupColumnsIndex, 1, ...groupColumns);
        }
        return columnsConfig;
    }, [ columns]);

   const handleHideColumnsSelectChange: (data: string[]) => void  = useCallback((data)=> {
        setShowColumnsFields(data);
    }, []);


    return (
        <>
            <Table
            dataSource={ dataSource ?? list }
            columns={ tableColumns }
                ...
            >
            </Table>
            {
                showColumnsProps?.selectContainerDom ? reactDOM.createPortal(
                    <CompShowColumnsSelect
                        onChange={ handleHideColumnsSelectChange }
                        tableColumns={  initialColumnsConfig }
                        tableKey={ showColumnsProps.key }
                        showFields={ showColumnsFields }
                        { ...showColumnsProps }
                    />,
                    showColumnsProps.selectContainerDom
                ) : null

            }
        </>

    );
};

使用上面表格传入的selectContainerDom的Ref进行createPortal把这个下拉框组件插入到对应表格组件的上面

下拉框单独抽离为组件:
CompShowColumnsSelect.tsx

import React, {  useCallback, useEffect, useMemo, useRef } from 'react';
import { CompSelect } from '@/basic/CompSelect';
import * as TYPE from '@/interface';
import  userService  from '@/lib/User';
import { ITableParseColumns } from '@/components/common/CompTableParser';
import message from '@/libs/Message';
import { Row, Col } from 'antd';

//下拉框显示隐藏表格列
export interface ICompShowColumnsSelect<T>{
    tableKey: TYPE.EUserSetting;
    onChange: (value: string[]) => void;
    excludeColumns?: string[];
    width?: number;
    showFields: IShowColumns;
    selectContainerDom: HTMLElement | null;
}
export type IShowColumns = string[];
export const CompShowColumnsSelect =  function<T extends object>(props: ICompShowColumnsSelect<T> ): JSX.Element {

    const onChange = props.onChange;
    const columns = props.tableColumns;
    const tableKey = props.tableKey;
    const showFields = props.showFields;
    const width = props.width;
    const excludeColumns = props.excludeColumns;

    const showColRef = useRef<IShowColumns>([]);

    const handleChange = useCallback(value => {
        if(value.length === 0) {
            onChange([...showFields]);
            message.warning('不能隐藏所有列!');
            return;
        }
        showColRef.current = value;
        onChange(value);

    }, [onChange, showFields]);

    const beforeunload: () => void = useCallback(() => {
        userService.setUserSettings(tableKey,  showColRef.current).catch(console.error);
    }, [tableKey]);
    useEffect(()=>{
        window.addEventListener('beforeunload', beforeunload);
        userService.getUserSettings([tableKey]).then((data) =>{
            const showColumns = data[tableKey];
            if(showColumns instanceof Array && showColumns.length > 0) {
                showColRef.current = showColumns;
                onChange(showColumns); //初始选中
            }
            else{
                onChange(
                    columns.map(col => col.alias ?? col.dataIndex as string).filter(colKey => {

                        return excludeColumns ? !excludeColumns.includes(colKey) : true;
                    })
                ); //全选,但不包含排除的列
            }
        }).catch(console.error);
        return ()=>{
            window.removeEventListener('beforeunload', beforeunload);
            userService.setUserSettings(tableKey,  showColRef.current).catch(console.error);
        };
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    const optionsSelect = useMemo(()=>{
        let options = columns.map(i => ({label: i.title, value: (i.alias ?? i.dataIndex) as string}));
        if(excludeColumns) {
            options =  options.filter(op => !excludeColumns.includes(op.value)? true : false);
        }
        return options;
    }, [columns, excludeColumns]);

    const handle: () => HTMLElement   = () => props.selectContainerDom as HTMLElement;

    return (
        <div
            className='CompHideColumnsSelect'
            style={{ width: width ?? 500 }}
        >
            <Row>
                <Col><span style={{ lineHeight: '32px' }}>请选择要显示的列:</span></Col>
                <Col span={ 16 }>
                    <CompSelect
                        showSearch={ true }
                        placeholder='请选择要显示的列'
                        optionFilterProp='children'
                        selectableAll={ true }
                        mode='multiple'
                        getPopupContainer={ handle }
                        value={ showFields }
                        style={{ width: '100%' }}
                        onChange={ handleChange }
                        options={ optionsSelect }
                    >
                    </CompSelect>
                </Col>
            </Row>

        </div>
    );
};

CompSelect组件其实也是Antd 的Select组件二次封装。

其中的userService是调取接口存储用户行为习惯,就像是给后端新增一条记录,通过当前表格的唯一id(tableKey)。这里可以改成浏览器Storage进行存储也是可以的
使用window beforeunload 是在浏览器页面关闭前对行为习惯的存储。

excludeColumns 是需要业务中排除某些列,不参与到显示隐藏中,也就是用户不可以调整某些列的显示隐藏

export enum EUserSetting {

    //CompShowColumnsSelect
    XXX_SHOWCOLUMNS_USERINFOLIST = 'userInfoList',
    略...
}

系统中使用到隐藏列的表格都要定义一个唯一id用于存储隐藏了哪些列,以便每次刷新保留上一次的列。


洛阳醉长安行
57 声望3 粉丝

charging...