Search 搜索,提供一般列表页搜索,本文简单讲述一下设计思想。
先看一下效果图
组件只封装了 RangePicker、Input、Select三个搜索条件,可以继续扩展
为什么要封装Search组件?
如图1列表页带搜索,运用场景很多,而且搜索条件有多有少,搜索类型也多有不同,产品还时不时的加或者删几个搜索条件......如果不封装的话我们每次使用都要引入大量antd组件代码,写一大堆Form,完全不不了一行组件,三两个参数简洁,美观。
封装后的使用 (文章最后附上组件源码)
const items = [{
type: 'Input',
label: 'ID查询',
required: false,
placeholder: '请输入id',
parameter: 'id',
}, {
type: 'Select',
label: '科目查询',
required: false,
placeholder: '请选择',
parameter: 'subject',
options: []
}];
<Search items={items} loading={loading} onSubmit={()=>{}} onReset={()=>{}}/>
如此便生成图1的搜索组件,一次封装,N次受益,我们再也不需要写那些antd 的Form了,我们只需要看产品要求的搜索条件都是什么,去配置items(array)就可以了,具体参数下文会详细解说,然后再把搜索方法和重置方法的业务逻辑加上,整个搜索就完成了。
Search组件的设计思路 “用对象生成UI”
我们把Search整体看做一个大的容器,其中包含了“一定一不定”,操作方法一定(提交、重置),输入条件不一定,我所说的一定不一定指的是UI显示上。
“一定”:所有的搜索基本上都需要“提交”、“重置”两个按钮。
“提交”就是把输入的搜索信息提交到后台获取到列表数据,但是每个页面的搜索接口不一样,数据处理或许也有区别,所以我们把提交方法抛出来,用的时候把方法传入就可以。
“重置”就是还原数据,重置搜索条件,清空搜索框,显示默认列表数据
“一不定”:输入条件不一定,条件个数不一定,不过输入条件一般就是输入名称搜索、或者类型搜索(下拉框)、或者时间搜索
需要提取的公共部分
1、生成ui的数组
2、提交、重置方法
我们着重讲一下对象生成UI的概念
首先我们把每一个搜索条件看成一个“框”
“框”之间的差别:
类型不一样Input、Select...,
提交参数名字不一样,
搜索框名字不一样
...
(我们默认一行显示三个组件,缺憾,待完善)
/**
* items [{ | array | 数组包含元素对象
* type | string | 类型 判断是选择还是输入 名字按照antd组件名字传入
* label | string | 标题
* required | boolean | 是否必填项 true / false
* placeholder | string | 描述
* parameter | string | 参数名字
* options | array | 如果type为Select必须传,否则认为此组件是Input
* pattern | | 正则
* }]
* onSubmit | function | 提交方法
* onReset | function | 重置方法
* */
以上就是现阶段组件的全部参数
由于我们没有把每行显示的个数抛出去,而且在组件内部定义好的3,所有当我们拿到items数组的时候就能知道,我们需要几行才能够显示
其中this.colLength=3;
//根据items长度判断需要显示几行
getChildren(items, getFieldDecorator) {
const len = items.length;
const rowLen = Math.ceil(len / this.colLength);
let rowArr = [];
for (let i = 0, j = rowLen; i < j; i++) {
rowArr.push(
<Row key={i}>
{
this.getColItem(items, getFieldDecorator, i)
}
</Row>
);
}
return rowArr;
}
判断每行里边有几个组件比较步骤多一些,不多说直接贴代码就不多说了,直接看源码,其中就是把数组里边的对象正确的显示
import React, {Component} from 'react';
import {
Form,
Row,
Col,
Input,
Select,
Button,
DatePicker,
} from 'antd';
const FormItem = Form.Item;
const Option = Select.Option;
const {RangePicker} = DatePicker;
/**
* lixin 2013.4.19
* items [{ | array | 数组包含元素对象
* type | string | 类型 判断是选择还是输入 名字按照antd组件名字传入
* label | string | 标题
* required | boolean | 是否必填项 true / false
* placeholder | string | 描述
* parameter | string | 参数名字
* options | array | 如果type为Select必须传,否则认为此组件是Input
* pattern | | 正则
* typeDiff | boolean | true type为Select的时候,key为string的时候会用到
* }]
* onSubmit | function | 提交方法
* onReset | function | 重置方法
* 未完待续...
*
* */
@Form.create()
class Search extends Component {
constructor(props) {
super(props);
this.colLength = 3;//每行显示个数,暂时因为栅格布局不能修改
}
//根据items长度判断需要显示几行
getChildren(items, getFieldDecorator) {
const len = items.length;
const rowLen = Math.ceil(len / this.colLength);
let rowArr = [];
for (let i = 0, j = rowLen; i < j; i++) {
rowArr.push(
<Row key={i}>
{
this.getColItem(items, getFieldDecorator, i)
}
</Row>
);
}
return rowArr;
}
//为每行里边塞Col
getColItem(items, getFieldDecorator, start) {
const colArr = [];
const _this = this;
//从items数组第几个元素开始循环
const _start = start * this.colLength;
//剩余几个对象没有遍历渲染
const _surplus = items.length - _start;
let len;
//如果剩下的小于3 长度直接登录items的长度
if (_surplus < this.colLength) {
len = items.length;
} else {
//如果剩下的大于3,那么长度等于开始索引加3
len = _start + this.colLength;
}
for (let i = _start, j = len; i < j; i++) {
const index = i;
const value = items[i];
const _offset = index % this.colLength == 0 ? 0 : 1;
let _type = value.type;
let _options;
if (value.hasOwnProperty('options')) {
_options = value.options;
} else {
if (_type === 'RangePicker') {
//
} else {
_type = 'Input';
}
}
let _rulesType = 'number';
if (_type === 'Input') {
_rulesType = 'string';
} else if (_type === 'RangePicker') {
_rulesType = 'array';
}
if (value.typeDiff) {
_rulesType = 'string';
}
const formItemLayout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
colArr.push(
<Col key={index} xl={{span: 7, offset: _offset}} lg={{span: 8}} md={{span: 12}} sm={24}>
<FormItem label={`${value.label}:`} {...formItemLayout}>
{getFieldDecorator(value.parameter, {
rules: [{
required: value.required,
message: value.placeholder,
pattern: value.pattern ? value.pattern : '',
type: _rulesType
}]
})(
_this.switchItem(_type, value.placeholder, _options)
)}
</FormItem>
</Col>
)
}
return colArr;
}
//如果是Select需要传入options
switchItem(which, placeholder, options) {
const _this = this;
switch (which) {
case 'Input':
return <Input placeholder={placeholder}/>
case 'Select':
return <Select placeholder={placeholder}>{_this.getOption(options)}</Select>
case 'RangePicker':
return <RangePicker/>
}
}
getOption(data) {
if (!data) {
return;
}
return data.map((value, index) => {
return <Option key={index} value={value.key}>{value.value}</Option>
})
}
//重置输入框内容
handleReset = () => {
this.props.form.resetFields();
if (this.props.onReset) {
this.props.onReset();
}
}
//提交
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, fieldsValue) => {
this.props.onSubmit(err, fieldsValue);
});
};
render() {
const {items, form, loading} = this.props;
const {getFieldDecorator} = form;
return (
<Form onSubmit={this.handleSubmit} layout="horizontal">
{
this.getChildren(items, getFieldDecorator)
}
<Row type="flex" justify="end">
<Col>
<FormItem>
<Button type="default" htmlType="button" style={{marginRight: '10px'}} onClick={this.handleReset}>
重置
</Button>
<Button loading={loading} type="primary" htmlType="submit">
查询
</Button>
</FormItem>
</Col>
</Row>
{
this.props.children
}
</Form>
)
}
}
export default Search;