/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-return */ import * as React from 'react'; import { Input, Icon, Checkbox } from 'cn-next'; import './style/index.scss'; import { Obj, ICnColumn } from 'src/type'; import { useControlValue } from './hooks'; import { debounce, isDef, isFunction, isString } from './util'; interface Iitem extends Obj { label?: string; value?: string; [labelKey: string]: any; children: Iitem[]; } export interface IListFilter { list: Iitem[]; value: string[]; defaultValue: string[]; onChange: (value: string[]) => void; openKeys: string[]; defaultOpenKeys: string[]; onChangeOpenKeys: (value: string[]) => void; labelKey?: string; valueKey?: string; className?: string; searchClassName?: string; style?: React.CSSProperties; searchStyle?: React.CSSProperties; searchTransform?: (str: string) => string; column: ICnColumn; } export default function ListFilter(props: IListFilter) { const { list, labelKey, valueKey, className, style, searchStyle, searchTransform, column, } = props; const [search, setSearch] = React.useState(''); const onChangeSearch = debounce( (val: string) => { setSearch(val); }, 500, false, ); const allValue = React.useMemo(() => getAllValue(list, valueKey), [list]); const filterList = React.useMemo( () => getFilterList( list, isFunction(searchTransform) ? searchTransform(search) : search, labelKey, ), [list, search, labelKey], ); // console.log(allValue, 'allValue'); const [value, setValue] = useControlValue(props as unknown as Obj, { defaultValuePropName: 'defaultValue', valuePropName: 'value', changePropName: 'onChange', defaultValue: [], }); // console.log(value, 'value'); const filterValue = React.useMemo(() => { return ( filterList?.filter((item) => { return value.includes(item.id); }) || [] ); }, [filterList, value]); const [openKeys, setOpenKeys] = useControlValue(props as unknown as Obj, { defaultValuePropName: 'defaultOpenKeys', valuePropName: 'openKeys', changePropName: 'onChangeOpenKeys', defaultValue: [], }); const valueMap = React.useMemo((): any => { return Array.isArray(value) ? value.reduce( (ret, next) => ({ ...ret, [next]: true, }), {}, ) : {}; }, [value]); const openKeysMap = React.useMemo((): any => { return Array.isArray(openKeys) ? openKeys.reduce( (ret, next) => ({ ...ret, [next]: true, }), {}, ) : {}; }, [openKeys]); const onChange = (changeValue: string, checked: boolean) => { const ret = Array.isArray(value) ? [...value] : []; if (checked && !valueMap[changeValue]) { ret.push(changeValue); setValue(ret); } else if (!checked && valueMap[changeValue]) { const valueSet = new Set(ret); valueSet.delete(changeValue); setValue(Array.from(valueSet)); } }; const onChangeOpenKeys = (changeOpenKey: string, isOpen: boolean) => { const ret = Array.isArray(openKeys) ? [...openKeys] : []; if (isOpen && !openKeysMap[changeOpenKey]) { ret.push(changeOpenKey); setOpenKeys(ret); } else if (!isOpen && openKeysMap[changeOpenKey]) { const valueSet = new Set(ret); valueSet.delete(changeOpenKey); setOpenKeys(Array.from(valueSet)); } }; const onCheckAll = (checked: boolean) => { setValue(checked ? [...allValue] : []); }; const onCheckAllFilterValue = (checked: boolean) => { const filterValueList = filterList?.map((item) => item.id); if (checked) { setValue([...value, ...filterValueList]); } else { setValue(allValue.filter((item) => filterList.includes(item))); } }; // @ts-ignore function renderList(list: Iitem[], level: number = 1) { return Array.isArray(list) ? list.map( ( item: Iitem = { children: [], }, index = 1, ) => ( <>
{item?.children?.length > 0 ? ( { onChangeOpenKeys( item?.[valueKey ?? 'value'], !openKeysMap[item?.[valueKey ?? 'value']], ); }} /> ) : null} { onChange(item?.[valueKey ?? 'value'], checked); }} className='cn-list-filter-checkbox' /> {column?.render instanceof Function ? column.render( item?.[column.code ?? column.key ?? labelKey ?? ''], item, level === 1 ? index : -1, ) : isUndefOrNullString(item[labelKey ?? 'label']) ? '空' : item[labelKey ?? 'label']}
{openKeysMap[item?.[valueKey ?? 'value']] && (
{renderList(item?.children, level + 1)}
)} ), ) : null; } return (
} placeholder='search' className='cn-list-filter-search' onChange={onChangeSearch} style={searchStyle} />
{!search ? (
全选({value?.length ?? 0}/{allValue?.length ?? 0})
) : filterList?.length ? (
全选搜索项目({filterValue?.length ?? 0}/{filterList?.length ?? 0})
) : null} {renderList(filterList, 1)}
); } function dfs(list: Iitem[], handlerCallback: (item: Iitem) => void) { Array.isArray(list) && list.forEach((item: Iitem) => { handlerCallback instanceof Function && handlerCallback(item); if (item?.children?.length > 0) { dfs(item?.children, handlerCallback); } }); } // 是否为叶子结点 function isLeafNode(item: Iitem) { return !(item?.children?.length > 0); } function dfsHouxu( list: Iitem[], handlerCallback: (item: Iitem, level: number) => void, level = 1, ) { Array.isArray(list) && list.forEach((item: Iitem) => { if (item?.children?.length > 0) { dfsHouxu(item?.children, handlerCallback, level + 1); } handlerCallback instanceof Function && handlerCallback(item, level); }); } function getAllValue(list: Iitem[], valueKey: string) { const allValue: string[] = []; dfs(list, (item: Iitem) => { if ((valueKey ?? 'value') in item) { allValue.push(item?.[valueKey ?? 'value']); } }); return allValue; } function getNewNode(labelKey: string): Iitem { return { [labelKey ?? 'label']: '', value: '', children: [], }; } function getFilterList( list: Iitem[], search: string, labelKey: string = 'label', ): Iitem[] { if (search === '') { return list; } let nowNodes: Iitem | null = null; let nowNodeLevel = -1; let nowTree: Iitem | null = null; let nowTreeLevel = -1; dfsHouxu(list, (item: Iitem, level) => { if (nowNodeLevel === -1) { // 初始化 nowNodeLevel = level; nowNodes = getNewNode(labelKey); } if (level < nowNodeLevel) { // 增加了一层,就做一层统计 if (nowNodes && nowNodes?.children?.length > 0) { // 当前有节点, 则新建 const newTempNode = { ...item, children: nowNodes?.children, }; nowNodes = { ...getNewNode(labelKey), children: [newTempNode], }; } if (level === nowTreeLevel && nowTree && nowNodes) { // 重新返回 nowTree.children = nowTree.children.concat(nowNodes.children); nowNodes = nowTree; } } else if (level > nowNodeLevel) { // 开始深入下去 // 记录当前start nowTreeLevel = nowNodeLevel; nowTree = nowNodes; // 记录当前end nowNodeLevel = level; nowNodes = getNewNode(labelKey); if ( item && isString(item[labelKey ?? 'label']) && item[labelKey ?? 'label'].includes(search) ) { nowNodes?.children?.push?.(item); } } else if (level === nowNodeLevel) { // 平层,开始compare // @ts-ignore if ( item && isString(item[labelKey ?? 'label']) && item[labelKey ?? 'label'].includes(search) ) { nowNodes?.children?.push?.(item); } } // 同步层次 nowNodeLevel = level; }); // @ts-ignore return nowNodes && nowNodes.children ? nowNodes.children : list; } interface IconProps extends React.SVGProps { height?: number; preserveAspectRatio?: string; title?: string; viewBox?: string; width?: number; xmlns?: string; ref?: any; } function CaretRightIcon(props: IconProps) { return ( ); } function isUndefOrNullString(val: any) { return !isDef(val) || (isString(val) && val.replace(/\s/g, '') === ''); }