在表格类的功能开发时,让用户自定义显示隐藏对应的列功能是比较常见的。
如图:选择下拉框中的列名 实时隐藏对应的列,并支持行为习惯的存储:
使用方法: 这里是个业务页面引用的例子
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用于存储隐藏了哪些列,以便每次刷新保留上一次的列。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。