实现
1. 实现所有表单项的非空校验、取值、赋值、提交、重置功能
2. 两种提交方式:实时根据输入的内容提交表单、点击提交按钮提交表单
3. 两种不同表单样式
4. 基础按钮和自定义按钮(可以自定义文字、颜色、事件)
效果
搜索表单示例
提交表单示例
包含
Input 输入框
使用 elements 的 Input 组件,集成 Formik 和 Yup 表单校验。
Select 选择框
使用 react-native-dropdown-picker 第三方实现,现在多选 multiple 的选择结果值有问题,仍是单选结果。
Checkbox 复选框
自己用 View 手写了一个,返回数组形式。
日期/时间选择器
使用 react-native-modal-datetime-picker 第三方组件,只能选择年月日,时间不进行处理的话是默认当前时间(为了适应后端接口需要,我这里设置了个 initTime ,可以手动将时间设置为 00:00:00 或者 23:59:59)。
自定义表单项(类似于 vue 中的插槽)
在 formItems 配置只需在 slot 传入组件名称,不需要传入整个组件,需要在 SlotComponent 文件中引入组件。(这里不知道为什么直接传入组件在 form 表单读不到,明明写其他组件的时候可以......)
封装代码(含使用说明)
代码能用,但感觉有点小乱,后面有时间再优化下。
配置示例
/** 搜索表单 配置示例:
* const formItems = [
* {
* type: 'input',
* key: 'key',
* label: '标签',
* value: '',
* leftIcon: 'Search',
* showLabel: true, // 是否显示label
* },
* {
* type: 'checkbox',
* key: 'key',
* label: '标签',
* options: options,
* multiple: true, // 是否多选
* showLabel: true, // 是否显示label
* }
* ];
* let whole = {
* row: 1, // 一行放多少个输入框(1/2/3) 默认全部放一行
* showClose: true, // 是否显示 右侧清空输入框图标
* }
*/
/** 提交表单 配置示例:
* const formItems = [
* {
* type: 'input',
* key: 'key',
* label: '标签',
* value: '',
* required: true, // 是否必填
* showClose: true, // 单独一项是否显示 右侧清空输入框图标
* showLabel: true, // 是否显示label
* isSubmitForm: true, // 是否为提交表单,与搜索表单部分样式不一样
* disabled: true
* },
* {
* type: 'checkbox',
* key: 'key',
* label: '标签',
* options: options,
* multiple: true, // 是否多选
* showLabel: true, // 是否显示label
* required: true, // 是否必填
* showClose: true, // 单独一项是否显示 右侧清空输入框图标
* showLabel: true, // 是否显示label
* isSubmitForm: true, // 是否为提交表单,与搜索表单部分样式不一样
* disabled: true
* }
* ];
* let whole = {
* row: 1, // 一行放多少个输入框(1/2/3) 默认全部放一行
* showClose: true, // 是否显示 右侧清空输入框图标
* buttons: {
* show: true, // 是否显示按钮(不包括自定义按钮):为true显示按钮;为false不显示按钮
* isSubmit: true, // 是否为提交表单:为true,按下按钮才触发onForm;为false,表单项的值改变就会触发onForm
* submitText: '搜索', // 提交按钮文本,默认为 提交
* hideBaseButton: true,
* // 自定义按钮,点击执行 onForm 方法
* customButton: [
* {
* text: '通过',
* onPress: () => submit(1)
* },
* {
* text: '拒绝',
* color: '#CF4343',
* onPress: () => submit(0)
* }
* ],
* position: 'left', // 按钮位置 left/right/center,默认right
* }
* }
*/
Form.tsx
/**
* 使用:
* <Form formItems={formItems} whole={whole} onForm={onForm} disabled={false} ref={formRef} />
* 表单项配置
* formItems 每一项是一个输入框的配置 (type, key, label, value 必传)
* let formItems = [
* {
* type: 'input',
* key: 'username',
* label: '用户名',
* value: 'text',
* required: true, // 是否必填
* leftIcon: 'Search', // 左侧图标
* showClose: true, // 单独一项是否显示 右侧清空输入框图标
* showLabel: true, // 是否显示label
* isSubmitForm: true, // 是否为提交表单,与搜索表单部分样式不一样
* },
* {
* type: 'textarea',
* key: 'suggest',
* label: '建议',
* value: '',
* required: true, // 是否必填
* showClose: true, // 单独一项是否显示 右侧清空输入框图标
* showLabel: true, // 是否显示label
* isSubmitForm: true, // 是否为提交表单,与搜索表单样式不一样
* },
* // 支持 单选
* {
* type: 'select',
* key: 'gender',
* label: '性别',
* options: [],
* showLabel: true, // 是否显示label
* isSubmitForm: true, // 是否为提交表单,与搜索表单样式不一样
* },
* // 支持 单选/多选
* {
* type: 'checkbox',
* key: 'selectList',
* label: '选择',
* options: selectList,
* multiple: true, // 是否多选
* showLabel: true, // 是否显示label
* },
* {
* type: 'dateTime',
* key: 'startTime',
* label: '开始日期',
* value: '',
* initTime: true // 是否为固定时间,key 中包含 start 的调整为 00:00:00,包含 end 的调整为 23:59:59
* },
* {
* type: 'slot',
* key: 'nameSlot',
* label: '选择名称',
* slot: 'SelectPeople', // 组件名称需要引入到 SlotComponent 文件中
* },
* ];
* 整体配置
* let whole = {
* row: 1, // 一行放多少个输入框(1/2/3) 默认全部放一行
* showClose: true, // 是否显示 右侧清空输入框图标
* buttons: {
* show: true, // 是否显示按钮(不包括自定义按钮):为true显示按钮;为false不显示按钮
* isSubmit: true, // 是否为提交表单:为true,按下按钮才触发onForm;为false,表单项的值改变就会触发onForm
* submitText: '搜索', // 提交按钮文本,默认为 提交
* hideBaseButton: true,
* // 自定义按钮,点击执行 onForm 方法
* customButton: [
* {
* text: '通过',
* onPress: () => submit(1)
* },
* {
* text: '拒绝',
* color: '#CF4343',
* onPress: () => submit(0)
* }
* ],
* position: 'left', // 按钮位置 left/right/center,默认right
* }
* }
*
* const onForm = (e: any) => {
* console.log('获取表单的值', e)
* }
*
* 调用赋值方法:formRef.current.setFormValue(form)
*/
import React, {
useEffect,
useRef,
useState,
forwardRef,
useImperativeHandle,
} from 'react';
import {Text, View, StyleSheet, Platform, TouchableOpacity} from 'react-native';
import {Input} from '@rneui/themed';
import * as icons from 'react-native-feather';
import {Calendar} from 'react-native-feather';
import {Formik, useFormikContext} from 'formik';
import * as Yup from 'yup';
import DropDownPicker from 'react-native-dropdown-picker';
import DateTimePickerModal from 'react-native-modal-datetime-picker';
import mainStyles from '@/style/css';
import {parseTime} from '@/utils/timeTool';
import {cloneDeep} from 'lodash';
import SlotComponent from './SlotComponent';
// 表单项配置
const FormInput = forwardRef((props: any, ref: any) => {
const [formItems, setFormItems] = useState(props?.formItems);
let {whole, onForm, disabled} = props;
let buttons = whole?.buttons ? whole?.buttons : {};
const {values}: any = useFormikContext();
const inputItemRef = useRef(null);
const selectRef = useRef(null);
const checkboxRef = useRef(null);
const dateTimePickerRef = useRef(null);
const slotItemRef = useRef(null);
// 表单赋值回显
const setFormItemValue = (form: any) => {
const copyFormItems = formItems.map((v: any) => {
if (v.key && form.hasOwnProperty(v.key)) {
v.value = form[v.key];
if (v.type === 'input' || v.type === 'textarea') {
if (inputItemRef.current) {
inputItemRef.current.setInputValue(v.key);
}
} else if (v.type === 'checkbox') {
if (checkboxRef.current) {
checkboxRef.current.setCheckbox(v.value);
}
}
}
return v;
});
setFormItems(copyFormItems);
};
// 使用 useImperativeHandle 将里面的方法暴露给父组件
useImperativeHandle(ref, () => ({
setFormItemValue,
getFormItemValue,
}));
// 获取表单所有值
const getFormItemValue = (params: any) => {
let requiredArr: any = [];
formItems.map((v: any) => {
if (v.required) {
if (
(v.type === 'input' || v.type === 'textarea') &&
inputItemRef.current
) {
requiredArr.push(inputItemRef.current.submit());
} else if (v.type === 'checkbox' && checkboxRef.current) {
requiredArr.push(checkboxRef.current.checkboxRequired(v.key));
} else if (v.type === 'dateTime' && dateTimePickerRef.current) {
requiredArr.push(dateTimePickerRef.current.dateTimeRequired(v.key));
} else if (v.type === 'slot' && slotItemRef.current) {
requiredArr.push(slotItemRef.current.slotRequired(v.key));
}
}
});
Promise.all(requiredArr).then(() => {
let obj: any = {};
formItems.map((v: any) => {
obj[v.key] = v.value;
});
// 回传给父组件
if (buttons?.customButton && buttons?.customButton.length > 0)
onForm({...obj, ...values}, params);
else onForm({...obj, ...values});
});
};
// 重置表单
const resetForm = () => {
let copyFormItems: any = cloneDeep(formItems || []);
copyFormItems.map((v: any) => {
if (v.type === 'input' || v.type === 'textarea') {
if (inputItemRef.current) {
inputItemRef.current.cleanInput(v.key);
}
} else if (v.type === 'checkbox') {
if (checkboxRef.current) {
checkboxRef.current.cleanCheckbox(v.key);
}
} else if (v.type === 'dateTime') {
if (dateTimePickerRef.current) {
dateTimePickerRef.current.cleanDateTime(v.key);
}
} else {
v.value = '';
}
});
setFormItems(copyFormItems);
};
useEffect(() => {
resetForm();
}, []);
useEffect(() => {
if (!buttons || !buttons.isSubmit) getFormItemValue();
}, [formItems]);
useEffect(() => {
if (!buttons || !buttons.isSubmit) getFormItemValue();
}, [values]);
let RightIcon = icons['X'];
const formItem = formItems.map((v: any, i: number) => {
return (
<View
style={
whole?.row == 1
? [styles.rowView, {minWidth: '98%'}]
: whole?.row == 2
? [styles.rowView, {minWidth: '48%'}]
: whole?.row == 3
? [styles.rowView, {minWidth: '32%'}]
: styles.rowView
}>
{v?.type === 'input' || v?.type === 'textarea' ? (
<InputItem
item={v}
whole={whole}
values={values}
methods={useFormikContext()}
RightIcon={RightIcon}
disabled={disabled}
ref={inputItemRef}
/>
) : v?.type === 'select' ? (
<SelectItem
item={v}
formItems={formItems}
i={i}
buttons={buttons}
setFormItems={setFormItems}
getFormItemValue={getFormItemValue}
disabled={disabled}
ref={selectRef}
/>
) : v?.type === 'checkbox' ? (
<CheckboxItem
item={v}
formItems={formItems}
buttons={buttons}
setFormItems={setFormItems}
getFormItemValue={getFormItemValue}
disabled={disabled}
ref={checkboxRef}
/>
) : v?.type === 'dateTime' ? (
<DateTimePickerItem
item={v}
formItems={formItems}
buttons={buttons}
setFormItems={setFormItems}
getFormItemValue={getFormItemValue}
disabled={disabled}
ref={dateTimePickerRef}
/>
) : v?.type === 'slot' ? (
<SlotItem
item={v}
formItems={formItems}
buttons={buttons}
setFormItems={setFormItems}
getFormItemValue={getFormItemValue}
disabled={disabled}
ref={slotItemRef}
/>
) : (
<></>
)}
</View>
);
});
return (
<View>
<View style={styles.inputItemView}>{formItem}</View>
{buttons?.show && !disabled ? (
<View
style={[
styles.buttonView,
buttons?.position === 'left'
? {justifyContent: 'flex-start'}
: buttons?.position === 'center'
? {}
: {justifyContent: 'flex-end'},
]}>
{!buttons?.hideBaseButton ? (
<TouchableOpacity onPress={() => resetForm()}>
<Text style={mainStyles.formWhiteButton}>重置</Text>
</TouchableOpacity>
) : (
<></>
)}
{buttons?.customButton && buttons?.customButton.length > 0 ? (
buttons?.customButton.map((v: any) => {
return (
<TouchableOpacity
onPress={v?.onPress ? v?.onPress : getFormItemValue}
style={{marginLeft: 4}}>
<Text
style={[
mainStyles.formBlueButton,
v?.color
? {
borderColor: v?.color,
backgroundColor: v?.color,
}
: {},
]}>
{v?.text}
</Text>
</TouchableOpacity>
);
})
) : (
<View>
{!buttons?.hideBaseButton ? (
<TouchableOpacity
onPress={getFormItemValue}
style={{marginLeft: 4}}>
<Text style={mainStyles.formBlueButton}>
{buttons.submitText ? buttons.submitText : '提交'}
</Text>
</TouchableOpacity>
) : (
<></>
)}
</View>
)}
</View>
) : (
<></>
)}
</View>
);
});
// 输入框组件
const InputItem = forwardRef((props: any, ref: any) => {
const {item, methods, values, whole, RightIcon, disabled} = props;
const {
setValues,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
}: any = methods;
// 赋值
const setInputValue = (key: string) => {
setValues({[key]: item.value});
};
// 清空输入框
const cleanInput = (key: string) => {
setValues({...values, [key]: ''});
};
useEffect(() => {
// 最开始执行一次清空输入框操作,确保在没有编辑输入框的情况下,表单校验也生效
cleanInput(item.key);
}, []);
useImperativeHandle(ref, () => ({
cleanInput,
setInputValue,
submit,
}));
const submit = () => {
return new Promise((resolve, reject) => {
handleSubmit();
if (errors && Object.keys(errors).length > 0) {
reject(errors);
return;
} else {
resolve(true);
}
});
};
// 动态引入图标
let LeftIcon = icons[item?.leftIcon];
return (
<View>
{item?.showLabel ? (
<Text>
<RequiredView required={item?.required} />
<Text style={styles.labelText}>{item.label}</Text>
</Text>
) : (
<></>
)}
<View
style={[
styles.inputView,
item.isSubmitForm ? styles.greyBorder : styles.blueBorder,
]}>
<Input
value={values[item.key]}
onChangeText={handleChange(item.key)}
placeholder={item?.placeholder ? item.placeholder : item?.label}
errorMessage={''}
inputContainerStyle={{borderBottomWidth: 0, borderColor: '#7FABE7'}}
inputStyle={styles.input}
containerStyle={
item?.type === 'textarea'
? {
marginLeft: -13,
marginRight: -12,
marginBottom: -15,
paddingTop: 5,
maxHeight: 60,
}
: {height: 28, margin: -12, marginLeft: -13}
}
leftIcon={
LeftIcon ? (
<LeftIcon stroke="#7F7F8E" width={16} height={16} />
) : (
<></>
)
}
rightIcon={
(item?.showClose || whole?.showClose) && !disabled ? (
<RightIcon
stroke="#7F7F8E"
width={16}
height={16}
style={{marginRight: -15}}
onPress={() => cleanInput(item.key)}
/>
) : (
<></>
)
}
multiline={item?.type === 'textarea'}
numberOfLines={item?.type === 'textarea' ? 4 : 1}
disabled={disabled}
/>
</View>
{touched[item.key] && errors[item.key] && (
<Text style={styles.errorText}>{errors[item.key]}</Text>
)}
</View>
);
});
// 下拉框组件 单选
const SelectItem = forwardRef((props: any, ref: any) => {
let {item, formItems, i, buttons, setFormItems, getFormItemValue, disabled} =
props;
const [copyFormItems, setCopyFormItems] = useState(formItems);
const [copyItem, setCopyItem] = useState(item);
// 非空校验
const selectRequired = (key: string) => {
return new Promise((resolve, reject) => {
if (item?.required) {
let copy = copyFormItems.map((v: any) => {
if (key === v.key) {
if (!v.value) {
v.showError = true;
reject(v.label + '不能为空');
} else {
v.showError = false;
resolve(true);
}
setCopyItem({...v});
return {...v};
}
return v;
});
setCopyFormItems(copy);
} else {
let copy = copyFormItems.map((v: any) => {
if (key === v.key) {
v.showError = false;
setCopyItem({...v});
return {...v};
}
return v;
});
setCopyFormItems(copy);
resolve(true);
}
});
};
// 打开下拉选项
const handleOpenChange = (index: number, isOpen: boolean) => {
const updatedCopy = copyFormItems.map((item: any, i: number) => {
if (i === index) {
return {...item, open: isOpen};
}
return item;
});
setCopyFormItems(updatedCopy);
};
// 获取选定值
const handleSelectChange = (index: number, val: any) => {
const updatedCopy = copyFormItems.map((item: any, i: number) => {
if (i === index) {
item.value = val();
return {...item, value: item.value};
}
return item;
});
setCopyFormItems(updatedCopy);
};
// 清空选定值
const cleanDropDownValue = () => {
const updatedCopy = copyFormItems.map((item: any) => {
return {...item, value: ''};
});
setCopyFormItems(updatedCopy);
};
useImperativeHandle(ref, () => ({
cleanDropDownValue,
selectRequired,
}));
useEffect(() => {
setFormItems(copyFormItems);
}, [copyFormItems]);
useEffect(() => {
if (!buttons || !buttons.isSubmit) getFormItemValue();
}, [formItems]);
return (
<View>
{item?.showLabel ? (
<Text>
<RequiredView required={item?.required} />
<Text style={styles.labelText}>{item.label}</Text>
</Text>
) : (
<></>
)}
<View
style={[
styles.inputView,
item.isSubmitForm ? styles.greyBorder : styles.blueBorder,
{zIndex: 10000 - i},
]}>
<DropDownPicker
open={item?.open}
value={item?.value}
items={item?.options}
setOpen={(isOpen: any) => handleOpenChange(i, isOpen)}
setValue={val => handleSelectChange(i, val)}
multiple={item?.multiple}
min={0}
max={item.maxSelect ? item.maxSelect : 5}
placeholder={item?.placeholder ? item.placeholder : item?.label}
style={{borderWidth: 0, backgroundColor: 'transparent'}}
textStyle={styles.placeholderText}
labelStyle={{color: '#7F7F8E', fontSize: 12}}
containerStyle={{height: 28, margin: -12}}
placeholderStyle={styles.placeholderText}
arrowIconStyle={{width: 14, height: 14}}
dropDownContainerStyle={styles.dropDownContainerStyle}
dropDownDirection="AUTO"
scrollViewProps={{
nestedScrollEnabled: true,
}}
disabled={disabled}
/>
</View>
{copyItem.showError ? (
<Text style={styles.errorText}>{copyItem.label}不能为空 </Text>
) : (
<></>
)}
</View>
);
});
// 多选组件
const CheckboxItem = forwardRef((props: any, ref: any) => {
let {item, formItems, buttons, setFormItems, getFormItemValue, disabled} =
props;
const [copyFormItems, setCopyFormItems] = useState(formItems);
const [copyItem, setCopyItem] = useState(item);
// 非空校验
const checkboxRequired = (key: string) => {
return new Promise((resolve, reject) => {
if (item?.required) {
let updatedCopy = copyFormItems.map((v: any) => {
if (key === v.key) {
if (
!v.value ||
v.value.length === 0 ||
(v.value.length === 1 && v.value[0] === '')
) {
v.showError = true;
reject(v.label + '不能为空');
} else {
v.showError = false;
resolve(true);
}
setCopyItem({...v});
return {...v};
}
return v;
});
setCopyFormItems(updatedCopy);
} else {
let copy = copyFormItems.map((v: any) => {
if (key === v.key) {
v.showError = false;
setCopyItem({...v});
return {...v};
}
return v;
});
setCopyFormItems(copy);
resolve(true);
}
});
};
// 获取选择的所有值
const changeCheckbox = (key: string, val: any, isChecked: boolean) => {
const updatedCopy = copyFormItems.map((v: any, i: number) => {
if (key === v.key) {
if (v.multiple) {
v.options.map((item: any) => {
if (item.value === val) item.checked = isChecked;
});
if (isChecked) {
let arr = [...v.value, val];
v.value = Array.from(new Set(arr));
} else {
v.value = v.value.filter((item: any) => item !== val);
}
} else {
v.options.map((item: any) => {
if (item.checked) item.checked = false;
if (item.value === val) item.checked = isChecked;
});
if (isChecked) {
v.value = val;
} else {
v.value = '';
}
}
}
return v;
});
setCopyFormItems(updatedCopy);
};
// 赋值回显
const setCheckbox = (val: any) => {
const updatedCopy = copyFormItems.map((v: any, i: number) => {
if (item.key === v.key) {
v.options.map((item: any) => {
if (typeof val === 'string') val = [val];
v.value = val;
val.map((p: string) => {
if (item.value === p) {
item.checked = true;
}
});
});
}
return v;
});
setCopyFormItems(updatedCopy);
};
// 清空选择的所有值
const cleanCheckbox = (key: string) => {
const updatedCopy = copyFormItems.map((v: any, i: number) => {
if (key === v.key) {
v.options.map((item: any) => {
item.checked = false;
});
if (v.multiple) v.value = [];
else v.value = '';
}
return v;
});
setCopyFormItems(updatedCopy);
};
useImperativeHandle(ref, () => ({
cleanCheckbox,
setCheckbox,
checkboxRequired,
}));
useEffect(() => {
setFormItems(copyFormItems);
}, [copyFormItems]);
useEffect(() => {
if (!buttons || !buttons.isSubmit) getFormItemValue();
}, [formItems]);
return (
<View>
{item?.showLabel ? (
<Text>
<RequiredView required={item?.required} />
<Text style={styles.labelText}>{item.label}</Text>
</Text>
) : (
<></>
)}
<View style={styles.checkboxListView}>
{item.options.map((v: any, i: number) => {
return (
<TouchableOpacity
onPress={() => changeCheckbox(item.key, v.value, !v.checked)}
disabled={disabled}>
<View style={styles.optionItem}>
<Text
style={
v.checked
? {
...styles.checkboxListItem,
backgroundColor: '#D6EFFF',
}
: {
...styles.checkboxListItem,
backgroundColor: '#F7F5F8',
}
}>
{v.label}
</Text>
</View>
</TouchableOpacity>
);
})}
</View>
{copyItem.showError ? (
<Text style={styles.errorText}>{copyItem.label}不能为空 </Text>
) : (
<></>
)}
</View>
);
});
// 时间选择组件
const DateTimePickerItem = forwardRef((props: any, ref: any) => {
let {item, formItems, buttons, setFormItems, getFormItemValue, disabled} =
props;
const [datePickerVisible, setDatePickerVisibility] = useState(false);
const [copyFormItems, setCopyFormItems] = useState(formItems);
const [copyItem, setCopyItem] = useState(item);
// 非空校验
const dateTimeRequired = (key: string) => {
return new Promise((resolve, reject) => {
if (item?.required) {
let copy = copyFormItems.map((v: any) => {
if (key === v.key) {
if (!v.value) {
v.showError = true;
reject(v.label + '不能为空');
} else {
v.showError = false;
resolve(true);
}
setCopyItem({...v});
return {...v};
}
return v;
});
setCopyFormItems(copy);
} else {
let copy = copyFormItems.map((v: any) => {
if (key === v.key) {
v.showError = false;
setCopyItem({...v});
return {...v};
}
return v;
});
setCopyFormItems(copy);
resolve(true);
}
});
};
// 确定按钮
const handleConfirm = (val: any) => {
let date = parseTime(val);
// 如果有initTime 设置固定时间 00:00:00 23:59:59
const copy = copyFormItems.map((v: any, i: number) => {
if (v.key === item.key) {
if (item?.initTime) {
if (item.key.includes('start')) {
let start = date?.substring(0, 10) + ' 00:00:00';
item.value = start;
} else if (item.key.includes('end')) {
let end = date?.substring(0, 10) + ' 23:59:59';
item.value = end;
}
} else {
item.value = date;
}
return {...item};
}
return v;
});
setCopyFormItems(copy);
setDatePickerVisibility(false);
};
// 清空所有值
const cleanDateTime = (key: string) => {
const updatedCopy = copyFormItems.map((v: any, i: number) => {
if (key === v.key) {
v.value = '';
}
return v;
});
setCopyFormItems(updatedCopy);
};
useImperativeHandle(ref, () => ({
dateTimeRequired,
cleanDateTime,
}));
useEffect(() => {
setFormItems(copyFormItems);
}, [copyFormItems]);
useEffect(() => {
if (!buttons || !buttons.isSubmit) getFormItemValue();
}, [formItems]);
return (
<View>
{item?.showLabel ? (
<Text>
<RequiredView required={item?.required} />
<Text style={styles.labelText}>{item.label}</Text>
</Text>
) : (
<></>
)}
<TouchableOpacity
onPress={() => setDatePickerVisibility(true)}
disabled={disabled}>
<View
style={[
styles.datePickerView,
item.isSubmitForm ? styles.greyBorder : styles.blueBorder,
]}>
<View>
<Text style={styles.placeholderText}>
{item.value
? item.value
: item?.placeholder
? item?.placeholder
: item.label}
</Text>
</View>
<Calendar stroke="#7F7F8E" width={16} height={16} />
<DateTimePickerModal
isVisible={datePickerVisible}
mode="date"
onConfirm={handleConfirm}
onCancel={() => setDatePickerVisibility(false)}
/>
</View>
</TouchableOpacity>
{copyItem.showError ? (
<Text style={styles.errorText}>{copyItem.label}不能为空 </Text>
) : (
<></>
)}
</View>
);
});
const SlotItem = forwardRef((props: any, ref) => {
let {item, formItems, setFormItems, buttons, getFormItemValue} = props;
let Children = SlotComponent[item.slot];
const [copyFormItems, setCopyFormItems] = useState(formItems);
const [copyItem, setCopyItem] = useState(item);
// 非空校验
const slotRequired = (key: string) => {
return new Promise((resolve, reject) => {
if (item?.required) {
let copy = copyFormItems.map((v: any) => {
if (key === v.key) {
if (v.value && Object.keys(v.value).length > 0) {
v.showError = false;
resolve(true);
} else {
v.showError = true;
reject(v.label + '不能为空');
}
setCopyItem({...v});
return {...v};
}
return v;
});
setCopyFormItems(copy);
} else {
let copy = copyFormItems.map((v: any) => {
if (key === v.key) {
v.showError = false;
setCopyItem({...v});
return {...v};
}
return v;
});
setCopyFormItems(copy);
resolve(true);
}
});
};
// 最终选择的数据
const resultData = (e: any) => {
const copy = copyFormItems.map((v: any) => {
if (v.key === item.key) {
item.value = e;
return {...item};
}
return v;
});
setCopyFormItems(copy);
};
useImperativeHandle(ref, () => ({
slotRequired,
}));
useEffect(() => {
setFormItems(copyFormItems);
}, [copyFormItems]);
useEffect(() => {
if (!buttons || !buttons.isSubmit) getFormItemValue();
}, [formItems]);
return (
<View>
{item?.showLabel ? (
<Text>
<RequiredView required={item?.required} />
<Text style={styles.labelText}>{item.label}</Text>
</Text>
) : (
<></>
)}
<Children navigation={item?.navigation} resultData={resultData} />
{copyItem.showError ? (
<Text style={styles.errorText}>{copyItem.label}不能为空 </Text>
) : (
<></>
)}
</View>
);
});
const RequiredView = (required: any) => {
return required ? <Text style={styles.requiredColor}>*</Text> : <></>;
};
const Form = forwardRef((props: any, ref) => {
const formInputRef = useRef(null);
let copyFormItems = cloneDeep(props?.formItems || []);
let disabled = props?.disabled;
// 需要校验的表单项
let fieldsToValidate: any = [];
let formItemValue: any = {};
copyFormItems.map((v: any) => {
if (v.type === 'input') {
formItemValue[v.key] = v.value;
} else if (v.type === 'select') {
v.open = false;
if (v.multiple) {
v.value = [];
} else {
v.value = '';
}
} else if (v.type === 'checkbox') {
if (v.multiple) {
v.value = [];
} else {
v.value = '';
}
}
v?.required &&
fieldsToValidate.push({key: v.key, label: v.label, type: v.type});
});
const validationSchema = Yup.object().shape(
fieldsToValidate.reduce((schema: any, field: any) => {
if (field.type === 'input' || field.type === 'textarea') {
return {
...schema,
[field.key]: Yup.string().required(field.label + '不能为空'),
};
}
}, {}),
);
const setFormValue = (form: any) => {
if (formInputRef.current && form) {
formInputRef.current.setFormItemValue(form);
}
};
const getFormValue = (params: any = null) => {
if (formInputRef.current) {
if (params || params === 0) formInputRef.current.getFormItemValue(params);
else formInputRef.current.getFormItemValue();
}
};
useImperativeHandle(ref, () => ({
setFormValue,
getFormValue,
}));
return (
<View style={styles.form}>
<Formik
initialValues={formItemValue}
onSubmit={values => {}}
validationSchema={validationSchema}>
<FormInput
formItems={copyFormItems}
whole={props?.whole}
onForm={props?.onForm}
ref={formInputRef}
disabled={disabled}
/>
</Formik>
</View>
);
});
const styles = StyleSheet.create({
form: {
backgroundColor: '#ffffff',
zIndex: 1000,
width: '100%',
},
rowView: {
flex: 1,
},
inputView: {
margin: 2,
padding: 0,
paddingBottom: 20,
paddingLeft: 10,
},
labelText: {
fontSize: 12,
color: '#333333',
},
greyBorder: {
borderWidth: 0.8,
borderRadius: 4,
borderColor: '#CFCCCF',
},
blueBorder: {
borderWidth: 1,
borderRadius: 50,
borderColor: '#7FABE7',
},
input: {
fontSize: 12,
padding: 0,
},
inputItemView: {
...mainStyles.centerFlexRow,
justifyContent: 'flex-start',
flexWrap: 'wrap',
marginTop: 6,
},
dropDownContainerStyle: {
borderWidth: 0,
marginTop: -8,
...Platform.select({
android: {
...mainStyles.androidShadow,
},
ios: {
...mainStyles.iosShadow,
},
}),
height: 120,
},
checkboxListView: {
...mainStyles.centerFlexRow,
justifyContent: 'flex-start',
flexWrap: 'wrap',
paddingTop: 4,
},
checkboxListItem: {
fontSize: 10,
color: '#333333',
paddingTop: 3,
paddingBottom: 3,
paddingLeft: 8,
paddingRight: 8,
borderRadius: 16,
},
optionItem: {
margin: 2,
},
datePickerView: {
margin: 2,
paddingTop: 4,
paddingBottom: 4,
paddingLeft: 10,
paddingRight: 10,
...mainStyles.centerFlexRow,
justifyContent: 'space-between',
},
placeholderText: {
fontSize: 12,
color: '#7F7F8E',
},
buttonView: {
...mainStyles.centerFlexRow,
marginTop: 10,
marginBottom: 10,
},
errorText: {
color: '#FF190C',
fontSize: 12,
marginLeft: 7,
},
requiredColor: {
color: '#FF190C',
},
});
export default Form;
SlotComponent.tsx
新加的自定义表单项都需要统一在这里引入。
import { SelectPeople } from '@/screens/MDT/FirstVisit/component/SelectPeople/Index';
export default {
SelectPeople
};