react antd实现动态增减表单

2022-07-23,,,,

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

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

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

需求:

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

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

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

4、编辑时候回填参数

效果图:

部分关键代码:

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动态增减表单内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

《react antd实现动态增减表单.doc》

下载本文的Word格式文档,以方便收藏与打印。