使用antd的Form组件动态创建复杂Select表单

image.png

image.png

最近在项目中需要开发这样一个动态的select组件功能,梳理一下需求:

1.左侧的select和右侧的select是一个联动效果,右侧的select有几个option取决于选择了左侧的某个option
2.左侧的select有几个option取决于后台接口返回的数据
3.当页面加载时,会去接口中读取已有的变量和成员表单,一行的变量和成员可以看做一个整体
4.页面的表单可以用户手动增删

在开发过程的遇到的问题和解决方案记录:

1.动态增删表单

原理是使用getFieldValue方法和setFieldsValue方法,通过增加keys数组成员和删除keys数组成员来达到增删表单的效果。

removeFormItem = (k) => {
    const { form } = this.props;
    const keys = form.getFieldValue('keys');
    form.setFieldsValue({
        keys: keys.filter(key => key !== k),
    });
}

addFormItem = () => {
    const { form } = this.props;
    let keys = form.getFieldValue('keys');
    //增加表单时,这个key必须唯一,这里我引入uuid这个库来生成唯一ID 
    //( import UUID from 'uuid/v1')
    const nextKeys = keys.concat(UUID());
    form.setFieldsValue({
        keys: nextKeys,
    });
}
2.如何使用后台动态获取的值去设置页面的form表单

这个一度成为我的难点,通过后台返回的已有变量列表值去设置初始页面的表单数量,比如后台返回3个,我需要设置三个select,那么这一步在什么时候做。

  • render的时候去设置已有表单,因为动态获取的表单数量是由父组件传入,所以在子组件中用props接收。
  • 在props中的taskVariableList属性读取每一项的id作为form的key值,遍历后放进一个formKeys的数组,这个数组就是已有表单的key值
  • getFieldDecorator('keys', { initialValue:formKeys}) 重点是使用这个方法设置初始表单
const { getFieldDecorator, getFieldValue } = this.props.form;
let {taskVariableList,taskVariableOptionList}=this.props;
let formKeys=[],initValueObject={};
//使用已选列表每项的id作为已有表单的key值
if(taskVariableList.length>0){
    taskVariableList.map(variable=>{
        formKeys.push(variable.id);
        //将已有选项的值 以id-选项值的key-value形式存入一个对象
        initValueObject[variable.id]=variable
    })
}else{
    //如果没有已有列表 保证页面有一个初始的select
    if(this.state.haveNoVariable){
        formKeys.push(singleSelectUUID);
    }
}
getFieldDecorator('keys', { initialValue:formKeys});
3.如何渲染表单组件

一切的数据获取,数据增删都是储存在form的keys属性里面,那么const keys = form.getFieldValue('keys'),使用这个keys数组去渲染表单,因为每次增删改查都是操作这个keys数组,所以通过keys总是渲染正确实时的表单数据

<Form style={{margin:"0px 12px"}} className="task-variable">
    {
        keys&&keys.map((key,index)=>{
            return (
                <Row gutter={24} key={key}>
                    <Col span={11}>
                        <FormItem
                            {...formItemLayout}
                            label="变量"
                        >
                            {getFieldDecorator(`variable#${key}`,{initialValue:initValueObject[key]?initValueObject[key]["fromDimensionId"]:initialLeftValue,})(
                                <Select onChange={this.handleNewVariableChange.bind(this,taskVariableOptionList,key)}>
                                    {taskVariableOptionList.map(dimension=>{
                                        return (
                                            <Option value={dimension.fromDimensionId} key={`${dimension.id}-${index}`}>{dimension.name}</Option>
                                        )
                                    })}
                                </Select>
                            )}
                        </FormItem>
                    </Col>
                    <Col span={11}>
                        <FormItem
                            {...formItemLayout}
                            label="成员"
                        >
                            {this.renderSubItem(initValueObject,taskVariableOptionList,key,getFieldDecorator,initValueObject[key]?initValueObject[key]["fromCodeId"]:initialRightValue)}
                        </FormItem>
                    </Col>
                    <Col className="select-action-wrap" span={2}>
                        {keys.length ===(index+1)&&keys.length!==1? (
                            <i className="icon font_family" onClick={this.addFormItem}>&#xe644;</i>
                        ) : <Icon
                            className="dynamic-delete-button"
                            type="minus-circle-o"
                            disabled={keys.length === 1}
                            onClick={() => this.removeFormItem(key)}
                        />}
                    </Col>
                </Row>
            )
        })
    }
</Form>
4.如何渲染根据左侧选择的option动态渲染右侧select组件

使用select的回调事件,每次选择左侧不同的option,将右侧的值存入state,然后读取这个值生成不同的option

handleNewVariableChange=(optionData,key,value)=>{
    /**
     * optionData 右边的option 数组
     * key 传入的form表单的key 也就是已有变量列表的id或者新增表单的id
     * value 左侧的option变化value值 是变量的fromDimensionId
     * */
    let fromCodeList=[];
    optionData.map(dimension=>{
        if(dimension.fromDimensionId===value){
            fromCodeList=dimension.fromCodeList
        }
    })
    //以key作为state的唯一标志 将属于该key的fromCodeList存入 之后右边的option只需要读这个对应的fromCodeList来生成option
    this.setState({
        [key]:{"fromCodeList":fromCodeList},
        optionChangeFlag:true
    })
}
renderSubItem = (initValueObject,taskVariableOptionList,key,getFieldDecorator,initSubValue) =>{
    /**
     * initValueObject 已有变量列表的id-value对应的对象
     * taskVariableOptionList 备选option列表
     * key 传入的form表单的key 也就是已有变量列表的id或者新增表单的id
     * getFieldDecorator form的方法
     * initSubValue 右侧option的默认值
     * */
    let items=[];
    if(!this.state[key]){
        //这一步判断左侧option没有变化过 也就是取默认的fromCodeList
        if(!initValueObject[key]){
            //这一步判断是新增的表单 所有右侧取taskVariableOptionList第一项的fromCodeList来生成option
            items=taskVariableOptionList.length>0&&taskVariableOptionList[0].fromCodeList||[];
        }else{
            //这一步判断是已有的表单列表 右侧要取对应的fromCodeList生成option
            let fromDimensionId=initValueObject[key].fromDimensionId;
            taskVariableOptionList.map(option=>{
                if(option.fromDimensionId===fromDimensionId){
                    items=option.fromCodeList
                }
            })
        }
    }else{
        //这一步判断如果左侧option变化了 是取左侧的option发生变化时存入的fromCodeList
        items=this.state[key].fromCodeList;
    }
    if(this.state.optionChangeFlag&&items.length>0){
        //如果左侧的option曾经发生变化 那么右侧的initvalue值取第一项的值
        initSubValue=items[0].id
    }
    if(items.length>0){
        return(
            <div>
                {getFieldDecorator(`member#${key}`,{initialValue:initSubValue})(
                    <Select >
                        {
                            items.map(suboption=>{
                                return (
                                    <Option value={suboption.id} key={suboption.id}>{suboption.name}</Option>
                                )
                            })
                        }
                    </Select>
                )}
            </div>
        )
    }else{
        return(
            <div>
                {getFieldDecorator(`member#${key}`,{initialValue:''})(
                    <Select initialValue={''}></Select>
                )}
            </div>
        )
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,967评论 3 119
  • 一年一度的英仙座流星雨即将到来,小伙伴们是不是都很兴奋呢?尤其那些绞尽脑汁准备惊喜和浪漫的小伙伴们,还有摄影发烧友...
    LadyVeronica阅读 602评论 4 5
  • 夜,无眠。 重温大话,泪流满面。 每个人都有故事,埋藏心底,纪念。 从前,已回不去,那叫回忆。 不珍惜,是我的标签...
    落月孤倚阅读 109评论 0 0
  • 事件一旦发生,除了当事双方,现场的目击者将会为调查提供第一手资料,小致二人斗殴,大致国家冲突,世界大战。 作为第二...
    龙_隆阅读 306评论 0 0