vue动态生成表单元素

86 篇文章 12 订阅
11 篇文章 0 订阅

前几天接了一个需求,需要动态生成一个表单数据,然后提交,提交完数据后。通过编辑按钮进入时,需要进行数据回填。

一、页面展示:


I. 没生成表单前的状态

Vue-UEedit
在这里插入图片描述
UEedit
在这里插入图片描述

II. 单机生成表单生成表单
在这里插入图片描述


III. 根据选择方式展示不同的表单元素

在这里插入图片描述


IV. 如果从编辑页进入该页面有数据的话,进行数据回填

样式同第三点相似,这里不再说明


二、思路:

请输入标题请选择类型 为父组件;请选择方式 为子组件;根据请选择方式出来的内容为孙子组件, 单选和下拉下面的生成参数是从孙组件


三、难点:

动态生成数据、数据多层传递(四层数据向下传递+四层数据向上传递)、数据格式转换、数据清空、
数据关联、数据解耦、空白表单数据添加、 含原始表单数据添加、表单数据删除、非响应式数据处理、
合并表单数据(判空+去重+重新排序)、深层数据传递监听


四、结构上分析:

(1)数据类型: 两层数据,三层数据,四层数据

二层数据:
新增
层级1 --> 层级2–>层级1(整合数据)–>提交
编辑
回填 层级1 --> 层级2
修改+新增 层级1 --> 层级2–>层级1 --> 提交

三层数据:
新增
层级1 --> 层级2–>层级3–>层级2–>层级1(整合数据)–>提交
编辑
回填 层级1 --> 层级2–>层级3
修改+新增 层级1 --> 层级2–>层级3–>层级2–>层级1(整合数据)–>提交

四层数据:
新增
层级1 --> 层级2–>层级3–>层级4–>层级3层级2–>层级1(整合数据)–>提交
编辑
回填 层级1 --> 层级2–>层级3–>层级4
修改+新增 层级1 --> 层级2–>层级3–>层级4–>层级3–>层级2–>层级1(整合数据)–>提交

(2)生成类型:

普通文本输入框、数字输入框、下拉框、

关联值类型1:文本输入框+文本输入框、

关联值类型2:文本输入框+单选框

(3)关键值传递: 新增/编辑来回数据格式化转换:

例如:

提交时候分享参数:

// 格式化URL动态添加数据格式
    formatURL(obj) {
      let url = "";
      const tempArr = [];
      const arr = Object.keys(obj);
      let leng = 0;
      arr.map(item => {
        if (item.slice(-1) * 1 > leng) {
          leng = item.slice(-1) * 1;
        }
      });
      for (let i = 1; i <= leng; i += 1) {
        const obj1key = arr.filter(item => item.slice(-1) * 1 === i);
        const obj1 = {};
        obj1key.map(item => {
          obj1[item] = obj[item];
        });
        tempArr.push(obj1);
      }
      tempArr.forEach(v => {
        Object.keys(v).map(key => {
          url += `${key}=${v[key]}&`;
        });
      });
      url = url.substring(0, url.length - 1);
      return `${this.data.link}?${url}`;
    }

回填时解析分享参数:


  // 解析返回的分享参数
    formatDEcodeURL(URL) {
      const urlsrc = URL;
      const arr1 = [];
      const arr2 = [];
      const obj = {};
      urlsrc.split("&").map(v => {
        if (v.substring(0, 4) === "link") {
          arr1.push(v);
        }
        if (v.substring(0, 4) === "type") {
          arr2.push(v);
        }
      });
      arr1.forEach(v => {
        arr2.forEach(k => {
          if (v.charAt(4) === k.charAt(4)) {
            obj[`link${v.charAt(4)}`] = v.substring(6);
            obj[`type${k.charAt(4)}`] =
              k.substring(6) === "1" ? "必填" : "非必填";
          } else {
            obj[`link${v.charAt(4)}`] = v.substring(6);
            obj[`type${k.charAt(4)}`] =
              k.substring(6) === "1" ? "必填" : "非必填";
          }
        });
      });
      this.todoObj = obj;
      const max = Math.max(arr1.length, arr2.length);
      for (let i = 0; i < max; i += 1) {
        this.addShareLink();
      }
    },
(4)监听第二三层数据变化,实现数据实时改变:

例如:

  watch: {
    // 新增页面 监测父组件传入值变化
    secdown: {
      handler(val) {
        this.changeChoose(val);
      },
      deep: true,
      immediate: true
    },
    // 编辑页面 监测子组件变化,来刷新子组件其余值
    chooseTypes: {
      handler(val) {
        this.changeTypes(val);
      },
      deep: true,
      immediate: true
    }
  },

五、代码分析:

动态生成数据父组件讲解

HTML

           <div
              v-for="item in createFormArray"
              :key="item.id">
              <el-row
                :gutter="24"
                style="margin-top:10px;">
                <el-col :span="3">
                  <div class="item-title">输入项{{ item.id }}:</div>
                </el-col>
                <el-col :span="3">
                  <el-input
                    v-model="createFormObj[item.value]"
                    placeholder="请输入标题"/>
                </el-col>
                <el-col :span="3">
                  <el-select
                    v-model="createFormObj[item.kind]"
                    placeholder="请选择类型">
                    <el-option
                      v-for="(item,index) in choose"
                      :key="index"
                      :label="item.label"
                      :value="item.value"/>
                  </el-select>
                </el-col>
                
                <!-- 嵌入的第二层,请选择方式组件-->
                
                <DynamicData
                  :dynamical = "item.id"
                  :secdown = "item.indexDA"
                  @receive= "receive"/>
              </el-row>
            </div>

JS

import DynamicData from "./dynamic_data"; //引入选择方式组件
export default {
components: {
    VueEditor,
    DynamicData
  },
data() {
    return {
          createIndex:1,      //生成表单的索引
          countPage: 0,       //输入需要生成表单的个数
          createFormObj: {},     //存放每一个生成表单对象
          createFormArray: [],   //生成表单所有生成对象的数组
          choose: [         //请选择类型选择器里面的选择值
		      {
		          value: 1,
		          label: "必填"
		        },
		        {
		          value: 2,
		          label: "非必填"
		        }
			],
          
          
    }
    },
    createForm() {
      for (; this.createIndex <= this.countPage; this.createIndex += 1) {
      
       //造数据,给每一项添加上 id,value,kind, type方便我们后面绑定数据使用(绑定的数据我们给后面加上索引区分)
       
        this.createFormArray.push({    
          id: this.createIndex,
          value: `link${this.createIndex}`,
          kind: `kind${this.createIndex}`,
          type: `type${this.createIndex}`
        });
      }
    }
}


DynamicData儿子组件讲解

HTML层

<template>
  <div class="data-manage-container">
    <el-col :span="3">
      <el-select
        v-model="chooseTypes"
        placeholder="请选择方式"
        @change="storeType">
        <el-option
          v-for="item in options"
          :key="item.value"
          :label="item.label"
          :value="item.value"
        />
      </el-select>
    </el-col>
    <div>
      <!-- 传入 项数 和 选择的方式 -->
      <InputItem
        :child = "secdown"
        :showitem = "dynamical"        //从儿子组件将“选择的方式” 传给孙子组件
        :showindex="+chooseTypes" //从儿子组件将“项数” 传给孙子组件
        @lastchild="getChild"/>         //为了获取孙子组件数据,绑定函数传递过去
    </div>
  </div>
</template>

JS层

<script>
import InputItem from "./show_input_item";  //引入孙子组件

export default {
  name: "DynamicData",
  components: {
    InputItem
  },
  props: {
    dynamical: {
      type: Number,
      default: 0
    },
    types: {
      type: Function,
      default() {}
    },
    secdown: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      chooseTypes: "", 
      options: [     //选择的类型
        {
          value: 1,
          label: "文字输入"
        },
        {
          value: 2,
          label: "电话号码"
        },
        {
          value: 3,
          label: "文件上传"
        },
        {
          value: 4,
          label: "下拉框选择"
        },
        {
          value: 5,
          label: "单选框"
        },
        {
          value: 6,
          label: "数字输入"
        },
        {
          value: 7,
          label: "Hidden"
        }
      ],
      childrenMess: []
    };
  },
  watch: {
    secdown: {
      handler(val) {
        this.changeChoose(val);
      },
      deep: true,
      immediate: true
    }
  },
  methods: {
    getChild(val) {    // 接受孙子组件传递过来的数据,并将数据传给父组件
      this.$emit("receive", { ...this.childrenMess, ...val });
    },
    storeType(val) {     // 每天选择时,接受孙子组件传递过来的数据,并将数据传给父组件
      this.childrenMess = { id: this.dynamical, value: val };
      this.$emit("receive", { ...this.childrenMess });
    },
    changeChoose(val) {
      this.chooseTypes = val.type;
    }
  }
};
</script>

InputItem孙子组件讲解

HTM层:

<template>
  <div class="data-manage-container">
    <div v-show="showindex === 1">
      <el-col :span="3">
        <el-input
          v-model="generated_data.input_title"
          placeholder="请输入默认值"
          @change="getTextOne(showindex,$event)"/>
      </el-col>
      <el-col :span="3">
        最大长度:<el-input-number
          v-model="generated_data.numLength"
          :min="1"
          size="small"
          label="描述文字"
          @change="getNumberOne(showindex,$event)"/>
      </el-col>
    </div>

    <div v-show="showindex === 4 || showindex === 5">
      <div style="visibility:hidden;">
        <el-select
          v-model="generated_data.formvalue"
          placeholder="请输入默认值">
          <el-option
            v-for="item in selectValue"
            :key="item.value"
            :label="item.label"
            :value="item.value"/>
        </el-select>
      </div>
      <div class="reduceparams">
        <el-row
          :gutter="10"
          style="padding-left:200px;">
          <el-col :span="5">
            <div
              class="item-title"
              @click = "formAddParam"> <i class="el-icon-circle-plus"/></div>
          </el-col>
        </el-row>
        <el-row
          v-for="(todo,index) in FormTodoParams"
          :key="todo.id">
          <el-row
            :gutter="20"
            style="padding-left:200px;padding-top:10px;">
            <el-col :span="1">
              <div
                class="item-title"
                style="padding-top:10px;"
                @click = "formRemoveParam(index)"> <i class="el-icon-remove"/></div>
            </el-col>
            <el-col
              :span="1"
              style="margin-top:10px;">
              参数:
            </el-col>
            <el-col
              :span="3"
              style="margin-left: -38px;">
              <el-input
                v-model.trim="formObj[todo.value]"
                placeholder="输入内容"
                size="mini"
                clearable
                @change="getParamsFour(showindex,formObj)"/>
            </el-col>
            <el-col
              :span="3"
              style="margin-left: 10px;
              margin-top:10px;">
              <el-radio-group
                v-model="generated_data.defaltRadio"
                size="small"
                @change="getSelectFour(showindex,$event)">
                <el-radio
                  :label="formObj[todo.value]">选择为默认值</el-radio>
              </el-radio-group>
            </el-col>
          </el-row>
        </el-row>
      </div>
    </div>

    <div v-show="showindex === 6">
      <el-col :span="3">
        <el-input
          v-model="generated_data.selectData"
          placeholder="请输入默认值"
          @change="getTextSix(showindex,$event)"/>
      </el-col>
      <el-col :span="3">
        最小值:<el-input-number
          v-model="generated_data.selectData_min"
          :min="0"
          size="small"
          label="最小值"
          @change="getMinSix(showindex,$event)"/>
      </el-col>
      <el-col :span="3">
        最大值:<el-input-number
          v-model="generated_data.selectData_max"
          :min="0"
          size="small"
          label="最大值"
          @change="getMaxSix(showindex,$event)"/>
      </el-col>
    </div>
    <div v-show="showindex === 7">
      <el-col :span="3">
        <el-input
          v-model="generated_data.selectnomalData"
          placeholder="请输入默认值"
          @change="getMaxSeven(showindex,$event)"/>
      </el-col>
    </div>
  </div>
</template>

HTML这里主要是根据不同的选择方式显示不同的表单内容,


JS层

<script>
export default {
  name: "InputItem",
  components: {},
  props: {
    showindex: {
      type: Number,
      default: 0
    },
    showitem: {
      type: Number,
      default: 0
    },
    child: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      indexNormal: 0,
      formObj: {},
      selectValue: [
        {
          value: 1,
          label: "必填"
        },
        {
          value: 0,
          label: "非必填"
        }
      ],
      generaData: {
        inputTitle: "",
        numLength: 0,
        formvalue: "",
        selectData: 0,
        selectData_min: 0,
        selectData_max: 0,
        selectnomalData: "",
        defaltRadio: "",
        value: 0
      },
      formIndex: 0,
      FormTodoParams: [],
      typeFour: {
        choose: "",
        chooseObj: {}
      },
      typeFive: {
        choose: "",
        chooseObj: {}
      }
    };
  },
  watch: {
    // 新增页面 监测父组件传入值变化
    child: {
      handler(val) {
        this.watchChoose(val);
      },
      deep: true,
      immediate: true
    },
    // 编辑页面 监测子组件变化,来刷新子组件其余值
    generaData: {
      handler(val) {
        this.watchGeneraData(val);
      },
      deep: true,
      immediate: true
    }
  },
  mounted() {},
  methods: {
    // 编辑时有数据触发 数据回填
    watchChoose(val) {
      this.generaData.inputTitle = val.text;
      this.generaData.numLength = val.length;
      this.generaData.selectData = +val.num_default;
      this.generaData.selectData_min = +val.min;
      this.generaData.selectData_max = +val.max;
      this.generaData.formvalue = val.is_required;
      this.generaData.selectnomalData = val.novel;
      this.generaData.defaltRadio = val.default;
      this.generaData.id = val.id;
      this.generaData.type = this.showindex;

      if (val.type_value && val.type_value.length) {
        this.indexNormal = val.type_value.length;
        val.type_value.forEach((v, i) => {
          this.FormTodoParams.push({
            id: i + 1,
            value: `value${i + 1}`
          });
        });
        for (let i = 1; i <= val.type_value.length; i += 1) {
          this.formObj[`value${i}`] = val.type_value[i - 1];
        }
      }
    },
    watchGeneraData(val) {
      return val;
    },
    // 没有 子组件时刷新页面
    getInputBox(index) {
      if (index === 1 || index === 6 || index === 7) {
        this.watchGeneraData(this.generaData);
        this.integrationData(index);
      }
    },
    // 当选择"单选"或者"下拉"时生成表单元素
    formAddParam() {
      this.formIndex += 1;
      if (this.indexNormal > 0) {
        this.FormTodoParams.push({
          id: this.formIndex + this.indexNormal,
          value: `value${this.formIndex + this.indexNormal}`
        });
      } else {
        this.FormTodoParams.push({
          id: this.formIndex,
          value: `value${this.formIndex}`
        });
      }
    },
    formRemoveParam(index) {
      this.FormTodoParams.splice(index, 1);
    },
    // 整合并获取输入数据
    integrationData(index) {
      switch (index) {
        case 1:
          this.$emit("lastchild", {
            ...this.generaData,
            type: this.showindex,
            id: this.showitem
          });
          break;
        case 4:
          this.$emit("lastchild", {
            ...this.generaData,
            ...this.typeFour,
            type: this.showindex,
            id: this.showitem
          });
          break;
        case 5:
          this.$emit("lastchild", {
            ...this.generaData,
            ...this.typeFive,
            type: this.showindex,
            id: this.showitem
          });
          break;
        case 6:
          this.$emit("lastchild", {
            ...this.generaData,
            type: this.showindex,
            id: this.showitem
          });
          break;
        case 7:
          this.$emit("lastchild", {
            ...this.generaData,
            type: this.showindex,
            id: this.showitem
          });
          break;
        default:
          break;
      }
    },
    // 下拉框处理
    getSelectFour(index, val) {
      if (index === 4) {
        this.typeFour.choose = val;
        this.integrationData(index);
      } else {
        this.typeFive.choose = val;
        this.integrationData(index);
      }
    },
    // 下拉框处理
    getParamsFour(index, val) {
      if (index === 4) {
        this.typeFour.chooseObj = val;
        this.integrationData(index);
      } else {
        this.typeFive.chooseObj = val;
        this.integrationData(index);
      }
    }
  }
};
</script>

这里代码并不是全部代码,只是抽出部分进行讲解,父组件有1300行左右的代码,主要是细节处理所有并没有贴出来。

  • 4
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值