React Native 封装一个表单组件

实现

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
};

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值