12

概述

后台管理系统里面有非常多的表单需求,我们希望能够通过写一个json格式的数据,通过vue的循环动态地去渲染动态表单。并且能够在外部得到渲染出来的表单的数据,可以对表单进行重置操作。我结合element ui的控件的下拉框,输入框,时间选择控件和vue-treeselect,做了一个动态表单。

clipboard.png

v-model的理解

先简单讲一下vue-model是怎么玩的。其实vue-model相当于给表单元素传递一个value,外部监听input事件。所以我们自己封装表单组件的时候也是可以传递一个value值,监听input事件获取输入的值。

<input type="text" v-model="something">
<!--等价于-->
<input type="text"
       v-bind:value="something"
       v-on:input="something = $event.target.value">

封装表单组件

组件最重要的开发思想就是设计好输入输出。这里就以下拉框组件为例吧。使用的是element ui的下拉框,进行一个简单封装。
输入:name:每个表单的数据标识,如区域编码输入框,父元素应该传递areaCode过来。

 value: 表单选择/输入的值,从父元素获取后赋值给currentValue,通过监听父元素的值实现同步变 
        化。
 options:下拉框要渲染的选项值,一般是个对象数组。

输出:onInputEvent,emit一个input事件,让父元素能够感知组件的数据变化。
也就是可以在组件使用的地方监听input事件

<template>
  <el-form-item :label="label">
    <el-select v-model="currentValue" @input="onInputEvent">
      <el-option
        v-for="item in options"
        :key="item.value"
        :label="item.label"
        :value="item.value">
      </el-option>
    </el-select>
  </el-form-item>
</template>

<script>
  import formMixins from '../../../mixins/form-model'
  export default {
    name: "SelectList",
    props: ['name', 'label', 'value','options'],
    mixins: [formMixins],
    data() {
      return {
        currentValue: this.value
      }
    },
    methods: {
      onInputEvent(value) {
        this.$emit('input', this.name, value);
      }
    },
    watch: {
      value(val) {
        this.currentValue= val;
      }
    }
  }
</script>

一点封装

由于每个表单组件都是监听父元素的value值变化,数据变化时都是触发onInputEvent并执行this.$emit('input'),所以我们可以把这部分内容抽取出来放在mixins里面。
form-model.js

export default {
  props: ['name', 'value'],

  data () {
    return {
      currentValue: this.value
    };
  },
  methods: {
    onInputEvent(value) {
      this.$emit('input', this.name, value);
    },
    reset() {
      this.currentValue = "";
    }
  },
  watch: {
    value (val) {
      this.currentValue = val;
    }
  }
};

然后我们的下拉框组件就可以少写一些共用的代码,直接用 mixins: [formMixins]

<template>
  <el-form-item :label="label">
    <el-select v-model="currentValue" @input="onInputEvent">
      <el-option
        v-for="item in options"
        :key="item.value"
        :label="item.label"
        :value="item.value">
      </el-option>
    </el-select>
  </el-form-item>
</template>

<script>
  import formMixins from '../../../mixins/form-model'
  export default {
    name: "SelectList",
    props: ['name', 'label', 'value', 'options'],
    mixins: [formMixins],
    data() {
      return {
        currentValue: this.value
      }
    }
  }
</script>

动态生成表单

这里主要是根据配置的数据,循环生成表单组件。默认提供提交和重置按钮,如果不需要可以通过slot传递其他操作按钮。这里的要点主要有:
监听表单组件的数据变化:
每个表单组件都有一个name标识它的业务含义,绑定的数据也是formData[field.name],@input事件传递updateForm,在updateForm里面更新this.formData[name],保证了this.formData里面的数据是和表单组件选择/填写的内容一致。
重置时改变表单组件的数据:
因为组件内部会监听父元素的value,所以这里只要清空this.formData的值,组件内部的数据也会跟着清空。

    <template>
  <div>
    <el-form :inline="true" ref="form" :model="formData" class="demo-form-inline">
      <el-col :span="field.cols" v-for="(field, index) in config.fieldsConfig" v-bind:key="index">
        <component :key="index"
                    :is="field.fieldType"
                    :label="field.label"
                    :value="formData[field.name]"
                    :multiple="field.multiple"
                    @input="updateForm"
                    v-bind="field"
                    :options="field.options"
                    :ref="field.name"
        >
        </component>
      </el-col>
      <slot name="buttons">
        <el-button type="primary" @click="submit" size="small">{{onSubmitText}}</el-button>
        <el-button type="default" @click="reset" size="small">{{onResetText}}</el-button>
      </slot>
    </el-form>
  </div>
</template>
<script>
  import SelectList from './basicComponent/SelectList'
  import TextInput from './basicComponent/TextInput'
  import TimeSelector from './basicComponent/TimeSelector'
  import SelectTree from './basicComponent/SelectTree'
  import StaffSelectPopedit from './businessComponent/StaffSelectPopedit'
  export default {
    name: "FormGenerator",
    components: { SelectList, TextInput, TimeSelector, SelectTree, StaffSelectPopedit},
    props: ["config", "value"],
    data() {
      return {
        formData: this.value,
        onSubmitText: this.config.buttons.onSubmitText || '提交',
        onResetText: this.config.buttons.onResetText || '重置'
      }
    },
    methods: {
      updateForm(fieldName, value) {
        this.formData[fieldName] = value;
      },
      submit() {
        this.$emit("submit");
      },
      reset() {
        for(var name in this.formData) {
          if(typeof this.formData === "String") {
            this.formData[name] = "";
          } else {
            this.formData[name] = null;
          }
        }
      }
    }
  }
</script>

业务使用的地方

像下拉框的选择数据,这些应该是后台渲染的,所以我们暂时用setTimeout模拟一下。感觉这里this.config.fieldsConfig[4].options写的不太优雅,依赖于配置数据的顺序肯定不是啥好事情。求大神指点。

<template>
  <div>
    <form-generator :config="config"
                      @submit="getFormData"
                      v-model="formData"
    >
    </form-generator>
  </div>
</template>
<script>
  import FormGenerator from '../components/form/FormGenerator'
  export default {
    name: "FormGeneratorDemo",
    components: { FormGenerator },
    created () {
      this.queryOrderType();
      this.queryAreaTree();
    },
    data() {
      return {
        formData: {
          orderCode: "",
          orderType: "",
          beginTime: "",
          endTime: "",
          area: [],
          staff:""
        },
        config: {
          fieldsConfig: [
            {
              name: 'orderCode',
              label: '定单编码',
              fieldType: 'TextInput',
              cols: 8
            },
            {
              name: 'orderType',
              label: '定单类型',
              fieldType: 'SelectList',
              options: [],
              cols: 8
            },
            {
              name: 'beginTime',
              label: '开始时间',
              fieldType: 'TimeSelector',
              cols: 8
            },
            {
              name: 'endTime',
              label: '结束时间',
              fieldType: 'TimeSelector',
              cols: 8
            },
            {
              name: 'area',
              label: '区域',
              fieldType: 'selectTree',
              options: [],
              multiple: true,
              cols: 8
            },
            {
              name: 'staff',
              label: '人员选择',
              fieldType: 'StaffSelectPopedit',
              cols: 8
            }
          ],
          buttons: {
            onSubmitText: '提交',
            onResetText: '重置'
          }
        }
      }
    },
    methods: {
      getFormData() {
        console.log(this.formData);
      },
      queryOrderType() {
        setTimeout(() => {
          this.config.fieldsConfig[1].options = [
            { label: 'select1', value: 'key1'},
            { label: 'select2', value: 'key2'},
            { label: 'select3', value: 'key3'}
          ];
         }, 100)
      },
      queryAreaTree() {
        this.config.fieldsConfig[4].options = [
          {
            id: 'a',
            label: 'a',
            children: [{
              id: 'aa',
              label: 'AA',
            }, {
              id: 'ab',
              label: 'AB',
            }],
          }, {
            id: 'b',
            label: 'B',
          }, {
            id: 'c',
            label: 'C',
          }
        ]
      }
    }
  }
</script>

大概就是这样的思路,我们希望我们只要写上面那样子的配置数据就可以动态生成各种这样的表单组件,不用写一大堆重复代码。如果有更好的解决办法,欢迎和我联系。另外,代码路径https://github.com/supportlss...


supportlss
230 声望16 粉丝