AntDesign Form 和 Table 组件套用,实现行数据的验证、添加和删除

7,238 阅读2分钟

开发中有这样的需求,需要对单元格中每一项进行校验,并提供新增行和删除行的操作。

因为 Form 组件可以很方便的对数据进行校验,所以就想到了套用 From 组件和 Table 组件来实现这个需求。能实现需求中的所有功能点,但是方案并不完美,小伙伴们可以借鉴一下👉链接在这里

效果图在这里

import React from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";
import "antd/dist/antd.css";
import { Form, Input, Table, Select, AutoComplete, Icon } from "antd";
import "./map.css";

class SetMapping extends React.Component {
 static propTypes = {
   form: PropTypes.shape({}).isRequired,
 };

 state = {
   mappingState: [],
   appnameList: [],
   tagList: []
 };

 componentDidMount() {}

 // 这里判断下 IP 是不是有效的,以及是否重复
 syslogIPValidator = () => ({
   validator: (rule, value, callback) => {
     if (value && value.trim() !== "") {
       const reg = /^(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)$/i;
       if (!reg.test(value.trim())) {
         callback("IP地址格式错误");
       } else if (this.checkIfExistedIP(value)) {
         callback("IP地址重复");
       } else {
         callback();
       }
     }
   }
 });

 checkIfExistedIP = value => {
   const { form } = this.props;
   const { getFieldValue } = form;
   const mapping = getFieldValue("mapping") || [];

   const mappingArray = mapping ? mapping.filter(one => one.ip === value) : [];
   if (mappingArray.length > 1) {
     return true;
   }
   return false;
 };

 // 添加一行数据
 addOneMapping = () => {
   const { form } = this.props;
   const { getFieldsValue, validateFields } = form;
   const { mappingState } = this.state;
   const mapping = getFieldsValue().mapping || mappingState;
   // 如果表格中有数据-->检测下之前的有没有填写完整
   if (mapping && mapping.length !== 0) {
     validateFields(["mapping"], { force: true }, errors => {
       if (!errors) {
         mapping.push({
           ip: "",
           appname: "",
           tag: "",
           charset: ""
         });
         this.setState({ mappingState: mapping });
       }
     });
   } else {
     mapping.push({
       ip: "",
       appname: "",
       tag: "",
       charset: ""
     });
     this.setState({ mappingState: mapping });
   }
 };

 // 移除某一项
 // mapping 为 form 的属性名
 // mappingState 为用来渲染 table 的数据源
 // 所以删除时,需要两个一起修改
 removeOneMapping = index => {
   const { form } = this.props;
   const { getFieldValue } = form;

   const mapping = getFieldValue("mapping");
   mapping.splice(index, 1);

   this.setState({
     mappingState: mapping
   });
   form.setFieldsValue({
     mapping
   });
 };

 render() {
   const { mappingState } = this.state;
   const { form, appnameList, tagList } = this.props;
   const { getFieldDecorator } = form;

   const PLACEHOLDER = "请输入";

   const tableTitle = [
     {
       title: "ip",
       dataIndex: "ip",
       key: "ip",
       width: 205,
       render: (ip, one, index) => (
         <Form.Item>
           {getFieldDecorator(`mapping[${index}].ip`, {
             initialValue: ip || "",
             validateFirst: true,
             validateTrigger: ["onChange", "onFocus"],
             rules: [
               {
                 required: true,
                 message: "请输入IP地址"
               },
               this.syslogIPValidator()
             ]
           })(
             <Input
               size="small"
               style={{ width: "170px" }}
               autoComplete="off"
               placeholder={PLACEHOLDER}
             />
           )}
         </Form.Item>
       )
     },
     {
       title: "appname",
       dataIndex: "appname",
       key: "appname",
       width: 205,
       render: (appname, one, index) => (
         <Form.Item>
           {getFieldDecorator(`mapping[${index}].appname`, {
             initialValue: appname || "",
             validateFirst: true,
             validateTrigger: ["onChange", "onBlur"],
             rules: [
               {
                 required: true,
                 message: "请输入 appname"
               }
             ]
           })(
             <AutoComplete
               size="small"
               dataSource={appnameList}
               style={{ width: "170px" }}
               placeholder={PLACEHOLDER}
               filterOption={(inputValue, option) =>
                 option.props.children
                   .toUpperCase()
                   .indexOf(inputValue.toUpperCase()) !== -1
               }
             />
           )}
         </Form.Item>
       )
     },
     {
       title: "tag",
       dataIndex: "tag",
       key: "tag",
       width: 205,
       render: (tag, one, index) => (
         <Form.Item>
           {getFieldDecorator(`mapping[${index}].tag`, {
             initialValue: tag || "",
             validateFirst: true,
             validateTrigger: ["onChange", "onBlur"],
             rules: [
               {
                 required: true,
                 message: "请输入 tag"
               }
             ]
           })(
             <AutoComplete
               size="small"
               dataSource={tagList}
               style={{ width: "170px" }}
               placeholder={PLACEHOLDER}
               filterOption={(inputValue, option) =>
                 option.props.children
                   .toUpperCase()
                   .indexOf(inputValue.toUpperCase()) !== -1
               }
             />
           )}
         </Form.Item>
       )
     },
     {
       title: "charset",
       dataIndex: "charset",
       key: "charset",
       width: 110,
       render: (charset, one, index) => (
         <Form.Item>
           {getFieldDecorator(`mapping[${index}].charset`, {
             initialValue: charset || "utf-8"
           })(
             <Select size="small">
               <Select.Option value="utf-8" style={{ fontSize: 12 }}>
                 utf-8
               </Select.Option>
               <Select.Option value="gbk" style={{ fontSize: 12 }}>
                 gbk
               </Select.Option>
             </Select>
           )}
         </Form.Item>
       )
     },
     {
       title: "操作",
       key: "operation",
       width: 60,
       render: (operation, one, index) => (
         <Icon
           style={{ height: "42px" }}
           type="delete"
           onClick={() => this.removeOneMapping(index)}
         />
       )
     }
   ];

   return (
     <div className="setMapping">
       <Table
         bordered
         size="small"
         className="tableBody"
         rowKey={(record, index) => index}
         columns={tableTitle}
         dataSource={mappingState}
         pagination={false}
       />
       <span className="addNew" onClick={this.addOneMapping}>
         添加新映射
       </span>
     </div>
   );
 }
}

const SetMappingForm = Form.create({ name: "register" })(SetMapping);

ReactDOM.render(<SetMappingForm />, document.getElementById("root"));

另外 AntDesign 官方也有类似的实现,大家可以研究下,👉链接在这里 。另外这个问题在 github 中也有讨论,其他小伙伴的方法,👉链接在这里