文章目录
前提说一下,我使用的ui组件是React-antd
一、form表单封装之树形选择框封装
树形选择框对应于antd的 https://ant.design/components/tree-select-cn/
1. 代码结构
(1)html代码
代码如下:
import React, { FC, forwardRef } from 'react';
import { MarketList } from 'utils/convertToTreeData';
import { TreeSelect } from 'antd';
import styles from './tree-picker.module.scss';
import { TreeSelectProps } from 'antd/lib/tree-select';
import { TreeNodeValue } from 'antd/lib/tree-select/interface';
//定义树形选择框所使用的prop
interface ComPonentProps<T extends TreeNodeValue> {
style?: object;
loading?: boolean;
treeData: MarketList[];
placeholder?: string;
value?: TreeNodeValue;
onChange?: TreeSelectProps<T>['onChange'];
}
type Props = ComPonentProps<TreeNodeValue>;
const TreePicker: FC<Props> = ({ onChange, value, style, loading, treeData, placeholder }, ref): React.ReactElement => {
return (
<>
<TreeSelect
className={styles['tree-select']} //css做样式调整
loading={loading}
style={style}
treeData={treeData}
treeCheckable={true}
placeholder={placeholder}
onChange={onChange}
ref={ref}
value={value}
></TreeSelect>
</>
);
};
export default forwardRef(TreePicker);
(2)树形选择框的结构
MarketList 的结构如下:
interface MarketItem {
key: string;
value: string;
title: string;
}
export interface MarketList {
key: string;
value: string;
title: string;
children: MarketItem[];
}
(3)css
我用的是scss
css如下:
.tree-select {
:global .ant-select-selection {
height: 30px;
overflow-y: scroll;
}
}
(4)转化函数
注意在使用树形选择框时,需要数据转化为MarketList的形式,转化函数如下:
先调用father函数转化大致格式,再将其转化为树形选择框的形式
//以下函数对我的数据有用,对自己的数据可以改变形式
//我的数据格式如下:
/*
[
{
"group": {
"id": "5f2b9d0832326e014cfe862a",
"parentId": "5f2b9d0832326e014cfe862a",
"rootId": "5f2b9d0832326e014cfe862a",
"groupName": "测试设备"
},
"devices": [
{
"id": "5f4f8863afc2140186e27b17",
"deviceId": "18:93:7F:B3:55:98",
"deviceName": "444-2"
},
{
"id": "5f63097fafc2140186e27b18",
"deviceId": "18:93:7F:94:D4:4D",
"deviceName": "333"
},
{
"id": "5fa3ab7ff34acc03cf0d2c99",
"deviceId": "18:93:7F:B3:34:94",
"deviceName": "5555"
},
{...}
]
},
{...}
]
*/
//将数据结构统一
export function convertToFatherTreeData(list: any[]): MarketList[] {
list.forEach(item => {
item.deviceName = item.group.groupName;
item.deviceId = item.group.rootId;
item.id = item.group.id;
item.children = item.devices;
delete item.devices;
delete item.group;
});
return list;
}
//将数据转成antd所需要的格式
export function convertToTreeData(list: any[]): MarketList[] {
list.forEach(item => {
item.title = item.deviceName;
item.value = item.deviceId;
item.key = item.id;
delete item.deviceName;
delete item.deviceId;
delete item.id;
if (item.children && item.children.length) {
convertToTreeData(item.children);
}
});
return list;
}
2. 使用方法
(1)单独使用
这里先介绍独立的使用方法
<Col span={6}>
<label style={{ verticalAlign: 'top', lineHeight: '31px' }}>范围:</label>
<TreePicker
onChange={handleDeviceChange}
loading={loading}
treeData={deviceArea} //转化好的数据,这里就不做赘述
style={{ width: '70%' }}
placeholder={'请选择范围'}
/>
</Col>
onchange函数
//可以将数据直接保存在state数组中
const handleDeviceChange = useCallback(
value => {
setDeviceGroup(value);
},
[setDeviceGroup],
);
(2)和form表单一起使用
这里不使用onChange函数,可以使用form的onSubmit/onFinish函数就可以拿到group的值
<Form.Item label={'范围'} >
{getFieldDecorator(`group`, {
initialValue: group,//默认数据
})(
<TreePicker
loading={loading}
treeData={deviceArea} //转化好的数据,这里就不做赘述
placeholder={ `请选择范围`}
/>,
)}
</Form.Item>
二、form表单之时间范围选择器封装
1. 代码
(1)html代码
import React, { FC, useCallback, forwardRef } from 'react';
import moment from 'moment';
import { DatePicker } from 'antd';
import { RangePickerValue, RangePickerProps } from 'antd/lib/date-picker/interface';
const { RangePicker } = DatePicker;
interface ComPonentProps {
style?: object;
value?: RangePickerValue;
onChange?: RangePickerProps['onChange'];
}
type Props = ComPonentProps;
const TimePicker: FC<Props> = ({ style, onChange, value }, ref): React.ReactElement => {
//设置今天以后的日期不可选
const disabledStartDate = useCallback(current => {
return current && current > moment().endOf('day');
}, []);
return (
<>
<RangePicker
style={style}
showTime={{ format: 'YYYY-MM-DD HH:mm:ss' }}
format="YYYY-MM-DD HH:mm:ss"
placeholder={['起始时间', '结束时间']}
disabledDate={disabledStartDate}
onChange={onChange}
ref={ref}
value={value}
/>
</>
);
};
export default forwardRef(TimePicker);
2. 使用方法
(1)单独使用
通过onChange函数就可以拿到数据
<col span={6}>
<label>时间选取:</label>
<TimePicker onChange={handleOnchange} />
</col>
(2)在form表单中使用
这里不使用onChange函数,可以使用form的onSubmit/onFinish函数就可以拿到起始时间和结束时间
<Form.Item label={'时间范围'} >
{getFieldDecorator(`group`, {
initialValue: group,//默认数据
})(
<TimePicker/>
)}
</Form.Item>
三、form表单封装
1. 代码
(1)html
import React, { FC, useCallback, useRef, ReactNode } from 'react';
import { ActionItem } from './form-types';
import { Input, Select, Button } from 'antd';
import Form, { FormComponentProps } from 'antd/lib/form';
import TimePicker from 'components/time-picker';
import TreePicker from 'components/tree-picker';
import styles from './form-basic.module.scss';
const { Option } = Select;
interface ComponentProps {
actions: ActionItem[];
onSubmit(value?: ActionItem['value']): void;
render?(value?: ReactNode): void;
method?: string;
}
type Props = FormComponentProps & ComponentProps;
const FormCustom: FC<Props> = ({ actions, method, form, children, onSubmit, render }): JSX.Element => {
const { getFieldDecorator, setFieldsValue } = form;
const TimePickerRef = useRef(null);
const TreePickerRef = useRef(null);
const renderForm = useCallback(
(item, index) => {
switch (item.type) {
case 'input':
return (
<Form.Item key={index} label={item.label}>
{getFieldDecorator(`${item.name}`, {
initialValue: item.value || '',
rules: [{ required: item.required, message: `请输入${item.label}` }],
})(<Input placeholder={item.placeholder || `请输入${item.label}`} style={item.style} />)}
</Form.Item>
);
case 'select':
return (
<Form.Item label={item.label} key={index}>
{getFieldDecorator(`${item.name}`, {
initialValue: item.value || '',
rules: [{ required: item.required, message: `请选择${item.label}` }],
})(
<Select style={item.style} placeholder={item.placeholder || `请选择${item.label}`}>
{item.options.map((o: any, i: any) => {
return (
<Option key={i} value={o.value}>
{o.label}
</Option>
);
})}
</Select>,
)}
</Form.Item>
);
case 'timePicker':
return (
<Form.Item label={item.label} key={index}>
{getFieldDecorator(`${item.name}`, {
initialValue: item.value,
})(<TimePicker style={item.style} ref={TimePickerRef} />)}
</Form.Item>
);
case 'treePicker':
return (
<Form.Item label={item.label} key={index} className={styles['tree-basic']}>
{getFieldDecorator(`${item.name}`, {
initialValue: item.value,
})(
<TreePicker
style={item.style}
ref={TreePickerRef}
treeData={item.treeOptions}
placeholder={item.placeholder || `请选择${item.label}`}
/>,
)}
</Form.Item>
);
}
},
[getFieldDecorator],
);
const handleSubmit: React.FormEventHandler<HTMLFormElement> = useCallback(
(e): void => {
e.preventDefault();
form.validateFields((err, values) => {
if (!err) {
onSubmit(values);
}
});
},
[form, onSubmit],
);
return (
<div>
<Form layout="inline" onSubmit={handleSubmit}>
{actions.map((item: ActionItem, index: number) => {
return renderForm(item, index);
})}
{children}
<Form.Item>
<Button type="primary" htmlType="submit">
{method ? method : '搜索'}
</Button>
</Form.Item>
{render && render()}
</Form>
</div>
);
};
export default FormCustom;
(2)数据结构
import { MarketList } from 'utils/convertToTreeData';//前面已经给了
import moment from 'moment';
type ValueForm = string | null | undefined | moment.Moment;
export interface ActionItem {
type: string;
name: string;
label: string;
value: ValueForm | ValueForm[];
min?: number;
max?: number;
required?: boolean;
placeholder?: string;
options?: ActionOptionsItem[];
treeOptions?: MarketList[];
style?: {};
}
export interface ActionOptionsItem {
value: string;
label: string;
}
(3)css
使用的css较少
.tree-basic {
margin-top: 4px;
}
2. 使用方法
(1)导入该组件
<FormCustom
onSubmit={handleSubmit} //提交表单的方法
form={form}
actions={formData} //显示该表单的遍历数据
render={(): ReactNode => {
return (
//在这里可以在搜索的button后面增加其他的button,不会影响美观【跑到搜索button前面】
<Form.Item>
<Button type="primary" style={{ marginLeft: 8 }} onClick={handleImportBtnClick}>
我是其他的button
</Button>
</Form.Item>
);
}}
>
//在这里可以增加其他表单
<Form.Item label={'我是其他表单'}>
{getFieldDecorator('xxx', {
rules: [{ required: true, message: '请选择' }],
})(
<Select style={{ width: 130 }} placeholder={'请选择'} onChange={handleBizCodeChange}>
{groups.map(
(item): JSX.Element => {
return (
<Option value={item.value} key={item.key}>
{item.name}
</Option>
);
},
)}
</Select>,
)}
</Form.Item>
</FormCustom>
(2)表单提交方法
a. 使用了时间选择框
需要将时间选择不以数据形式展示的,需要拿出来。【对应于上面,如果时间选择框在form.item中使用一样】
const handleSubmit = (values: any): void => {
const {
timeRange: [from, to],
...rest
} = values;
console.log({
...rest,
from: from ? moment(from._d).format('YYYY-MM-DD HH:mm:ss') : '',
to: to ? moment(to._d).format('YYYY-MM-DD HH:mm:ss') : '',
});
};
b. 没有使用时间选择框
直接可以拿到数据
const handleSubmit = (values: any): void => {
console.log(values)
};
(3)formData数据
//ActionItem上面有定义
const formData: ActionItem[] = [
{
type: 'input',
label: '姓名',
value: name || '',
name: 'name',
},
{
type: 'select',
label: '下拉框',
value: limitStatus || 'permit',
name: 'limitStatus',
options: STATUS_TYPES,
style: { width: '80px' },
},
{
type: 'timePicker',
label: '时间选择框',
value: [timeJudge(from as string), timeJudge(to as string)],
name: 'timeRange',
},
{
type: 'treePicker',
label: '树形选择框',
//判断数据是否存在,如果转成字符串,则需要转会来
//value: (limitType as string) ? (limitType as string).split(',') : [],
//没有转成字符串则不需要,只需要判断是否为空就行
value: limitType || [],
name: 'limitType',
treeOptions: LIMIT_TYPE,
style: { width: '200px' },
},
];
//下拉框数据格式
const STATUS_TYPES = [
{
value: '',
label: '全部',
},
{
value: '111',
label: '111',
},
{
value: '222',
label: '222',
},
];
(4)其他
有timeJudge函数,是我自己写的,用来判断初始值是否为空字符串
import moment from 'moment';
export const timeJudge = (value: string): any => {
if (value && value.length > 0) {
return moment(value);
} else {
return undefined;
}
};
综上,完成form表单封装,实践中就可以用了