react antd實現動態增減表單
之前寫動態表單遇到過坑,就是用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動態增減表單內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 解決Antd中Form表單的onChange事件中執行setFieldsValue不生效
- SpringBoot+Mybatis plus+React實現條件選擇切換搜索實踐
- React實現下拉框的key,value的值同時傳送
- vue和iview結合動態生成表單實例
- iview實現動態表單和自定義驗證時間段重疊