设计思路:多维度动态表单怎么校验?【周计划】

167 阅读2分钟

一、效果

  1. 当点击提交时,先校验大表单
  2. 每个tab下有自己的小表单,点击提交,大表单校验通过,开始校验每个tab下的表单 动图.gif

二、如何设计?

  • 首先,需要一个存放子表单的容器subForms
  • 子表单要抽离出去,成为一个组件,子表单组件中定义一个校验自己的方法validate,将这个方法暴露给父组件
  • 在提交时,首先校验大表单,然后挨个校验子表单
  • 如果某个子表单校验没有通过,但是将这个子表单所属的tab删掉,那么这个表单不需要校验了,也就是要从subForms中删除

1、点击【提交】按钮

  • 首先校验大表单:form.validateFields()
  • 然后挨个校验每个tab的小表单:instance.validate(),这里的validate函数是自己定义的校验函数,所以我肯定需要一个subForms容器,用来放子表单
  const onCheck = async () => {
    try {
      const values = await form.validateFields();
      let subForm = [];
      let i = 0;
      try {
        for (let l = subForms.length; i < l; i++) {
          let instance = subForms[i].instance;
          let valid = await instance.validate();
          subForm.push(valid);
        }
        message.success("校验完成,打开console,查看数据");
        console.log("最后的数据是:", { ...values, subForm });
      } catch (err) {
        console.log(err);
        message.error(
          `第${err.order}个表单有必填项没填,${err.name}填写不正确`
        );
      }
    } catch (errorInfo) {
      message.error(`大表单表单有必填项没填`);
    }
  };

2、子表单validate函数的设计

子表单中也是用过antd的validateFields去校验表单,在父组件中通过collectForm函数可以获取到当前组件的实例,这就可以在onCheck函数中指定子表单校验有没有通过

  validate() {
    return new Promise((resolve, reject) => {
      this.formRef.current
        .validateFields()
        .then((res) => {
          resolve(res);
        })
        .catch((err) => {
          reject({
            ...err,
            order: this.props.order,
            name: this.props.name,
          });
        });
    });
  }
  componentDidMount() {
    this.props.collectForm({
      instance: this,
      id: this.props.id,
    });
    console.log(this.props.name, "did mount");
  }

父组件中收集子表单的collectForm函数,将子表单收集到subForms容器中

  const collectForm = (formInstance) => {
    setSubForms((prevArr) => {
      let tmep = { ...formInstance };
      let curDimensionsIds = prevArr.map((item) => item.id);
      if (!curDimensionsIds.includes(tmep.id)) {
        return [...prevArr, tmep];
      } else {
        return [...prevArr];
      }
    });
  };

3、操作【选择维度】

重置subForms

  const selectDimensionsHandler = (val) => {
    let arr = [...dimensions].filter((dimension) => val.includes(dimension.id));
    let _subForms = subForms.filter((item) => val.includes(item.id));
    setSubForms(_subForms);
    setTablist(arr);
  };

三、完整代码

App.js

import "./App.css";
import { Form,  Button, DatePicker, Select, Tabs, message } from "antd";
import { MyForm } from "./myForm";
import { useState } from "react";

const { Option } = Select;

const dimensions = [
  {
    id: 1,
    name: "维度一",
    form: [
      {
        type: 1, // 表单类型 1 输入框  2 下拉框  3 多选框  4 单选框
        label: "姓名", // 标签名称
        field: "name", // 字段名
        required: true, // 是否必填
        editable: true, // 是否可编辑
        value: "", // 表单值
      },
      {
        type: 2,
        label: "年龄",
        field: "age",
        required: false,
        editable: false,
        options: [
          { label: "20", value: 20 },
          { label: "21", value: 21 },
          { label: "22", value: 22 },
        ], // 下拉框 多选框 多选框特有, 组件选项数组
        value: "",
      },
      {
        type: 4,
        label: "性别",
        field: "gender",
        required: true,
        editable: true,
        options: [
          { label: "男", value: 1 },
          { label: "女", value: 2 },
        ],
        value: "",
      },
    ],
  },
  {
    id: 2,
    name: "维度二",
    form: [
      {
        type: 3,
        label: "爱好",
        field: "hobby",
        required: true,
        editable: true,
        options: [
          { label: "吃饭", value: 1 },
          { label: "睡觉", value: 2 },
          { label: "打豆豆", value: 3 },
        ],
        value: "",
      },
      {
        type: 1,
        label: "其他",
        field: "other",
        required: false,
        editable: true,
        value: "",
      },
    ],
  },
];

const App = () => {
  const [form] = Form.useForm();
  const [tablist, setTablist] = useState([]);
  const [subForms, setSubForms] = useState([]);

  // 子表单收集函数
  const collectForm = (formInstance) => {
    setSubForms((prevArr) => {
      let tmep = { ...formInstance };
      let curDimensionsIds = prevArr.map((item) => item.id);
      if (!curDimensionsIds.includes(tmep.id)) {
        return [...prevArr, tmep];
      } else {
        return [...prevArr];
      }
    });
  };

  const onCheck = async () => {
    try {
      const values = await form.validateFields();
      let subForm = [];
      let i = 0;
      try {
        for (let l = subForms.length; i < l; i++) {
          let instance = subForms[i].instance;
          let valid = await instance.validate();
          subForm.push(valid);
        }
        message.success("校验完成,打开console,查看数据");
        console.log("最后的数据是:", { ...values, subForm });
      } catch (err) {
        console.log(err);
        message.error(
          `第${err.order}个表单有必填项没填,${err.name}填写不正确`
        );
      }
    } catch (errorInfo) {
      message.error(`大表单表单有必填项没填`);
    }
  };

  const selectDimensionsHandler = (val) => {
    let arr = [...dimensions].filter((dimension) => val.includes(dimension.id));
    let _subForms = subForms.filter((item) => val.includes(item.id));
    setSubForms(_subForms);
    setTablist(arr);
  };

  return (
    <main className="container">
      {/* 表单 */}
      <Form form={form} name="dynamic_rule">
        <Form.Item
          name="date"
          label="选择时间"
          rules={[{ required: false, message: "请选择时间" }]}
        >
          <DatePicker placeholder="请选择时间" />
        </Form.Item>
        <Form.Item
          name="dimensions"
          label="选择维度"
          rules={[{ required: true, message: "请选择维度" }]}
        >
          <Select
            mode="multiple"
            allowClear
            placeholder="请选择维度"
            onChange={selectDimensionsHandler}
          >
            {dimensions.map((item, i) => {
              return (
                <Option key={item.id} value={item.id}>
                  {item.name}
                </Option>
              );
            })}
          </Select>
        </Form.Item>
      </Form>
      {/* tab页 */}
      <Tabs defaultActiveKey="0">
        {tablist.map((dimension, idx) => {
          return (
            <Tabs.TabPane forceRender={true} tab={dimension.name} key={dimension.id}>
              <MyForm
                form={dimension.form}
                collectForm={collectForm}
                order={idx + 1}
                id={dimension.id}
                name={dimension.name}
              ></MyForm>
            </Tabs.TabPane>
          );
        })}
      </Tabs>

      {/* 提交按钮 */}
      <Button type="primary" onClick={onCheck}>
        提交
      </Button>
    </main>
  );
};

export default App;

MyForm.js

import React from "react";
import { Form, Input, Radio, Select, Checkbox } from "antd";
const { Option } = Select;

const FormItem = (props) => {
  const { item } = props;
  return (
    <Form.Item
      name={item.field}
      label={item.label}
      rules={[{ required: item.required, message: item.label + "不能为空" }]}
    >
      {(() => {
        switch (item.type) {
          case 2:
            return (
              <Select disabled={!item.editable}>
                {item.options.map((option, i) => {
                  return (
                    <Option key={i} value={option.value}>
                      {option.label}
                    </Option>
                  );
                })}
              </Select>
            );

          case 3:
            return (
              <Checkbox.Group
                disabled={!item.editable}
                options={item.options}
              />
            );

          case 4:
            return (
              <Radio.Group disabled={!item.editable}>
                {item.options.map((option, i) => {
                  return (
                    <Radio key={i} value={option.value}>
                      {option.label}
                    </Radio>
                  );
                })}
              </Radio.Group>
            );

          default:
            return <Input disabled={!item.editable} />;
        }
      })()}
    </Form.Item>
  );
};
/* */
export class MyForm extends React.Component {
  constructor(props) {
    super(props);
    this.formRef = React.createRef();
  }
  validate() {
    return new Promise((resolve, reject) => {
      this.formRef.current
        .validateFields()
        .then((res) => {
          resolve(res);
        })
        .catch((err) => {
          reject({
            ...err,
            order: this.props.order,
            name: this.props.name,
          });
        });
    });
  }
  componentDidMount() {
    this.props.collectForm({
      instance: this,
      id: this.props.id,
    });
    console.log(this.props.name, "did mount");
  }
  render() {
    return (
      <Form ref={this.formRef}>
        {this.props.form.map((formItem, i) => {
          return <FormItem key={i} item={formItem}></FormItem>;
        })}
      </Form>
    );
  }
}