动态表单探索(一)

背景

根据后端返回的不同类型,直接动态渲染不同表单

数据格式

treeData:[{
    type: 'HInput',
    title: '患者姓名',
    value: ''
},{
    type: 'HRadio',
    title: '性别',
    label: [{
        title: '男',
        checked: false // 选中标识
    },{
        title: '女',
        contactKey: 'aaa', // 联动标识
        checked: false
    }]
},{
    type: 'HDatePicker',
    title: '出生日期',
    value: ''
},{
    type: 'HInput',
    title: '地区',
    value: ''
},{
    type: 'HInput',
    title: '身高',
    unit: '厘米',
    value: ''
},{
    type: 'HRadio',
    title: '是否孕期',
    contactVal: {// 响应上方联动标识,false不展示,true展示,这儿为对象,可能受多个项目影响
    	aaa: 'aaa-false',  
    }   
   	label: [{
       title: '是',
       checked: false,
       children: [{  // 联动块
           type: 'HDatePicker',
           title: '预产期',
           value: ''
       }]
    },{
        title: '否',
        checked: false
    }]
}]

实现思路

想法1:数据——虚拟dom——视图

1、根据表单数据,映射出一套虚拟dom
2、通过createElement方法实现视图
3、数据修改,重新映射虚拟dom(diff处理)
难点:
1、虚拟dom规范设计
2、思路尚不成熟

想法2:复合组件方式

在这里插入图片描述
下面着重介绍下思路2的实现方式。

思路2实现(组件划分)

基本组件

1、element组件的包装,用于处理数据,传值
2、后端返回的表单type类型为基本组件名称

<template>
    <div>
        {{value.title}}
        <el-radio-group v-model="radio" @change="changeHandler">
            <el-radio v-for="(item, index) in value.label" :key="index" :label="item.title">{{item.title}}</el-radio>
        </el-radio-group>
    </div>
</template>
 
<script>
    export default {
        props:{
            value:{
                default: () => {},
                type: Object
            }
        },
        data(){
            return {
                radio: ''
            }
        },
        methods:{
            // 数据变动校验,存在变动,发布事件通知页面组件,并回传当前数据以便于页面组件判断是否存在联动模块
            changeHandler(val){
                for(let i = 0; i < this.value.label.length; i++){
                    this.value.label[i].checked = false;
                    if(this.value.label[i].title === val){
                        this.value.label[i].checked = true;
                    }
                }
                this.$emit('event1', this.value)
            }
        }
    }
</script>

复合递归组件

1、使用component动态组件,透传直接渲染基本组件,v-model绑定数据,实现数据的双向绑定;
2、使用插槽根据判读渲染label或children模块;
3、根据contactVal属性是否含有true判断联动部分是否展示
4、通过require.context去阅读基本组件目录,动态引入基本组件

<template>
    <div>
        <component class="itemContent" v-if="!itemData.contactVal||itemData.contactVal.indexOf('true')>=0" v-bind:is="itemData.type" @event1="change($event)" v-model="itemData"></component>
        <slot v-if="itemData.label||(itemData.children&&itemData.checked)"></slot>
    </div>
</template>
 
<script>
    import moduleComponents from '../util/getBasicArr.js';
    export default {
        name: 'HComponent',
        props:{
            itemData:{
                default: () => {},
                type: Object
            }
        },
        methods: {
            change(data) {
                // 通知页面组件进行数据处理,接口调用
                this.$emit('event1', data)
            }
        },
        components: moduleComponents
    }
</script>
// 动态引入子组件
var context = require.context('../components/BasicComponents',false,/\.vue$/);
var moduleComponents = {};
context.keys().forEach((key)=>{
    const fileName = key.split(".")[1].split("/")[1];
    const fileModule = context(key).default;
    moduleComponents[fileName] = {
        ...fileModule,
        namespaced: true
    }
})
export default moduleComponents;

页面组件

1、遍历渲染数据;
2、根据回传内容进行数据处理(修改或清空对应联动模块数据)
3、调用暂存接口

<template>
  <div class="call_status">
      <HdfComponent v-for="(item,index) in treeData" :key="index" :itemData="item"  @event1="change($event)">
          <HdfComponent v-for="(subItem, subIndex) in item.label" :key="subIndex" :itemData="subItem" @event1="change($event)">
              <HdfComponent v-for="(sunItem, sunIndex) in subItem.children" :key="sunIndex" :itemData="sunItem" @event1="change($event)"></HdfComponent>
          </HdfComponent>
      </HdfComponent>
  </div>
</template>
 
<script>
import HdfComponent from './components/HdfComponent.vue'
export default {
    data(){
        return {
            treeData:[]
        }
    },
    methods: {
        change(data) {
            this.hasContact(data)
        },
        // 判断当前处理的选项是否有联动
        hasContact(obj){
            if(Object.prototype.hasOwnProperty.call(obj, 'contactKey')){
                // 存在联动并且有值,去更新联动数据
                if(obj.checked || obj.value){
                    this.handleData(obj.contactKey, 'update')
                }else{
                    // 存在联动没有值,去清空联动数据
                    this.handleData(obj.contactKey, 'reset')
                }
            }
            for(const key in obj){
                if(Object.prototype.toString.call(obj[key]) === '[object Array]'){
                    for(const item of obj[key]){
                        this.hasContact(item)
                    }
                } else if(Object.prototype.toString.call(obj[key]) === '[object Object]'){
                    this.hasContact(obj[key])
                }
            }
        },
        // 修改所有联动部分
        handleData(contactKey, handleType){
            const treeData = this.treeData;
            for(const item of treeData){
                this.handleObj(item, contactKey, handleType)
            }
            this.treeData = treeData;
        },
        // 深层次遍历去修改联动数据
        handleObj(obj, contactKey, handleType){
            if(Object.prototype.hasOwnProperty.call(obj, 'contactVal') && obj.contactVal.indexOf(contactKey) >= 0){
                if(handleType === 'update'){
                    obj.contactVal = obj.contactVal.replace('false', 'true')
                }
                if(handleType === 'reset'){
                    this.resetData(obj)
                }
            }
            for(const key in obj){
                if(Object.prototype.toString.call(obj[key]) === '[object Array]'){
                    for(const item of obj[key]){
                        this.handleObj(item, contactKey, handleType)
                    }
                } else if(Object.prototype.toString.call(obj[key]) === '[object Object]'){
                    this.handleObj(obj[key], contactKey, handleType)
                }
            }
        },
        // 重置联动数据
        resetData(obj){
            for(const key in obj){
                if(key === 'value'){
                    obj[key] = ''
                }
                if(key === 'checked'){
                    obj[key] = false
                }
                if(key === 'contactVal'){
                    obj[key] = obj[key].replace('true', 'false')
                }
                if(Object.prototype.toString.call(obj[key]) === '[object Array]'){
                    for(const item of obj[key]){
                        this.resetData(item)
                    }
                }
                if(Object.prototype.toString.call(obj[key]) === '[object Object]'){
                    this.resetData(obj[key])
                }
            }
        }
    },
    components: {
        HdfComponent
    }
}
</script>

难点

1、联动较为复杂的,使得结构变乱;
2、因为为树结构,需要伴随深层次的遍历,若表单数据较多,可能存在性能问题

思路尚不成熟,各位大佬要有更好的方法即时留言。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值