
主要实现的功能
- 能够实时搜索,能够手动搜索、清除
- 能根据不同类型显示各种表单类型,例如:日期、文本框、下拉列表
- 每个搜索选项,可以根据需要设置单选或多选操作
实现过程
构建组件结构
基于ant design 的 Form表单进行开发,主要分为两部分,表单搜索选项和表单搜索、清除按钮,
表单搜索选项,是一些常见的表单选项,如果是一些不常见表单类型,采用自定义表单组件
实时搜索,主要调用了Form表单的 onValuesChange 方法
配置项:
属性 | 类型 | 描述 | 是否必填 | 默认值 |
---|---|---|---|---|
formInitialValue | Array | 初始值 | 可选 | 无 |
filterConfig | Array | 搜索项字段配置 | 必填 | 无 |
buttonLocation | String | 按钮位置 | 可选 | left |
okButtonStyle | String | 搜索按钮样式 | 可选 | 无 |
resetButtonStyle | String | 清除按钮样式 | 可选 | 无 |
onSubmit | Function | 搜索事件 | 可选 | 无 |
onReset | Function | 清除事件 | 可选 | 无 |
filterConfig
属性 | 类型 | 描述 | 是否必填 | 默认值 |
---|---|---|---|---|
label | String | 字段 标签 | 是 | 无 |
name | String | 字段 name | 是 | 无 |
type | String | 搜索类型(多选按钮或者单选按钮: Button(默认)、日期: Date、下拉选择: Select、文本框: Text) | Button | |
options | Array | type为 Button、Select 时 | 否 | 无 |
添加反向数据流
Form 会对直接子元素绑定表单功能,即不用去手动绑定,就能获取其数据,但是对于一些自定义表单组件,就要提供 onChange 事件或 trigger 的值同名的事件,在把自定义表单组件的数据传到父组件中去

代码结构
SearchFilter.tsx
/**
* 搜索组件
* <SearchFilter
filterConfig={this.filterConfig}
onSubmit={this.handleSearch}
onReset={this.handleReset}
/>
表单配置项
filterConfig = [
{
label: '备件类型',
name: 'type',
type: 'Button', // 表单类型 多选按钮或单选按钮:Button(默认)、 日期:Date、 下拉选择:Select、 文本框:Text
mode: 'multiple', // 默认多选 单选为 single
options: [
{
id: '',
label: ''
}
]
},
{
label: '时间周期',
name: 'date_picker',
type: 'Date'
},
];
*/
import React, { Component } from 'react';
import { Button, Form, Row, Col, Select, Card, Input } from 'antd';
import { FormComponentProps } from 'antd/lib/form/Form';
import ChoiceDatePicker from '../ChoiceDatePicker';
import ButtonSelect from '../ButtonSelect';
import {
filterForm, filterFormItemWithNormal,
} from './index.less';
const { Option } = Select;
export interface ItemProps {
id: string | number;
label: string | number;
}
export interface SearchFilterValue {
[propName: string]: string | number | any;
}
export interface FormItem {
label: string;
mode: string;
name: string;
type: string;
options: ItemProps[];
}
export interface SearchFilterProps extends FormComponentProps{
onSubmit?: (values: SearchFilterValue) => void;
onReset?: () => void;
formInitialValue?: {
[propName: string]: string | number;
};
disableFields?: string[];
filterConfig: FormItem[];
okButtonStyle?: {
[propName: string]: string | number;
};
resetButtonStyle?: {
[propName: string]: string | number;
};
buttonLocation?: 'left' | 'right';
}
export interface SearchFilterState {
}
class SearchFilter extends Component<SearchFilterProps, SearchFilterState> {
handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
this.props.form.validateFields((err: any, values: SearchFilterValue) => {
const { onSubmit } = this.props;
const formData = {};
for (let key in values) {
if (values[key] !== undefined) {
if (Array.isArray(values[key])) {
if (values[key].length > 0)
formData[key] = values[key].join(',');
} else {
formData[key] = values[key];
}
}
}
if (onSubmit) onSubmit(formData);
});
};
handleReset = () => {
this.props.form.resetFields();
const { onReset } = this.props;
if (onReset) onReset();
};
isDisabled = (name: string) => {
const { disableFields } = this.props;
return disableFields && disableFields.length>0 && disableFields.includes(name)
};
render() {
const {
form: {getFieldDecorator},
formInitialValue,
filterConfig,
okButtonStyle,
resetButtonStyle,
buttonLocation,
} = this.props;
return (
<>
<Card bordered={false} style={{ marginTop: 24 }}>
<Form className={filterForm} onSubmit={this.handleSearch} labelAlign={"right"} labelCol={{xxl: {span: 1}, xl: {span: 2}, lg: {span: 3}, sm: {span: 3}}} wrapperCol={{span: 20}} style={{textAlign: 'left', marginLeft: -10}}>
{
filterConfig && filterConfig.map(item => {
switch(item.type) {
case 'Select':
return <Form.Item label={item.label} key={item.name} className={filterFormItemWithNormal}>
{getFieldDecorator(item.name, {
initialValue: formInitialValue ? formInitialValue[item.name] : undefined
})(
<Select
showSearch
mode={item.mode}
style={{ width: '100%' }}
placeholder="请选择"
disabled={ this.isDisabled(item.name) }
>
{item.options && item.options.length > 0 ? item.options.map((valueItem: ItemProps) =>
(valueItem.label && <Option key={valueItem.id}>{valueItem.label}</Option>)
) : undefined}
</Select>
)}
</Form.Item>
break;
case 'Text':
return <Form.Item label={item.label} key={item.name} className={filterFormItemWithNormal}>
{getFieldDecorator(item.name, {
initialValue: formInitialValue ? formInitialValue[item.name] : undefined
})(
<Input
allowClear
disabled={ this.isDisabled(item.name) }
/>
)}
</Form.Item>
break;
case 'Date':
return <Form.Item label={item.label} key={item.name} className={filterFormItemWithNormal}>
{getFieldDecorator(item.name, {
initialValue: formInitialValue ? formInitialValue[item.name] : undefined
})(
<ChoiceDatePicker
choiceList={['oneMonth', 'halfYear', 'oneYear']}
/>
)}
</Form.Item>
break;
default:
// Button
return <Form.Item label={item.label} key={item.name} className={filterFormItemWithNormal}>
{getFieldDecorator(item.name, {
initialValue: formInitialValue ? formInitialValue[item.name] : undefined
})(
<ButtonSelect
formItem={item}
/>
)}
</Form.Item>
break;
}
})
}
<Row>
<Col
span={24}
style={ buttonLocation && buttonLocation === 'right' ? {
textAlign: 'right',
} : { textAlign: 'left', marginLeft: 10 }}
>
<Button type="primary" htmlType="submit" style={okButtonStyle}>
搜索
</Button>
<Button
style={Object.assign({
marginLeft: 8,
}, resetButtonStyle)}
onClick={this.handleReset}
>
清除
</Button>
</Col>
</Row>
</Form>
</Card>
</>
);
}
}
let timeout: any;
const onValuesChange = (props: SearchFilterProps, changedValues: any, allValues: any) => {
const formData = {};
for (let key in allValues) {
if (allValues[key] !== undefined) {
formData[key] = allValues[key];
if (Array.isArray(allValues[key])) {
if (allValues[key].length > 0)
formData[key] = allValues[key].join(',');
} else {
formData[key] = allValues[key];
}
}
}
if (props.onSubmit) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
var debounceChange = function() {
if (props.onSubmit)
props.onSubmit(formData)
};
timeout = setTimeout(debounceChange, 500);
}
};
export default Form.create<SearchFilterProps>({ name: 'part_filter', onValuesChange: onValuesChange })(SearchFilter);
ButtonSelect.tsx
import React, { useState, useEffect } from 'react';
import { Button } from 'antd';
import {
filterFormItemButton, filterFormItemButtonSelected,
} from './index.less';
export interface ItemProps {
id: string | number;
label: string | number;
}
export interface FormItem {
label: string;
mode: string;
name: string;
type: string;
options: ItemProps[];
}
interface ButtonSelectProps {
value?: any[] | undefined;
onChange?: (values: any) => void;
formItem: FormItem;
}
// 多选按钮 单选按钮
const ButtonSelect = (props: ButtonSelectProps) => {
const { formItem, onChange, value } = props;
const [selected, setSelected] = useState(value ? value : []);
useEffect(() => {
const val = props.value ? props.value : [];
setSelected(val)
}, [props.value])
const triggerChange = (changedValue: any[]) => {
if (onChange) {
onChange(changedValue);
}
};
const handleChange = (item: FormItem, value: string | number) => {
let valueList: any[] = [value];
if (selected && selected.includes(value)) {
valueList = selected.filter(v => v !== value);
} else if (selected) {
if (item.mode === 'single') {
valueList = [value];
} else {
valueList = [...selected, value];
}
}
setSelected(valueList);
triggerChange(valueList);
};
return (
<>
{formItem.options && formItem.options.map((valueItem: ItemProps) =>
<Button
key={valueItem.id}
size='small'
type={selected && selected.includes(valueItem.id) ? undefined : 'link'}
className={selected && selected.includes(valueItem.id) ? filterFormItemButtonSelected : filterFormItemButton}
onClick={()=>{handleChange(formItem, valueItem.id)}}
>
{valueItem.label}
</Button>
)}
</>
);
};
export default ButtonSelect;
性能优化
因为是实时搜索,特别是文本类型的搜索,每次输入,都会触发请求,为了节省资源,添加了防抖操作。

不足之处
当搜索项为下拉列表时,如果选项过多,会造成页面卡顿问题,正在解决中.....