react antd实现动态增减表单


Posted in Javascript onJune 03, 2021

之前写动态表单遇到过坑,就是用index下标做key会导致bug,而且很严重!

今天有空写下文章记录下:怎么处理和逻辑

我用的是antd3的版本,3和4的表单有点不一样,不过差别应该不大。

需求:

1、选择类型切换展示固定的模板

2、通过新增字段可以动态增减表单里面的每一行

3、控制每一行的字段是否需要必填

4、编辑时候回填参数

效果图:

react antd实现动态增减表单

react antd实现动态增减表单

react antd实现动态增减表单

部分关键代码:

import React, { Component } from 'react';
import styles from './index.less';
import {
  Table,
  Button,
  Select,
  Popconfirm,
  Modal,
  Form,
  Input,
  Radio,
  Row,
  Col, Tooltip,
  Icon,
  message,
  Pagination, InputNumber,
} from 'antd';

const Option = Select.Option;
const FormItem = Form.Item;

let id = 0;

@Form.create()
class Index extends Component {
  marketId = 0;
  state = {
    selectType: '',
    orderType: 1,  //文章1  地图2
    typeLoading: false,
    isEdit: false,
    lookVisible: false,
    visible: false,
    pageSize: 10,
    pageNum: 1,
    keyWord: '',
    row: {},
    typeList: {},
    mock: {},
    mapType: [{
      'fieldName': 'name',
      'isImg': 0,
      'order': 0,
      'remarks': '名称',
    }, {
      'fieldName': 'label',
      'isImg': 0,
      'order': 0,
      'remarks': '标签',
    }, {
      'fieldName': 'lon',
      'isImg': 0,
      'order': 0,
      'remarks': '经度',
    }, {
      'fieldName': 'lat',
      'isImg': 0,
      'order': 0,
      'remarks': '纬度',
    }],
    articleType: [{
      'fieldName': 'name',
      'isImg': 0,
      'order': 0,
      'remarks': '名称',
    }, {
      'fieldName': 'label',
      'isImg': 0,
      'order': 0,
      'remarks': '标签',
    }],
  };
/**
   * 将动表单态值生成需要的数据格式
   * @param values
   * @returns {[]}
   */
  createValues = (values) => {
    const { row } = this.state;
    const data = [];
    const newValues = { // 用新的对象承载提交的数据
      ...values,
    };
    const fieldNameData = []; // 保存fieldName值
    const remarksData = []; // 保存remarks值
    const isImgData = []; // 保存isImg值
    const orderData = []; // 保存orderData值
    const fieldName = RegExp(/fieldName/);
    const remarks = RegExp(/remarks/);
    const isImg = RegExp(/isImg/);
    for (const key in newValues) {
      if (fieldName.test(key)) {
        fieldNameData.push(newValues[key]);
      }
    }
    for (const key in newValues) {
      if (remarks.test(key)) {
        remarksData.push(newValues[key]);
      }
    }
    for (const key in newValues) {
      if (isImg.test(key)) {
        isImgData.push(newValues[key]);
      }
    }
    for (const key in newValues) {
      if (isImg.test(key)) {
        orderData.push(newValues[key]);
      }
    }
    fieldNameData.forEach((item, index) => {
      data.push({
        fieldName: item,
        remarks: remarksData[index],
        isImg: isImgData[index],
        order: orderData[index],
        id: row.dataType ? row.dataType.id : '',
      });
    });
    return data;
  };

  handleOk = e => {
    this.props.form.validateFields((err, values) => {
      if (!err) {
        const { row, isEdit } = this.state;
        const params = {
          dataType: {
            name: values.name,
            type: values.type,
            id: row.dataType ? row.dataType.id : '',
          },
          typeFields: [],
        };
        params.typeFields = this.createValues(values);
        if (isEdit) {
          editType(params).then(res => {
            if (res.code === 0) {
              message.info('修改成功');
              this.setState({
                visible: false,
                isEdit: false,
              });
              this.fetchTypeList();
              this.props.form.resetFields();
            }
          });
        } else {
          addType(params).then(res => {
            if (res.code === 0) {
              message.info('新增成功');
              this.setState({
                visible: false,
                isEdit: false,
              });
              this.fetchTypeList();
              this.props.form.resetFields();
            }
          });
        }
      }
    });
  };

  lookOrEditTypeModal = (flag, record) => {
    const { articleType, mapType } = this.state;
    if (flag === 'add') {  //添加默认为文章模板
      this.marketId = articleType.length + 1;  //设置动态key标记长度
      this.setState({
        visible: true,
        row: { typeFields: articleType },
      });
    } else if (flag === 'edit') {
      this.setState({
        visible: true,
      });
      getType({ dataTypeId: record.id }).then(res => {
        if (res.code === 0) {
          this.marketId = res.data.typeFields.length + 1;  //设置动态key标记长度
          this.setState({
            row: res.data,
            isEdit: flag === 'edit',
          });
        }
      });
    } else {
      this.setState({
        lookVisible: true,
      });
      getType({ dataTypeId: record.id }).then(res => {
        if (res.code === 0) {
          this.setState({
            row: res.data,
          });
        }
      });
    }
  };


  onChangeType = (value) => {
    const { form } = this.props;
    const { orderType, row, articleType, mapType } = this.state;
    this.props.form.resetFields();

    const params = {};
    if (value === 1) {  //文章类型
      params['typeFields'] = articleType;
      this.marketId = articleType.length + 1;
    } else {
      params['typeFields'] = mapType;
      this.marketId = mapType.length + 1;
    }
    this.setState({
      row: params,
      orderType: value,
    });
  };
//删除方法!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  removeFile = k => {
    const { form } = this.props;
    const keys = form.getFieldValue('keys');
    if (keys.length === 1) {
      return;
    }
    form.setFieldsValue({
      keys: keys.filter(key => key !== k),
    });
  };
//添加方法!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  addFile = () => {
    const { form } = this.props;
    const keys = form.getFieldValue('keys');
    const nextKeys = keys.concat(this.marketId++);
    form.setFieldsValue({
      keys: nextKeys,
    });
  };

  judgeIsTemplet = (data) => {
    if (!data) {
      return false;
    }
    if ((data.fieldName === 'lat') || (data.fieldName === 'lon') || (data.fieldName === 'label') || (data.fieldName === 'name')) {
      return true;
    }
  };
  handleValidator = (rule, val, callback) => {
    if (!val) {
      callback();
    }
    let validateResult = /^[5A-Za-z0-9-\_]+$/.test(val);
    if (!validateResult) {
      callback('请输入正确表字段');
    }
    callback();
  };

  columns = [
    {
      title: '类型名称',
      dataIndex: 'name',
      key: 'name',
      width: 500,
    },
    {
      title: '所属类型',
      dataIndex: 'type',
      key: 'type',
      render: (text) => {
        return text === 1 ? '文章' : '地图';
      },
    },
    {
      title: '操作',
      dataIndex: 'address',
      key: 'address',
      render: (text, record) => {
        return <div>
          <Button type='link' onClick={() => this.lookOrEditTypeModal('look', record)}>查看</Button>
          <Button type='link' onClick={() => this.lookOrEditTypeModal('edit', record)}>编辑</Button>
          <Popconfirm title="确认删除?" onConfirm={() => this.deleteTypeClick(record)}>
            <Button type='link'>删除</Button>
          </Popconfirm>
        </div>;
      },
    },
  ];

  render() {
    const { selectType, typeLoading, mock, row, isEdit, typeList, keyWord, lookVisible } = this.state;
    const { getFieldDecorator, getFieldValue } = this.props.form;
    let typeFields = row.typeFields || [];
    const initData = [];
    typeFields.forEach((item, index) => {//根据真实数据,设置默认keys数组
      initData.push(index);
    });
    getFieldDecorator('keys', { initialValue: initData });  //给表单增加keys字段,并设置默认值,这里编辑时候可以生成编辑回填的效果。
    const keys = getFieldValue('keys');
    const formItems = keys.map((k) => (
      <Row gutter={12} key={k} className={styles.form_row}>
        <FormItem label="字段" key={`fieldName_${k}`}>
          {getFieldDecorator(`fieldName_${k}`, {
            initialValue: row.typeFields[k] ? row.typeFields[k].fieldName : '',
            validateTrigger: ['onChange', 'onBlur'], //校验子节点值的时机
            rules: [{
              required: true,
              message: '请输入英文字段!',
            }, {
              validator: this.handleValidator,
            }],
          })(<Input placeholder="请输入英文字段" max={30} disabled={this.judgeIsTemplet(row.typeFields[k])}/>)}
        </FormItem>
        <FormItem label="名称" key={`remarks_${k}`}>
          {getFieldDecorator(`remarks_${k}`, {
            initialValue: row.typeFields[k] ? row.typeFields[k].remarks : '',
            validateTrigger: ['onChange', 'onBlur'],
            rules: [{
              required: true,
              message: '请输入中文名称!',
            }],
          })(<Input placeholder="请输入中文名称" disabled={this.judgeIsTemplet(row.typeFields[k])}/>)}
        </FormItem>
        <FormItem label="排序" key={`order_${k}`}>
          {getFieldDecorator(`order_${k}`, {
            initialValue: row.typeFields[k] ? row.typeFields[k].order : 0,
          })(<InputNumber style={{width:75}} placeholder="排序" />)}
        </FormItem>
        <FormItem label="图片" key={k}>
          {getFieldDecorator(`isImg_${k}`, {
            initialValue: row.typeFields[k] ? row.typeFields[k].isImg : 0,
            rules: [{
              required: true,
            }],
          })(<Radio.Group disabled={this.judgeIsTemplet(row.typeFields[k])}>
            <Radio value={0}>否</Radio>
            <Radio value={1}>是</Radio>
          </Radio.Group>)}
        </FormItem>
        {!this.judgeIsTemplet(row.typeFields[k]) ? (
          <Icon type="minus-circle" onClick={() => this.removeFile(k)} title='删除'/>
        ) : null}
      </Row>
    ));


    return (
      <div className={styles.wrap_type}>
        <Modal
          title="类型管理"
          visible={this.state.visible}
          onOk={this.handleOk}
          onCancel={this.handleCancel}
          width={890}
          // className={styles.modal_type}
          maskClosable={false}
        >
          <Form layout='inline'>
            <Row style={{ textAlign: 'center', marginBottom: 14 }}>
              <FormItem label="选择类型">
                {getFieldDecorator('type', {
                  initialValue: row.dataType ? row.dataType.type : 1,
                  rules: [{
                    required: true,
                  }],
                })(<Select onChange={this.onChangeType} disabled={isEdit} style={{ width: 200 }}>
                  <Option value={1}>文章类型</Option>
                  <Option value={2}>地图类型</Option>
                  <Option value={3} disabled={true}>文件类型</Option>
                </Select>)}
              </FormItem>
              <FormItem label="类型名称">
                {getFieldDecorator('name', {
                  initialValue: row.dataType ? row.dataType.name : '',
                  rules: [{
                    required: true,
                    message: '请输入类型名称!',
                  }],
                })(<Input placeholder="请输入类型名称" style={{ width: 200 }}/>)}
              </FormItem>
            </Row>
            {formItems}
            <div style={{ margin: 'auto', textAlign: 'center' }}>
              <Button icon="plus" onClick={this.addFile} style={{ marginTop: 10 }}>新增字段</Button>
            </div>
          </Form>
        </Modal>
      </div>
    );
  }
}

export default Index;

关键地方是设置一个marketID作为动态添加的key,然后用他的值作为动态key。(千万不要用数组的下标index来作为key)!

到此这篇关于react antd实现动态增减表单的文章就介绍到这了,更多相关react antd动态增减表单内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
深入聊聊Array的sort方法的使用技巧.详细点评protype.js中的sortBy方法
Apr 12 Javascript
使用JavaScript库还是自己写代码?
Jan 28 Javascript
jquery select多选框的左右移动 具体实现代码
Jul 03 Javascript
javascript 寻找错误方法整理
Jun 15 Javascript
javascript电商网站抢购倒计时效果实现
Nov 19 Javascript
jQuery Checkbox 全选 反选的简单实例
Nov 29 Javascript
vue2.0 keep-alive最佳实践
Jul 06 Javascript
node.js操作MongoDB的实例详解
Oct 11 Javascript
Vue验证码60秒倒计时功能简单实例代码
Jun 22 Javascript
Bootstarp在pycharm中的安装及简单的使用方法
Apr 19 Javascript
浅谈对于react-thunk中间件的简单理解
May 01 Javascript
vue 子组件修改data或调用操作
Aug 07 Javascript
react 项目中引入图片的几种方式
Jun 02 #Javascript
小程序wx.getUserProfile接口的具体使用
js实现自动锁屏功能
Jun 02 #Javascript
如何将JavaScript将数组转为树形结构
Jun 02 #Javascript
关于antd tree 和父子组件之间的传值问题(react 总结)
使用react+redux实现计数器功能及遇到问题
微信小程序基础教程之echart的使用
You might like
smarty模板嵌套之include与fetch性能测试
2010/12/05 PHP
php中计算程序运行时间的类代码
2012/11/03 PHP
深入PHP中慎用双等于(==)的详解
2013/06/06 PHP
PHP采用curl模仿用户登陆新浪微博发微博的方法
2014/11/07 PHP
php防止恶意刷新与刷票的方法
2014/11/21 PHP
对比PHP对MySQL的缓冲查询和无缓冲查询
2016/07/01 PHP
jquery插件之信息弹出框showInfoDialog(成功/错误/警告/通知/背景遮罩)
2013/01/09 Javascript
jquery zTree异步加载简单实例分享
2013/02/05 Javascript
用js实现小球的自由移动代码
2013/04/22 Javascript
基于IE下ul li 互相嵌套时的bug,排查,解决过程以及心得介绍
2013/05/07 Javascript
ExtJS自定义主题(theme)样式详解
2013/11/18 Javascript
千分位数字格式化(用逗号隔开 代码已做了修改 支持0-9位逗号隔开)的JS代码
2013/12/05 Javascript
jquery实现的伪分页效果代码
2015/10/29 Javascript
深入浅出ES6之let和const命令
2016/08/25 Javascript
Javascript实现代码折叠功能
2016/08/25 Javascript
微信小程序 sha1 实现密码加密实例详解
2017/07/06 Javascript
promise和co搭配生成器函数方式解决js代码异步流程的比较
2018/05/25 Javascript
python发送邮件示例(支持中文邮件标题)
2014/02/16 Python
几个提升Python运行效率的方法之间的对比
2015/04/03 Python
安装dbus-python的简要教程
2015/05/05 Python
Python实现动态图解析、合成与倒放
2018/01/18 Python
python中numpy的矩阵、多维数组的用法
2018/02/05 Python
详解爬虫被封的问题
2019/04/23 Python
python 浅谈serial与stm32通信的编码问题
2019/12/18 Python
解决pycharm导入本地py文件时,模块下方出现红色波浪线的问题
2020/06/01 Python
HTML5未来发展趋势
2016/02/01 HTML / CSS
英国玛莎百货美国官网:Marks & Spencer美国
2018/11/06 全球购物
StubHub澳大利亚:购买或出售您的门票
2019/08/01 全球购物
经典优秀毕业生求职信范文分享
2013/12/18 职场文书
优秀团队获奖感言
2014/02/19 职场文书
中西医专业毕业生职业规划书
2014/02/24 职场文书
收款委托书
2014/10/14 职场文书
2015年董事长秘书工作总结
2015/07/23 职场文书
2019年度行政文员工作计划范本!
2019/07/04 职场文书
Python编写可视化界面的全过程(Python+PyCharm+PyQt)
2021/05/17 Python
java中为什么说子类的构造方法默认访问的是父类的无参构造方法
2022/04/13 Java/Android