在elementui,antDesign,antDesignVue中表格嵌套多个表单项校验

前言

在此记录下使用 elementuiantDesignantDesignVue 的表格嵌套表单校验的一些经验。

要达到的目的是:有个多行表格,每一行有多个表单项(比如输入框),表单项填完值后,点击提交,校验表格中所有表单项,校验通过则将整个表格数据送到后台。还可以重新给表格赋值(比如进入详情页面)。效果如下:

正文

elementuielementPlus 使用方式一样,这里就只用 elementui 来举例,版本是 2.x

antDesignVue 1.x 版本有两种表单校验方式,v-decoratorFormModel ,要达到上面的效果需要用 FormModel 的方式,而后面 2.x 及以后的版本将两种方式合并,统一用 FormModel 的方式。

antDesign 我这里用的 3.x 版本,用的是 getFieldDecorator 方式,并且是函数式组件的写法。

需要注意的是如果使用的是 react@<15.3.0,则 getFieldDecorator 调用不能位于纯函数组件中。

elementui 用法

只需要将 prop 里面的属性和v-model表单数据引用保持一致即可,例如:

js 复制代码
:prop="'rows.'+scope.$index+'.age'"
v-model.trim="asdfform.rows[scope.$index].age"

页面结构

html 复制代码
<el-form :model="asdfform" ref="asdfform" label-width="20px">
    <el-table :data="asdfform.rows">
        <el-table-column prop="name" label="name"></el-table-column>
        <el-table-column prop="age" label="age">
            <template slot-scope="scope">
                <el-form-item label=" " :rules="[{required: true, message:'请输入'}]" :prop="'rows.'+scope.$index+'.age'">
                    <el-input v-model.trim="asdfform.rows[scope.$index].age"></el-input>
                </el-form-item>
            </template>
        </el-table-column>
        <el-table-column prop="address" label="address">
            <template slot-scope="scope">
                <el-form-item label=" " :rules="[{required:true,message:'请输入'}]" :prop="'rows.'+ scope.$index + '.address'">
                    <el-input v-model.trim="asdfform.rows[scope.$index].address"></el-input>
                </el-form-item>
            </template>
        </el-table-column>
    </el-table>
</el-form>
<el-button type="primary" size="small" @click="handleSubmit">表单提交</el-button>
<el-button type="danger" size="small" @click="resetTableData">重新给表格赋值</el-button>

数据和方法定义

js 复制代码
data() {
    return {
      asdfform: {
        rows: [
          { key: 1, name: `Edrward ${1}`, age: "1234", address: "1234" },
          { key: 2, name: `Edrward ${2}`, age: "1234", address: "1234" },
          { key: 3, name: `Edrward ${3}`, age: "", address: "" },
        ],
      },
    };
  },
  methods: {
    handleSubmit() {
      this.$refs.asdfform.validate((valid) => {
        console.log('表格数据',JSON.parse(JSON.stringify(this.asdfform)));
        if (valid) {
        }
      });
    },
    resetTableData() {
      this.asdfform.rows = [
        { key: 1, name: `Edrward ${1}`, age: "1234", address: "1234" },
        { key: 2, name: `Edrward ${2}`, age: "1234", address: "1234" },
        { key: 3, name: `Edrward ${3}`, age: "", address: "" },
        { key: 4, name: `Edrward ${4}`, age: "4", address: "44" },
      ];
    },
  },

完整示例

antDesignVue 1.x 用法

如果用的是FormModel 方式,那么写法和 elementui 中一样,不过需要先引入

js 复制代码
import { FormModel } from 'ant-design-vue'
Vue.use(FormModel)

页面中使用

html 复制代码
<a-form-model :model="asdfform" ref="asdfform">
    <a-table
        :columns="asdfcolumns"
        :dataSource="asdfform.rows"
        bordered
        :rowKey="(record,index)=>index"
    >
        <template slot="age" slot-scope="text,record, index">
            <a-form-model-item
                label=" "
                :prop="'rows.'+index+'.age'"
                :rules=" [{required: true, message:'请输入'}]"
            >
                <a-input v-model="asdfform.rows[index].age" />
            </a-form-model-item>
        </template>
        <template slot="address" slot-scope="text,record,index">
            <a-form-model-item
                label=" "
                :prop="'rows.'+index+'.address'"
                :rules="[{required: true, message:'请输入' }]"
            >
                <a-input v-model="asdfform.rows[index].address" />
            </a-form-model-item>
        </template>
    </a-table>
    <a-form-item>
        <a-button type="primary" @click="handleSubmit">Submit</a-button>
    </a-form-item>
</a-form-model>
js 复制代码
data: function () {
    return {
        asdfform: {
            rows: [
                { key: 1, name: `Edrward ${1}`, age: '1234', address: '1234' },
                { key: 2, name: `Edrward ${2}`, age: '1234', address: '1234' },
                { key: 3, name: `Edrward ${3}`, age: '', address: '' },
            ]
        },
        asdfcolumns: [
            { title: 'name', dataIndex: 'name', width: '25%' },
            { 
                title: 'age', 
                dataIndex: 'age',
                width: '15% ',
                scopedSlots: { customRender: 'age' }
            },
            { 
                title: 'address',
                dataIndex: 'address',
                width: '40%',
                scopedSlots: { customRender: 'address' }
            },
            { title: 'operation', dataIndex: 'operation' },
        ],
    }
},
methods: {
    handleSubmit() {
        this.$refs.asdfform.validate(valid => {
            console.log(this.asdfform)
            if (valid) { }
        });
    }
}

antDesign 3.x 用法

react 中没有 v-model 的用法,这里通过 setState + useEffect 来实现。

vue 中使用 asdfform 定义表单数据,asdfform.rows 定义表格数据,也就是表格数据是嵌套在表单数据里面的,所以表单项改变,表格数据也会同步改变。

react 中表格数据和表单数据的分开维护的,所以当表格数据改变时,需要在 useEffect 里面同步表单数据,同样表单数据变化后,提交函数中也会创建一份新的表格数据。

1、定义基本结构

这里使用了 Form.creategetFieldDecorator 的方式校验表单。

Form.create() 包装过的组件会自带 this.props.form 属性。

使用 Form.create 处理后的表单具有自动收集数据并校验的功能,但如果您不需要这个功能,或者默认的行为无法满足业务需求,可以选择不使用 Form.create 并自行处理数据。

js 复制代码
const App = ({ form }) => {

    const { getFieldDecorator, setFieldsValue, validateFields } = form;
    
    // 定义表格数据
    const [data, setData] = useState([
        { key: 1, name: `Edrward ${1}`, age: "1234", address: "1234" },
        { key: 2, name: `Edrward ${2}`, age: "1234", address: "1234" },
        { key: 3, name: `Edrward ${3}`, age: "", address: "" },
    ]);

    return (
        <>
          <Form>
            <Table
              columns={asdfcolumns}
              dataSource={data}
              pagination={false}
              rowKey="key"
            ></Table>
            <Form.Item>
              <Button type="primary" onClick={handleSubmit}>
                Submit
              </Button>
              <Button onClick={handleReset}>reset</Button>
            </Form.Item>
          </Form>
        </>
    );
}

const WrappedMyFormComponent = Form.create()(App);
export default WrappedMyFormComponent;

2、添加表格列,根据需要自定义列内容

js 复制代码
const asdfcolumns = [
    { title: "name", dataIndex: "name", width: "25%" },
    {
      title: "age",
      dataIndex: "age",
      width: "15%",
      render: (text, record, index) => (
        <Form.Item>
          {getFieldDecorator(`data.${index}.age`, {
            rules: [{ required: true, message: "请输入" }],
            initialValue: record["age"],
          })(<Input />)}
        </Form.Item>
      ),
    },
    {
      title: "address",
      dataIndex: "address",
      width: "40%",
      render: (text, record, index) => (
        <Form.Item>
          {getFieldDecorator(`data.${index}.address`, {
            rules: [
              {
                required: true,
                message: "请输入!",
              },
            ],
            initialValue: record["address"],
          })(<Input />)}
        </Form.Item>
      ),
    },
    { title: "operation", dataIndex: "operation" },
];

3、添加提交函数和重置数据函数(给表格重新赋值)

js 复制代码
const handleSubmit = () => {
    validateFields((err, values) => {
      console.log("Received values of form: ", values);
      if (!err) {
        const updatedData = data.map((item, index) => ({
          ...item,
          age: values.data[index].age,
          address: values.data[index].address,
        }));
        console.log(updatedData); //更新后的数据
      }
    });
  };
js 复制代码
const handleReset = () => {
    let newData = [
      { key: 1, name: `Edrward ${1}`, age: "1", address: "2" },
      { key: 2, name: `Edrward ${2}`, age: "3", address: "4" },
      { key: 3, name: `Edrward_${3}`, age: "", address: "" },
      { key: 4, name: `Edrward ${3}`, age: "5", address: "6" },
      setData(newData),
    ];
  };

4、表格数据改变后同步更新表单

js 复制代码
useEffect(() => {
    //当 data 状态更新时,手动设置表单字段的值
    const fields = data.reduce((acc, record, index) => {
      acc[`data.${index}.age`] = record.age;
      acc[`data.${index}.address`] = record.address;
      return acc;
    }, {});
    setFieldsValue(fields);
  }, [data]);

antDesign 的完整代码

js 复制代码
import { Button, Form, Input, Table } from "antd";
import { useEffect, useState } from "react";
const App = ({ form }) => {
  const { getFieldDecorator, setFieldsValue, validateFields } =
    form;
  const asdfcolumns = [
    { title: "name", dataIndex: "name", width: "25%" },
    {
      title: "age",
      dataIndex: "age",
      width: "15%",
      render: (text, record, index) => (
        <Form.Item>
          {getFieldDecorator(`data.${index}.age`, {
            rules: [{ required: true, message: "请输入" }],
            initialValue: record["age"],
          })(<Input />)}
        </Form.Item>
      ),
    },
    {
      title: "address",
      dataIndex: "address",
      width: "40%",
      render: (text, record, index) => (
        <Form.Item>
          {getFieldDecorator(`data.${index}.address`, {
            rules: [
              {
                required: true,
                message: "请输入!",
              },
            ],
            initialValue: record["address"],
          })(<Input />)}
        </Form.Item>
      ),
    },
    { title: "operation", dataIndex: "operation" },
  ];
  const [data, setData] = useState([
    { key: 1, name: `Edrward ${1}`, age: "1234", address: "1234" },
    { key: 2, name: `Edrward ${2}`, age: "1234", address: "1234" },
    { key: 3, name: `Edrward ${3}`, age: "", address: "" },
  ]);
  const handleSubmit = () => {
    validateFields((err, values) => {
      if (!err) {
        console.log("Received values of form: ", values);
        const updatedData = data.map((item, index) => ({
                ...item,
                age: values.data[index].age,
                address: values.data[index].address,
        }));
        console.log(updatedData);
      }
    });
  };

  const handleReset = () => {
    let newData = [
      { key: 1, name: `Edrward ${1}`, age: "1", address: "2" },
      { key: 2, name: `Edrward ${2}`, age: "3", address: "4" },
      { key: 3, name: `Edrward_${3}`, age: "", address: "" },
      { key: 4, name: `Edrward ${3}`, age: "5", address: "6" },
      setData(newData),
    ];
  };

  useEffect(() => {
    //当 data 状态更新时,手动设置表单字段的值
    const fields = data.reduce((acc, record, index) => {
      acc[`data.${index}.age`] = record.age;
      acc[`data.${index}.address`] = record.address;
      return acc;
    }, {});
    setFieldsValue(fields);
  }, [data]);
  return (
    <>
      <Form>
        <Table
          columns={asdfcolumns}
          dataSource={data}
          pagination={false}
          rowKey="key"
        ></Table>
        <Form.Item>
          <Button type="primary" onClick={handleSubmit}>
            Submit
          </Button>
          <Button onClick={handleReset}>reset</Button>
        </Form.Item>
      </Form>
    </>
  );
};
const WrappedMyFormComponent = Form.create()(App);
export default WrappedMyFormComponent;
相关推荐
代码猎人19 小时前
forEach和map方法有哪些区别
前端
恋猫de小郭19 小时前
Google DeepMind :RAG 已死,无限上下文是伪命题?RLM 如何用“代码思维”终结 AI 的记忆焦虑
前端·flutter·ai编程
m0_4711996319 小时前
【小程序】订单数据缓存 以及针对海量库存数据的 懒加载+数据分片 的具体实现方式
前端·vue.js·小程序
编程大师哥19 小时前
Java web
java·开发语言·前端
A小码哥19 小时前
Vibe Coding 提示词优化的四个实战策略
前端
Murrays19 小时前
【React】01 初识 React
前端·javascript·react.js
大喜xi19 小时前
ReactNative 使用百分比宽度时,aspectRatio 在某些情况下无法正确推断出高度,导致图片高度为 0,从而无法显示
前端
helloCat19 小时前
你的前端代码应该怎么写
前端·javascript·架构
电商API_1800790524719 小时前
大麦网API实战指南:关键字搜索与详情数据获取全解析
java·大数据·前端·人工智能·spring·网络爬虫
康一夏19 小时前
CSS盒模型(Box Model) 原理
前端·css