先上效果图~
import React, {
useState,
useCallback,
useMemo,
useEffect,
useRef,
} from 'react';
import {
Select,
SelectProps,
Space,
Checkbox,
Input,
Empty,
Tooltip,
} from 'antd';
import { debounce, isArray, isEmpty, isFunction } from 'lodash-es';
import classnames from 'classnames';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
import { useIntl } from 'umi';
type Key = string | number;
type OptionType = {
label: string | React.ReactNode;
value: Key;
};
type TypeOptMap = {
[key: string]: OptionType;
};
type CustomTagProps = {
label: React.ReactNode;
value: any;
disabled?: boolean;
onClose?: (event?: React.MouseEvent) => void;
closable?: boolean;
};
type PluralSelectProps = Omit<
SelectProps,
'onChange' | 'options' | 'value' | 'mode'
> & {
onChange?: (value: Key[]) => void;
options?: OptionType[];
value?: Key[];
showSearch?: boolean;
};
const transformMap = (params: OptionType[]) => {
const result: { [key: string]: OptionType } = {};
if (isEmpty(params) || !isArray(params)) return result;
params.forEach((item) => {
result[item.value] = item;
});
return result;
};
const labelVisible = (searchValue: string, label: string) => {
return !searchValue || (searchValue && label.indexOf(searchValue) > -1);
};
const PluralSelect: React.FC = ({
options,
maxTagCount = 'responsive',
value = [],
onChange,
placeholder,
className,
dropdownClassName,
showSearch = true,
...props
}) => {
const { formatMessage } = useIntl();
const [currentValues, setCurrentValues] = useState(value);
const [searchValue, setSearchValue] = useState('');
const searchInputRef: any = useRef();
const valueCacheRef = useRef();
const optMap: TypeOptMap = useMemo(() => {
return transformMap(options || []) || {};
}, [options]);
const allValues = useMemo(() => {
return options?.map((opt) => opt?.value) || [];
}, [options]);
// 半选状态
const indeterminate = useMemo((): boolean => {
return (
Boolean(currentValues?.length) &&
isArray(options) &&
currentValues?.length < options.length
);
}, [currentValues, options]);
// 全选状态
const checkedAll = useMemo((): boolean => {
return Boolean(
options?.length && currentValues?.length === options?.length,
);
}, [currentValues, options]);
// 选择checkbox
const handleChange = useCallback(
(event: CheckboxChangeEvent) => {
const {
target: { value, checked },
} = event;
let mergedValues: Key[] = [];
if (checked) {
mergedValues = [...currentValues, value];
setCurrentValues(mergedValues);
} else {
mergedValues = currentValues.filter(
(each) => String(each) !== String(value),
);
setCurrentValues(mergedValues);
}
isFunction(onChange) && onChange?.(mergedValues);
},
[currentValues, onChange],
);
// 输入搜索条件
const handleInputKeyword = useCallback(() => {
const value = searchInputRef.current?.input?.value || '';
setSearchValue(value);
}, []);
// 搜索(防抖)
const debounceSearch = useMemo(() => debounce(handleInputKeyword, 200), [
handleInputKeyword,
]);
// 全选或者全不选
const handleCheckAllChange = useCallback(
(event: CheckboxChangeEvent) => {
const {
target: { checked },
} = event;
isFunction(onChange) && onChange?.(checked ? allValues : []);
setCurrentValues(checked ? allValues : []);
},
[onChange, allValues],
);
// select 变化
const handleSelectChange = useCallback(
(values: Key[]) => {
setCurrentValues(values);
isFunction(onChange) && onChange?.(values);
},
[onChange],
);
useEffect(() => {
// 当 value 发生变化时,更新 currentValues
const currentValueStr = value ? JSON.stringify(value) : '';
if (currentValueStr === valueCacheRef.current) return;
valueCacheRef.current = currentValueStr;
setCurrentValues(value);
}, [value]);
// 卸载时清空
useEffect(() => {
return () => {
setSearchValue('');
};
}, []);
const renderTitle = (title: string | React.ReactNode) => {
if (!searchValue) {
return {title};
}
const strTitle = String(title);
const index = strTitle.indexOf(searchValue);
const beforeStr = strTitle.substring(0, index);
const afterStr = strTitle.substring(index + searchValue.length);
// 高亮搜索关键字
const currentTitle =
index > -1 ? (
<>
{beforeStr}
{searchValue}
{afterStr}
>
) : (
strTitle
);
return {currentTitle};
};
const CheckboxOptions = () => {
if (isEmpty(options) || !isArray(options))
return ;
return (
{formatMessage({ id: 'ACTION_SELECT_ALL' })}
{isArray(options) &&
options.map(
({ label, value }, idx) =>
labelVisible(searchValue, String(label)) && (
{renderTitle(label)}
),
)}
);
};
const tagRender = (props: CustomTagProps) => {
const { value } = props;
const { label } = optMap[value];
return (
{label}
);
};
const dropdownRender = () => (
{showSearch && (
)}
);
return (
);
};
export default PluralSelect;