一、效果
- 当点击提交时,先校验大表单
- 每个tab下有自己的小表单,点击提交,大表单校验通过,开始校验每个tab下的表单
二、如何设计?
- 首先,需要一个存放子表单的容器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>
);
}
}