import React, { useMemo, useState, useCallback, useEffect, useRef, useImperativeHandle, } from 'react'; import classNames from 'classnames'; import ConfigProvider from 'antd/es/config-provider'; import locale from 'antd/es/locale/zh_CN'; import Input, { InputProps } from 'antd/es/input'; import 'antd/es/input/style/index'; import Select from 'antd/es/select'; import 'antd/es/select/style/index'; import Button from 'antd/es/button'; import 'antd/es/button/style/index'; import Cascader, { CascaderValueType, CascaderOptionType, CascaderProps } from 'antd/es/cascader'; import 'antd/es/cascader/style/index'; import DatePicker from 'antd/es/date-picker'; import { PickerProps, PickerDateProps, RangePickerProps } from 'antd/es/date-picker/generatePicker'; import 'antd/es/date-picker/style/index'; import { checkType } from '@jy-fe/utils'; import { XuiReactFilterIcon } from '@jy-fe/icons'; import { SearchHeadHandles, SearchHeadProps, NodeType, ValidatorMapType, } from './xui-search-head.d'; const XuiSearchHead: React.ForwardRefRenderFunction = ( { searchNode, defaultValues = {}, onSearch, hideResetButton = false }, ref, ) => { const className = 'xui-ant__search-head'; const searchheadEl = useRef(null); /** 渲染的数据 */ const [state, setState] = useState({} as any); /** 真正筛选的数据 */ const [searchState, setSearchState] = useState({} as any); /** 外部input焦点控制 */ const [visible, setVisible] = useState(false); /** 获取校验集 */ const validatorMap = useMemo(() => { const map: ValidatorMapType = {}; searchNode.forEach((node: NodeType) => { const { key, validator } = node; if (validator) { map[key] = validator; } }); return map; }, [searchNode, visible]); /** 解析keys,rangeInput keys */ const analysis = useMemo(() => { const keys: string[] = []; const outerKeys: string[] = []; const innerKeys: string[] = []; const rangeKeys: string[] = []; searchNode.forEach((node: NodeType) => { const { key, hidden, type } = node; keys.push(key); if (hidden) { innerKeys.push(key); } else { outerKeys.push(key); } if (type === 'rangeInput') { rangeKeys.push(key); } }); return { keys, outerKeys, innerKeys, rangeKeys, }; }, [searchNode]); /** 改变渲染的数据 */ const changeState = useCallback( (newState: any, callback?: () => void) => { setState(newState); if (callback) { callback(); } }, [searchNode, state, analysis, visible], ); const changeVisible = useCallback( (newVisible: boolean) => { if (newVisible) { setState(searchState); } setVisible(newVisible); }, [visible, searchState], ); /** 查找 */ const search = useCallback( (newState?: any) => { let flag = true; const currentState = newState || state; const validatorKeys = Object.keys(validatorMap); for (let i = 0; i < validatorKeys.length; i++) { const f = validatorMap[validatorKeys[i]]; if (f) { const { rangeKeys } = analysis; const currentKey = validatorKeys[i]; const callbackParam: { [key: string]: any } = {}; if (rangeKeys.includes(currentKey)) { callbackParam[`${currentKey}Low`] = currentState[`${currentKey}Low`]; callbackParam[`${currentKey}High`] = currentState[`${currentKey}High`]; } else { callbackParam[currentKey] = currentState[currentKey]; } const result = f(callbackParam); if (!result) { flag = false; break; } } } if (flag) { let params = searchState; const { keys } = analysis; Object.keys(currentState).forEach((itemKey: string) => { if ( keys.includes(itemKey) || (itemKey.includes('Low') && keys.includes(itemKey.replace('Low', ''))) || (itemKey.includes('High') && keys.includes(itemKey.replace('High', ''))) ) { const value = currentState[itemKey]; if (value || value === 0) { params = { ...params, [itemKey]: currentState[itemKey], }; } else { delete params[itemKey]; } } }); const resultParams = {}; Object.keys(params).forEach(pKey => { const pValue = params[pKey]; if ( !( (!pValue && pValue !== 0) || (checkType(pValue) === 'Array' && pValue.length === 0) || (checkType(pValue) === 'Object' && Object.keys(pValue).length === 0) ) ) { resultParams[pKey] = pValue; } }); setSearchState(resultParams); onSearch(resultParams, 'search'); changeVisible(false); } }, [state, searchState, searchNode, visible, analysis], ); const dropdownClassName = `${className}--dropdown`; const classNamePopoverContentCol = `${className}--col`; const classNamePopoverContentColTitle = `${className}--col--title`; const classNamePopoverContentColContent = `${className}--col--content`; /** 渲染节点 */ const renderNode = useMemo(() => { const outerNode: (JSX.Element | null)[] = []; const innerNode: (JSX.Element | null)[] = []; searchNode.forEach((node: NodeType) => { let dom: any = null; const { hidden, type, label, width, options = [], cascaderOptionMap = [], key, placeholder, extraProps, placeholders, getPopupContainer = () => document.body, } = node; if (type === 'input') { const props: InputProps = { className: classNamePopoverContentColContent, value: state[key], allowClear: true, placeholder, onChange: (e: React.ChangeEvent) => { const { value } = e.target; const newState = { ...state, [key]: value || undefined, }; changeState(newState); }, onPressEnter: () => search(), ...extraProps, }; if (!visible && width) { props.style = { width, }; } dom = (
{label}
); } else if (type === 'select' || type === 'multiple') { const props = { className: classNamePopoverContentColContent, dropdownClassName, showArrow: true, value: state[key], placeholder, getPopupContainer, allowClear: true, onChange: (value: string) => { const newState = { ...state, [key]: value, }; changeState(newState); }, ...extraProps, } as any; if (type === 'multiple') { props.mode = 'multiple'; } if (!visible && width) { props.style = { width, }; } dom = (
{label}
); } else if (type === 'rangeInput') { const lowProps: InputProps = { style: { width: 88 }, placeholder: placeholders ? placeholders[0] : '', value: state[`${key}Low`], allowClear: true, onChange: (e: React.ChangeEvent) => { const { value } = e.target; const newState = { ...state, [`${key}Low`]: value, }; changeState(newState); }, onPressEnter: () => search(), ...extraProps, }; const highProps: InputProps = { style: { width: 88 }, placeholder: placeholders ? placeholders[1] : '', value: state[`${key}High`], allowClear: true, onChange: (e: React.ChangeEvent) => { const { value } = e.target; const newState = { ...state, [`${key}High`]: value, }; changeState(newState); }, onPressEnter: () => search(), ...extraProps, }; if (!visible && width) { lowProps.style = { width, }; highProps.style = { width, }; } dom = (
{label}
); } else if (type === 'cascader') { const props: CascaderProps = { popupClassName: dropdownClassName, options: cascaderOptionMap, placeholder, getPopupContainer, value: state[key], style: { width: '100%' }, onChange: (value: CascaderValueType) => { const newState = { ...state, [key]: value, }; setState(newState); }, showSearch: { matchInputWidth: false, filter: (inputValue, path: CascaderOptionType[]) => path.some((option: CascaderOptionType) => { if (option && option.label && typeof option.label === 'string') { return option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1; } return false; }), }, ...extraProps, }; if (!visible && width) { props.style = { width, }; } dom = (
{label}
); } else if (type === 'datePicker') { const props: PickerProps = { dropdownClassName, placeholder, getPopupContainer, value: state[key], style: { width: '100%' }, onChange: value => { const newState = { ...state, [key]: value, }; setState(newState); }, ...extraProps, }; if (!visible && width) { props.style = { width, }; } dom = (
{label}
); } else if (type === 'monthPicker') { const props: PickerDateProps = { dropdownClassName, placeholder, getPopupContainer, value: state[key], style: { width: '100%' }, onChange: value => { const newState = { ...state, [key]: value, }; setState(newState); }, ...extraProps, }; if (!visible && width) { props.style = { width, }; } dom = (
{label}
); } else if (type === 'rangePicker') { const props: RangePickerProps = { dropdownClassName, placeholder: placeholders, getPopupContainer, value: state[key], style: { width: '100%' }, onChange: value => { const newState = { ...state, [key]: value, }; setState(newState); }, suffixIcon: null, ...extraProps, }; dom = (
{label}
); } if (hidden) { innerNode.push(dom); } else { outerNode.push(dom); } }); return { outerNode, innerNode, }; }, [searchNode, state, visible]); /** 重置 */ const reset = useCallback(() => { setState({}); setSearchState({}); onSearch({}, 'reset'); }, [searchState, analysis]); /** 判断是否在组件内 */ const contains = useCallback((e: MouseEvent) => { let ownFlag = false; let dropdownFloag = false; if (searchheadEl.current) { ownFlag = (searchheadEl.current as any).contains(e.target); } const dropdownDom = document.querySelectorAll(`.${dropdownClassName}`); dropdownDom.forEach((element: any) => { if (element.contains(e.target)) { dropdownFloag = true; } }); if (!(ownFlag || dropdownFloag)) { setVisible(false); } }, []); useImperativeHandle(ref, () => ({ clear: () => { setState({}); setSearchState({}); onSearch({}, 'reset'); }, resetState: (paramNameList: string[]) => { const paramObjNeedClear = Object.assign( {}, ...paramNameList.map(item => ({ [item]: undefined })), ); setState((pre: { [key: string]: any }) => ({ ...pre, ...paramObjNeedClear, })); setSearchState((pre: { [key: string]: any }) => ({ ...pre, ...paramObjNeedClear, })); }, })); useEffect(() => { if (defaultValues) { setState(defaultValues); setSearchState(defaultValues); } }, [defaultValues]); useEffect(() => { window.addEventListener('click', contains); return () => { window.removeEventListener('click', contains); }; }, []); return (
{renderNode.outerNode} {analysis.innerKeys.length > 0 && (
setVisible(!visible)} >
)}
{!hideResetButton && }
{renderNode.outerNode} {renderNode.innerNode}
{!hideResetButton && }
); }; export default React.forwardRef(XuiSearchHead);