index.tsx 8.4 KB
import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
import { Space, Button, Tooltip, Tag } from 'antd';
import './index.less';
import { FormProvider, VoidField } from '@formily/react';
import { createForm, onFieldValueChange, Field, FormPath } from '@formily/core';
import { ossAddress } from '@/utils/ossAddress';
import { useDebounce } from '@/utils/format';
import moment from 'moment';

// Avoid magic numbers
const MAX_COMPONENTS = 6;
const DEBOUNCE_DELAY = 800;

export interface SearchWrapProps {
  leftSectionComponents?: ReactElement[];
  searchInput?: ReactElement;
  popupContent?: ReactElement[];
  searchChange?: Function;
  getFormInstance?: Function;
  moreCategories?: Boolean;
  ProForm?: any;
}
type FormPathPattern =
  | string
  | number
  | Array<string | number>
  | FormPath
  | RegExp
  | (((address: Array<string | number>) => boolean) & {
      path: FormPath;
    });
interface SearchChooseProps {
  name: string;
  title: string;
  value: string;
  path: FormPathPattern;
  address: any;
}

const getValueLable = ({
  title,
  value,
  dataSource,
  props,
  path,
  address,
  componentProps,
}: Field) => {
  const { name } = props;

  const { componenttypename, format } = componentProps;
  const labelMap: any = {
    name,
    path,
    address,
    title: title ? title : '',
  };

  const getValue = (key: any) => {
    const dataSourceMap: Record<string, string> = {};
    dataSource.map((item) => {
      if (item && item.value) {
        dataSourceMap[item.value] = item.label;
      }
    });
    return dataSourceMap[key];
  };

  switch (componenttypename) {
    case 'Radio.Group':
      labelMap.value = getValue(value);
      break;
    case 'Select':
      console.log(value, 'selectValue');

    case 'Checkbox.Group':
      const valArray: any[] = [];
      if (value && value.length > 0 && typeof value != 'string') {
        value.map((val: any) => {
          valArray.push(getValue(val));
        });
        labelMap.value = valArray.join(',');
      } else {
        labelMap.value = getValue(value);
      }
      break;
    case 'Cascader':
      break;
    case 'DatePicker':
    case 'MapSelect':
    case 'InputNumber':
    case 'Input':
    case 'Input.TextArea':
    case 'UploadFiles':
      labelMap.value = value;
      break;
    case 'DatePicker.RangePicker':
      if (value && value.length > 0) {
        const start = value[0] ? moment(value[0] * 1000).format(format[0]) : '';
        const end = value[1] ? moment(value[1] * 1000).format(format[1]) : '';
        labelMap.value = `${start}~${end}`;
      } else {
        labelMap.value = undefined;
      }
      break;
    default:
      labelMap.value = getValue(value);
  }
  return labelMap;
};

const getPopupValuesLabel = (field: any, searchLabels: SearchChooseProps[]) => {
  const { props } = field;
  const { name } = props;

  const searchLableMap: any = {};
  const changeLabel = getValueLable(field);
  if (searchLabels.length > 0) {
    searchLabels.map((label) => {
      if (name == label.name) {
        if (changeLabel && changeLabel.value) {
          searchLableMap[label.name] = changeLabel;
        } else {
          delete searchLableMap[label.name];
        }
      } else {
        searchLableMap[label.name] = label;
      }
    });
  }
  if (changeLabel && changeLabel.value) {
    searchLableMap[name] = changeLabel;
  }
  return searchLableMap;
}

const SearchWrap: React.FC<SearchWrapProps> = (props) => {
  const {
    leftSectionComponents = [],
    searchInput,
    popupContent,
    searchChange,
    moreCategories = false,
    ProForm,
    getFormInstance,
  } = props;
  const [isPopupVisible, setPopupVisible] = useState(false);
  const [open, setOpen] = useState(false);
  const [searchLabels, setSearchLabels] = useState<SearchChooseProps[]>([]);
  const [showPopupValue, setShowPopupValue] = useState<boolean>(false);
  const [centerWidth, setCenterWidth] = useState<number>(0);
  const valueChange = useDebounce((value: any) => {
    searchChange && searchChange(value);
  }, DEBOUNCE_DELAY);

  const form = ProForm ? ProForm : useMemo(() => createForm(), []);

  useEffect(() => {
    if (searchLabels && searchLabels.length === 0) {
      setShowPopupValue(false);
    }
  }, [searchLabels]);

  useEffect(() => {
    const removeEffects = form.addEffects('popup_search', () => {
      onFieldValueChange('*', (field) => {
        const {
          address: { entire = '' },
        } = field;
        if ((entire as string).indexOf('popup') === -1) {
          searchChange && valueChange(form.values);
        }
        if ((entire as string).indexOf('popup') > -1) {
          setSearchLabels((searchLabels) => {
            const labelValues = getPopupValuesLabel(field, searchLabels);

            return Object.values(labelValues);
          });
        }
      });
    });
    return () => {
      form.removeEffects(removeEffects);
    };
  }, [ProForm]);

  useEffect(() => {
    getFormInstance && getFormInstance(form);
  }, [getFormInstance]);

  useEffect(() => {
    const leftWidth = document.querySelector('.left-section')?.clientWidth || 0;
    const rightWidth = document.querySelector('.right-section')?.clientWidth || 0;
    const clientWidth = document.querySelector('.ant-layout-content')?.clientWidth || 0;
    const width = clientWidth - leftWidth - rightWidth - 85;
    setCenterWidth(width);
  }, []);

  const togglePopup = () => {
    setPopupVisible(!isPopupVisible);
    setOpen(!open);
  };

  const submit = useCallback(() => {
    const { values: allValue } = form;
    searchChange && searchChange(allValue);
    togglePopup();
    setShowPopupValue(true);
  }, []);

  const closeLabel = useCallback((label: SearchChooseProps) => {
    const { path } = label;
    const field = form.query(path).take();
    if (field && field.setValue) {
      field.setValue(undefined);
    }
    const { values: allValue } = form;
    searchChange && searchChange(allValue);
  }, []);

  const handleCancle = useCallback(() => {
    form.reset('popup.*');
    const { values: allValue } = form;
    searchChange && searchChange(allValue);
    togglePopup();
  }, []);

  const text = useCallback(() => {
    return (
      <div>
        <VoidField name="popup">
          {popupContent &&
            popupContent.map((item, index) => {
              return <div key={index}>{item}</div>;
            })}
        </VoidField>
        <div className="btn">
          <Button onClick={handleCancle}>重置</Button>
          <Button style={{ marginLeft: 10 }} type="primary" onClick={submit}>
            确定
          </Button>
        </div>
      </div>
    );
  }, [popupContent]);

  const labels = useCallback(() => {
    // 左边显示操作6个搜索条件
    const len = (leftSectionComponents && leftSectionComponents.length - 1) || 0;
    return (
      <div style={{ width: centerWidth, overflow: 'hidden' }}>
        {showPopupValue &&
          searchLabels &&
          searchLabels.slice(0, MAX_COMPONENTS - len).map((item) => {
            return (
              <Tag key={item.name} closable onClose={() => closeLabel(item)}>
                {item.value}
              </Tag>
            );
          })}
      </div>
    );
  }, [showPopupValue, centerWidth]);

  return (
    <FormProvider form={form}>
      <div className="search-component">
        <Space className="left-section">
          {leftSectionComponents.slice(0, MAX_COMPONENTS).map((component) => (
            <div key={component.key || component.props.id} className="left-section-component">
              {component}
            </div>
          ))}
          {labels()}
        </Space>
        <div className="right-section">
          <div className="section-search">{searchInput}</div>
          {moreCategories && (
            <Tooltip
              placement="bottomRight"
              overlayStyle={{ padding: 0 }}
              color="#fff"
              title={text}
              overlayClassName="popups"
              trigger="click"
              zIndex={10}
              open={open}
              getPopupContainer={(triggerNode) => triggerNode.parentNode as any}
            >
              <div
                style={{ cursor: 'pointer' }}
                onClick={() => {
                  setOpen(!open);
                }}
              >
                <span className="more">更多筛选</span>
                <img className="img" src={ossAddress['searchMore']}></img>
              </div>
            </Tooltip>
          )}
        </div>
      </div>
    </FormProvider>
  );
};

export default SearchWrap;