后台管理系统都会用到如页头搜索栏以及table展示数据,为了减少代码量以及尽量公用部分逻辑,集成对应控件并统一封装成一个组件显得十分有必要。
功能
组件依赖于moment
,使用demo依赖loadsh
做防抖。
先展示功能部分。
/**
*
* @param {页面搜索栏组件} form 前端chen
* ----避免各个页面的搜索栏各自为战的问题,统一组件,减少代码量,尽量逻辑/组件复用----
* 兼容的搜索控件:
===Button:可以添加多个button按钮;
===Input:基本使用,输入框;
===InputNumber:number输入框;
===AutoComplete:输入匹配输入框,匹配规则默认option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1;
===Select:下拉选择器,传递selectList{id,name}或者render=>reactNode生成对应的下拉框内容;
===TreeSelect:同·Select·;
===Switch:开关按钮,默认文字为“开/关”;
===Cascader:级联选择器,例如省市区;
===WeekPicker:星期选择器;
===MonthPicker:月份选择器;
===DatePicker:日期选择器;
===RangePicker:日期间隔选择器;
===TimeRangePicker:基于timepicker二次封装的时间段选择器,onChange时额外返回permission,开始/结束时间都有值以及autoSearch时触发搜索;
*
* 使用规则:除了各个组件使用原有API,新增如下API
===searchList:搜索栏list,其中三个字段必填:tagName标签名称,key搜索键值名称,label搜索前置名称 ;
===reset:是否重置按钮,boolean值,默认true ;
===onChange:每个非button控件值改变时触发的操作,function(itemValue);
===onDateChange:对应日期/时间选择器相关onchange[moment,str]事件,方便获取对应的str,会先于onChange执行
===onSearch:触发搜索,function(fieldsValue);
===autoSearch:是否自动触发搜索,boolean值,默认false ;
===noSearchButton:是否不用搜索按钮,boolean值,默认false ;
===stopPagination:是否返回true禁止其他如table的分页器关联操作, function(boolean) ;
===onButtonClick:有其他额外button按钮时触发的操作,function(item);
*
*/
Demo
接着看下使用demo
import React, { PureComponent } from "react";
import { debounce } from "lodash"; // 防抖
import { SearchForm } from "@/components";
const searchList = [
{
tagName: "treeselect",
key: "treeSelectItem",
label: "类型",
defaultValue: null,
placeholder: "请选择",
selectList: [
{
id: 0,
name: "123",
children: [
{
id: 10,
name: "12311"
},
{
id: 11,
name: "asd11"
}
]
},
{
id: 1,
name: "asd"
}
]
},
{
tagName: "select",
key: "selectItem",
label: "类型2",
defaultValue: null,
placeholder: "请选择",
render: () => {
// render权限比selectList高
return [
{
id: 0,
name: "12311"
},
{
id: 1,
name: "asd111"
}
].map(item => {
return (
<Option key={item.id} value={item.id}>
{item.name}
</Option>
);
});
},
selectList: [
{
id: 0,
name: "123"
},
{
id: 1,
name: "asd"
}
]
},
{
tagName: "autoComplete",
key: "autoCompleteItem",
label: "标题",
dataSource: ["Burns Bay Road", "Downing Street", "Wall Street"]
},
{
tagName: "TimeRangePicker",
key: "timerangepickerItem",
label: "时间",
timeRange: {
startTime: "00:10:00",
endTime: "00:20:00"
}
}
];
export function SearchFormFunctionDemo() {
const onSearch = e => {
console.log(e);
};
return (
<SearchForm
reset
autoSearch
searchList={searchList}
stopPagination={debounce(e => console.log(e), 1500)}
onSearch={debounce(e => onSearch(e), 350)}
onChange={debounce(e => console.log(e), 350)}
/>
);
}
export class SearchFormClassDemo extends PureComponent {
constructor(props) {
super(props);
this.onSearch = debounce(this.onSearch, 350);
}
onSearch = e => {
console.log(e);
};
render() {
return (
<SearchForm
reset
autoSearch
searchList={searchList}
stopPagination={e => console.log(e)}
onSearch={e => this.onSearch(e)}
onChange={e => console.log(e)}
/>
);
}
}
实现封装
最后看下封装代码,其中TimeRangePicker
是之前就封装好的组件,可以看下另外一篇文章
import React, { Fragment } from "react";
import moment from "moment";
import {
Icon,
Button,
Input,
InputNumber,
AutoComplete,
Select,
message,
DatePicker,
TreeSelect,
Switch,
Cascader,
TimePicker,
Form
} from "antd";
import { TimeRangePicker } from "@/components";
const FormItem = Form.Item;
const { Option } = Select;
const { TreeNode } = TreeSelect;
const { MonthPicker, RangePicker, WeekPicker } = DatePicker;
/**
*
* @param {页面搜索栏组件} form chenhaoyin
* ----避免各个页面的搜索栏各自为战的问题,统一组件,减少代码量,尽量逻辑/组件复用----
* 兼容的搜索控件:
===Button:可以添加多个button按钮;
===Input:基本使用,输入框;
===InputNumber:number输入框;
===AutoComplete:输入匹配输入框,匹配规则默认option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1;
===Select:下拉选择器,传递selectList{id,name}或者render=>reactNode生成对应的下拉框内容;
===TreeSelect:同·Select·;
===Switch:开关按钮,默认文字为“开/关”;
===Cascader:级联选择器,例如省市区;
===WeekPicker:星期选择器;
===MonthPicker:月份选择器;
===DatePicker:日期选择器;
===RangePicker:日期间隔选择器;
===TimeRangePicker:基于timepicker二次封装的时间段选择器,onChange时额外返回permission,开始/结束时间都有值以及autoSearch时触发搜索;
*
* 使用规则:除了各个组件使用原有API,新增如下API
===searchList:搜索栏list,其中三个字段必填:tagName标签名称,key搜索键值名称,label搜索前置名称 ;
===reset:是否重置按钮,boolean值,默认true ;
===onChange:每个非button控件值改变时触发的操作,function(itemValue);
===onDateChange:对应日期/时间选择器相关onchange[moment,str]事件,方便获取对应的str,会先于onChange执行
===onSearch:触发搜索,function(fieldsValue);
===autoSearch:是否自动触发搜索,boolean值,默认false ;
===noSearchButton:是否不用搜索按钮,boolean值,默认false ;
===stopPagination:是否返回true禁止其他如table的分页器关联操作, function(boolean) ;
===onButtonClick:有其他额外button按钮时触发的操作,function(item);
*
*/
/** formItem的getFieldDecorator配置 */
const getFieldDecoratorOps = form => {
let rules = null;
if (form.rules) {
rules = [...form.rules];
}
if (form.pattern) {
rules = [
{
pattern: form.pattern,
message: form.message || "内容不符合规则"
}
];
}
return {
initialValue: form.nextdefaultvalue || form.nextvalue,
rules
};
};
const Search = ({
searchList /** 搜索栏list,其中三个字段必填:tagName标签名称,key搜索键值名称,label搜索名称 */,
reset = true /** 重置按钮 */,
onDateChange /** 日期相关onchange[str,moment]事件,方便获取对应的str */,
onSearch /** 触发搜索 */,
autoSearch /** 自动触发搜索 */,
noSearchButton /** 不用搜索按钮 */,
stopPagination /** 是否返回true禁止其他如table的分页器关联操作 */,
onButtonClick /** 有其他额外button按钮时触发的操作 */,
form: { getFieldDecorator, validateFields, setFieldsValue, resetFields }
}) => {
if (!searchList) {
searchList = [];
}
/** 搜索 */
const handleSearch = () => {
validateFields((err, fieldsValue) => {
// console.log(err);
// console.log(fieldsValue);
if (!err) {
stopPagination && stopPagination(false);
} else {
message.destroy();
message.error("搜索条件有误", 1.5);
stopPagination && stopPagination(true);
}
onSearch && onSearch(fieldsValue);
});
};
/** 获取时间段 */
const getRangeTime = (key, range, permission) => {
const time = [range.startTime, range.endTime, permission];
const fields = {
[key]: time
};
setFieldsValue(fields);
range.startTime && range.endTime && autoSearch && handleSearch();
};
/** datePicer衍生器onchange */
const dateChange = (mo, str) => {
onDateChange && onDateChange([str, mo]);
autoSearch && handleSearch();
};
return (
<Form layout="inline">
{searchList.map((form, idx) => {
if (!form.tagName) {
return console.error("请使用tagName作为控件的标签名字段");
}
form = {
...form,
nextvalue: form.value || form.checked, // 新增字段要用小写,不然会警告
nextdefaultvalue: form.defaultValue || form.defaultChecked // 新增字段要用小写,不然会警告
};
const exp = form.pattern;
const tagname = form.tagName;
const Options = getFieldDecoratorOps(form);
delete form.checked;
delete form.defaultChecked;
// delete form.dataSource;
delete form.pattern; // 警告:pattern命名会和组件本身冲突,但为了更友好继续沿用此命名
delete form.tagName; // 警告:在react中属性都是小驼峰命名,所以也会有属性的命名冲突
delete form.value; // 警告:如果有改属性,{...form}会与getFieldDecorator产生冲突
delete form.defaultValue; // 警告:如果有改属性,{...form}会与getFieldDecorator产生冲突
return (
<FormItem label={form.label} key={`${form.key}-${tagname}-${idx}`}>
{tagname.toLowerCase() === "input" &&
getFieldDecorator(form.key, Options)(
<Input
allowClear
type={form.type || "text"}
size="default"
onPressEnter={() => handleSearch()}
suffix={<Icon type="search" />}
{...form}
/>
)}
{tagname.toLowerCase() === "inputnumber" &&
getFieldDecorator(form.key, Options)(
<InputNumber
allowClear
min="0"
size="default"
onPressEnter={() => handleSearch()}
suffix={<Icon type="search" />}
{...form}
c
/>
)}
{tagname.toLowerCase() === "autocomplete" &&
getFieldDecorator(form.key, Options)(
<AutoComplete
allowClear
dataSource={form.dataSource}
filterOption={(inputValue, option) =>
option.props.children
.toUpperCase()
.indexOf(inputValue.toUpperCase()) !== -1
}
onSearch={() => handleSearch()}
onSelect={() => autoSearch && handleSearch()}
// onSearch={() => handleSearch()}
{...form}
/>
)}
{tagname.toLowerCase() === "switch" &&
getFieldDecorator(form.key, {
...Options,
valuePropName: "checked"
})(
<Switch
checkedChildren="开"
unCheckedChildren="关"
onChange={() => autoSearch && handleSearch()}
{...form}
/>
)}
{tagname.toLowerCase() === "select" &&
getFieldDecorator(form.key, Options)(
<Select
allowClear
style={{ minWidth: "150px", maxWidth: "250px" }}
{...form}
onSelect={() => autoSearch && handleSearch()}
>
{form.render && form.render()}
{!form.render &&
(form.selectList || []).map(cl => {
return (
<Option key={cl.id} value={cl.id}>
{cl.name}
</Option>
);
})}
</Select>
)}
{tagname.toLowerCase() === "treeselect" &&
getFieldDecorator(form.key, Options)(
<TreeSelect
allowClear
multiple
style={{ minWidth: "150px", maxWidth: "350px" }}
onSelect={() => autoSearch && handleSearch()}
{...form}
>
{form.render && form.render()}
{!form.render &&
(form.selectList || []).map(cl => {
return (
<TreeNode key={cl.id} value={cl.id} title={cl.name}>
{(cl.children || []).map(chil => {
return (
<TreeNode
key={`${cl.id}_${chil.id}`}
value={chil.id}
title={chil.name}
/>
);
})}
</TreeNode>
);
})}
</TreeSelect>
)}
{tagname.toLowerCase() === "datepicker" &&
getFieldDecorator(form.key, Options)(
<DatePicker
onChange={(date, str) => dateChange(date, str)}
{...form}
/>
)}
{tagname.toLowerCase() === "weekpicker" &&
getFieldDecorator(form.key, Options)(
<WeekPicker
onChange={(week, str) => dateChange(week, str)}
{...form}
/>
)}
{tagname.toLowerCase() === "monthpicker" &&
getFieldDecorator(form.key, Options)(
<MonthPicker
onChange={(mon, str) => dateChange(mon, str)}
{...form}
/>
)}
{tagname.toLowerCase() === "rangepicker" &&
getFieldDecorator(form.key, Options)(
<RangePicker
onChange={(range, str) => dateChange(range, str)}
style={{ width: "230px" }}
{...form}
/>
)}
{tagname.toLowerCase() === "timepicker" &&
getFieldDecorator(form.key, Options)(
<TimePicker
onChange={(date, str) => dateChange(date, str)}
{...form}
/>
)}
{tagname.toLowerCase() === "timerangepicker" &&
getFieldDecorator(form.key, Options)(
<Fragment>
{/* 因为TimeRangePicker本身是平级双标签生成,
所以必须要有外层标签包裹,否则无效 */}
<TimeRangePicker
{...form}
getRangeTime={(range, permission) =>
getRangeTime(form.key, range, permission)
}
/>
</Fragment>
)}
{tagname.toLowerCase() === "cascader" &&
getFieldDecorator(form.key, Options)(
<Cascader style={{ width: "230px" }} {...form} />
)}
{tagname.toLowerCase() === "button" &&
getFieldDecorator(form.key, Options)(
<Button
style={{ marginLeft: "15px" }}
onClick={onButtonClick && onButtonClick(form)}
type="primary"
{...form}
>
{form.btnLabel || form.label}
</Button>
)}
</FormItem>
);
})}
<FormItem>
{!noSearchButton && (
<Button
style={{ marginLeft: "15px" }}
onClick={handleSearch}
type="primary"
>
查询
</Button>
)}
{reset && (
<Button
style={{ marginLeft: "15px" }}
onClick={() => {
resetFields();
handleSearch();
}}
>
重置
</Button>
)}
</FormItem>
</Form>
);
};
Search.propTypes = {};
export const SearchForm = Form.create({
name: "SearchForm",
// onFieldsChange(props, changedFields) {
/** 有多少个item会执行多少次 */
// props.onChange && props.onChange(changedFields);
// },
// mapPropsToFields(props) {
// // 对props做处理
// console.log(props);
// },
onValuesChange(props, values, e) {
/** 只会执行一次 */
props.onChange && props.onChange(values);
}
})(Search);
网友评论