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 相关文章推荐
jQuery 研究心得 取得属性的值
Nov 30 Javascript
jquery 关键字“拖曳搜索”之“拖曳”以及 图片“提示自适应放大”效果 的实现
Apr 18 Javascript
jQuery 淡出一个图像到另一个图像的实现代码
Jun 12 Javascript
js 动态修改css文件的方法
Aug 05 Javascript
使用ngView配合AngularJS应用实现动画效果的方法
Jun 19 Javascript
javascript检测flash插件是否被禁用的方法
Jan 14 Javascript
javascript数组对象常用api函数小结(连接,插入,删除,反转,排序等)
Sep 20 Javascript
详解Node.js access_token的获取、存储及更新
Jun 20 Javascript
深入理解JS中Number(),parseInt(),parseFloat()三者比较
Aug 24 Javascript
vue项目中使用particles实现粒子背景效果及遇到的坑(按钮没有点击响应)
Feb 11 Javascript
JavaScript数组排序功能简单实现
May 14 Javascript
Vue项目中使用mock.js的完整步骤
Jan 12 Vue.js
react 项目中引入图片的几种方式
Jun 02 #Javascript
小程序wx.getUserProfile接口的具体使用
js实现自动锁屏功能
Jun 02 #Javascript
如何将JavaScript将数组转为树形结构
Jun 02 #Javascript
关于antd tree 和父子组件之间的传值问题(react 总结)
使用react+redux实现计数器功能及遇到问题
微信小程序基础教程之echart的使用
You might like
PHP setcookie设置Cookie用法(及设置无效的问题)
2011/07/13 PHP
PHP排序算法的复习和总结
2012/02/15 PHP
基于magic_quotes_gpc与magic_quotes_runtime的区别与使用介绍
2013/04/22 PHP
php根据操作系统转换文件名大小写的方法
2014/02/24 PHP
php实现水印文字和缩略图的方法示例
2016/12/29 PHP
PHP使用星号替代用户名手机和邮箱的实现代码
2018/02/07 PHP
javascript中IE浏览器不支持NEW DATE()带参数的解决方法
2012/03/01 Javascript
让元素在网页中可拖动示例代码
2013/08/13 Javascript
浅析JS运动
2015/12/28 Javascript
node.js实现快速截图
2016/08/27 Javascript
深入理解AngularJS中的ng-bind-html指令
2017/03/27 Javascript
详解Angular.js数据绑定时自动转义html标签及内容
2017/03/30 Javascript
移动端web滚动分页的实现方法
2017/05/05 Javascript
JQuery EasyUI的一些常用组件
2017/07/12 jQuery
JS库 Highlightjs 添加代码行号的实现代码
2017/09/13 Javascript
vue+express+jwt持久化登录的方法
2019/06/14 Javascript
微信小程序数据统计和错误统计的实现方法
2019/06/26 Javascript
[48:48]2014 DOTA2国际邀请赛中国区预选赛 SPD-GAMING VS Dream TIME
2014/05/21 DOTA
[24:42]VP vs TNC Supermajor小组赛B组 BO3 第三场 6.2
2018/06/03 DOTA
Python3编程实现获取阿里云ECS实例及监控的方法
2017/08/18 Python
手把手教你用python抢票回家过年(代码简单)
2018/01/21 Python
python实现根据指定字符截取对应的行的内容方法
2018/10/23 Python
django rest framework serializer返回时间自动格式化方法
2020/03/31 Python
Python selenium键盘鼠标事件实现过程详解
2020/07/28 Python
Python+unittest+requests 接口自动化测试框架搭建教程
2020/10/09 Python
CSS3 @media的基本用法总结
2019/09/10 HTML / CSS
台湾森森购物网:U-mall
2017/10/16 全球购物
毕业自我评价范文
2013/11/17 职场文书
办公室文员工作自我评价
2013/12/01 职场文书
大学英语演讲稿(中英文对照)
2014/01/14 职场文书
《我为你骄傲》教学反思
2014/02/20 职场文书
考研英语复习计划
2015/01/19 职场文书
2015年材料员工作总结
2015/04/30 职场文书
《1942》观后感
2015/06/08 职场文书
恶魔之树最顶端的三颗果实 震震果实上榜,第一可以制造岩浆
2022/03/18 日漫
vue中this.$http.post()跨域和请求参数丢失的解决
2022/04/08 Vue.js