乐趣区

关于element-ui:配置化elform的二次封装之思路分析附上代码可直接使用

问题形容

集体愚见编写代码其实就是:

  • 学习规定(看官网文档)
  • 应用规定(在应用的过程中进一步了解官网文档)
  • 最终基于原有底层官网文档规定再自定义新规定(封装新的规定,便于复用)

所以本文讲述一下基于原有的 el-form 的规定,进行二次封装自定义新的规定的思路,以及附上能间接用的代码。咱们先看一下效果图:

效果图

思路剖析

最终成果是配置化“写代码”,就像 echarts 一样,写不同的配置,呈现不同的成果,天然是配置,所以就要提前思考好有哪些须要配置。当然也要思考数据的回显。

  • 配置表单项类型(组件中要加上校验规定)
  • 配置表单项的名字
  • 配置表单项的字段
  • 配置表单项是否必填
  • 配置输入框的单位(如果有的话)
  • 配置 placeholder 的文本提醒
  • 配置下拉框选项数据数据(如果是固定的下拉框能够传过来)
  • 如果是枚举值类型的下拉框就须要发申请获取下拉框选项数组数据
    等 …

这里要多提一下表单项类型

配置表单项~ 输入框的类型

首先咱们要分明 form 表单项的类型,这里为了便于了解,只举例三种大类型,当然大类型中也蕴含小类型,同时也要做校验。至于别的类型,大家了解了这几个类型当前,就能够本人写了。

  • 输入框类型

    • 文本输入框类型(校验得填写,不能为空)
    • 数字输入框类型(校验输出的数字类型,比方须要正整数、须要保留两位小数等)
  • 下拉框类型

    • 固定选项的下拉框类型(这里间接写死,传过来即可,比方性别下拉框,只有男女两种类型选项)
    • 枚举多个选项的单选下拉框类型(须要提前发申请获取数据,或者 visible-change 事件发申请获取)
    • 枚举多个选项的单选多选下拉框类型(同上)
  • 工夫选择器范畴类型

留神绑定的后果值是数组即可

最初不要忘了回显逻辑哦

el-form 表头数据举例

子组件表单数据依据依据父组件传递过去的 formHeader 动静渲染。即 v -for 中搭配 v -if 去出现,先简略看一下 formHeader 数据结构,具体在后边代码中都有的

// 表头数组数据
      formHeader: [
        {
          itemType: "text", // 输入框类型
          labelName: "姓名", // 输入框名字
          propName: "name", // 输入框字段名
          isRequired: true, // 是否必填
          placeholder: "请填写名字", // 输入框 placeholder 提醒语加上,可用于告知用户规定
        },
        {
          itemType: "number",
          labelName: "年龄",
          propName: "age",
          isRequired: true,
          unit: "year", // 数字类型的要有单位
          placeholder: "请输出年龄(大于 0 的正整数)",
        },
        {
          itemType: "selectOne", // 下拉框类型一,固定的选项能够写死在配置里,比方性别只有男女
          labelName: "性别",
          propName: "gender",
          isRequired: true,
          placeholder: "请抉择性别",
          optionsArr: [
            {
              label: "男",
              value: 1,
            },
            {
              label: "女",
              value: 2,
            },
          ],
        },
      ],

残缺代码

倡议复制粘贴,运行跑起来,这样成果更加显著,更便于了解。
毕竟:no words,show codes

父组件传递配置数据

<template>
  <div class="myWrap">
    <h2> 填写表单 </h2>
    <br />
    <my-form
      ref="myForm"
      :formHeader="formHeader"
      @submitForm="submitForm"
      @resetForm="resetForm"
    ></my-form>
    <h2> 表单数据回显 </h2>
    <el-button size="small" type="primary" @click="showData"
      > 点击按钮回显数据 </el-button
    >
  </div>
</template>
<script>
import myForm from "./myForm.vue";
export default {
  components: {myForm,},
  data() {
    return {
      // 表头数组数据
      formHeader: [
        /**
         * 输入框类型 3 种
         *    1. 一般文本输入框 text
         *    2. 数字类型输入框 number
         *    3. 文本域输入框 textarea
         *
         * 下拉框 select 类型 2 中
         *    1. 固定配置的 el-option selectOne
         *    2. 枚举值的 el-option 单选 selectTwo
         *    2. 枚举值的 el-option 多选 selectThree
         *
         * 工夫选择器类型 1 种
         *    1. 两个工夫选择器、选取一个范畴
         *
         * 等等,还有其余类型,这里举三种类型,别的类型仿照着即可写进去
         * 组件封装适可而止。如果是比较复杂(奇葩)的须要联动的表单,倡议一个个写
         * 毕竟适度的封装,会导致代码不好保护(集体愚见)*
         * */
        {
          itemType: "text", // 输入框类型
          labelName: "姓名", // 输入框名字
          propName: "name", // 输入框字段名
          isRequired: true, // 是否必填
          placeholder: "请填写名字", // 输入框 placeholder 提醒语加上,可用于告知用户规定
        },
        {
          itemType: "number",
          labelName: "年龄",
          propName: "age",
          isRequired: true,
          unit: "year", // 数字类型的要有单位
          placeholder: "请输出年龄(大于 0 的正整数)",
        },
        {
          itemType: "number",
          labelName: "工资",
          propName: "salary",
          isRequired: true,
          unit: "元 / 月", // 数字类型的要有单位
          placeholder: "请输出每月工资金额(大于 0 且保留两位小数)",
        },
        {
          itemType: "textarea",
          labelName: "备注",
          propName: "remark",
          isRequired: true,
          placeholder: "请填写备注",
        },
        {
          itemType: "selectOne", // 下拉框类型一,固定的选项能够写死在配置里,比方性别只有男女
          labelName: "性别",
          propName: "gender",
          isRequired: true,
          placeholder: "请抉择性别",
          optionsArr: [
            {
              label: "男",
              value: 1,
            },
            {
              label: "女",
              value: 2,
            },
          ],
        },
        {
          itemType: "selectTwo", // 下拉框类型二,枚举值单选,在点击下拉选项时依据枚举 id 发申请,获取枚举值
          labelName: "可选职业",
          propName: "job",
          isRequired: true,
          placeholder: "请抉择职业",
          enumerationId: "123123123",
        },
        {
          itemType: "selectTwo", // 下拉框类型二,枚举值单选,在点击下拉选项时依据枚举 id 发申请,获取枚举值
          labelName: "欲望",
          propName: "wish",
          isRequired: true,
          placeholder: "请抉择欲望",
          enumerationId: "456456456",
        },
        {
          itemType: "selectThree", // 下拉框类型三,枚举值多选,在点击下拉选项时依据枚举 id 发申请,获取枚举值
          labelName: "喜好",
          propName: "hobby",
          isRequired: true,
          placeholder: "请抉择喜好",
          enumerationId: "789789789",
        },
        {
          itemType: "selectThree", // 下拉框类型三,枚举值多选,在点击下拉选项时依据枚举 id 发申请,获取枚举值
          labelName: "想买手机",
          propName: "wantPhone",
          isRequired: true,
          placeholder: "请抉择手机",
          enumerationId: "147258369",
        },
        {
          itemType: "dateRange", // 日期范畴类型
          labelName: "日期",
          propName: "date",
          isRequired: true,
        },
      ],
    };
  },
  mounted() {
    // 数据回显的时候,要先发申请获取枚举值下拉框的值才可能正确的回显,所以
    // 就提前发申请获取对应下拉框的值了,这里要留神!留神!留神!this.formHeader.forEach((item) => {if ((item.itemType == "selectTwo") | (item.itemType == "selectThree")) {this.$refs.myForm.getOptionsArrData(item);
      }
    });
  },
  methods: {showData() {
      let apiData = {
        name: "孙悟空",
        age: 500,
        salary: 6666.66,
        remark: "齐天大圣是也",
        gender: 1, // 1 代表男
        job: 1, // 1 医生 2 老师 3 公务员
        wish: 3, // 1 成为百万富翁 2 长生不老 3 家人衰弱幸福安全
        hobby: [1, 2, 3], // 1 乒乓球 2 羽毛球 3 篮球
        wantPhone: [1, 2, 4], // 1 华为 2 小米 3 苹果 4 三星
        date: ["2018-06-06", "2022-05-05"],
      };
      setTimeout(() => {this.$refs.myForm.form = apiData;}, 300);
    },
    submitForm(form) {console.log("表单提交喽", form);
    },
    resetForm() {console.log("表单重置喽");
    },
  },
};
</script>
<style lang='less' scoped>
.myWrap {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  padding: 25px;
  overflow-y: auto;
}
</style>

封装的子组件依据传递的配置数据动静渲染

<template>
  <div class="formWrap">
    <el-form ref="form" label-position="top" :model="form" label-width="80px">
      <template v-for="(item, index) in formHeader">
        <!-- 当类型为一般文本输入框时 -->
        <el-form-item
          v-if="item.itemType =='text'":key="index":label="item.labelName":prop="item.propName":rules="
            item.isRequired
              ? [
                  {
                    required: true, // 是否必填 是
                    trigger: 'blur', // 触发形式,失去焦点
                    itemType: 'text', // 以后类型,文字输入框
                    labelName: item.labelName, // 以后输入框的名字
                    value: form[item.propName], // 输入框输出的绑定的值
                    validator: validateEveryData, // 校验规定函数
                  },
                ]
              : []
          "
        >
          <el-input
            :placeholder="item.placeholder"
            v-model.trim="form[item.propName]"
            clearable
            size="small"
          ></el-input>
        </el-form-item>
        <!-- 当类型为数字类型输入框时 -->
        <el-form-item
          v-if="item.itemType =='number'":key="index":label="item.labelName":prop="item.propName":rules="
            item.isRequired
              ? [
                  {
                    required: true, // 是否必填 是
                    trigger: 'blur', // 触发形式,失去焦点
                    itemType: 'number', // 以后类型,文字输入框
                    labelName: item.labelName, // 以后输入框的名字
                    value: form[item.propName], // 输入框输出的绑定的值
                    validator: validateEveryData, // 校验规定函数
                  },
                ]
              : []
          "
        >
          <el-input
            :placeholder="item.placeholder"
            v-model.trim="form[item.propName]"
            @change="checkInput(item)"
            clearable
            size="small"
          >
            <span slot="suffix">{{item.unit}}</span>
          </el-input>
        </el-form-item>
        <!-- 当类型为文本域输入框时 -->
        <el-form-item
          v-if="item.itemType =='textarea'":key="index":label="item.labelName":prop="item.propName":rules="
            item.isRequired
              ? [
                  {
                    required: true, // 是否必填 是
                    trigger: 'blur', // 触发形式,失去焦点
                    itemType: 'textarea', // 以后类型,文本域输入框
                    labelName: item.labelName, // 以后输入框的名字
                    value: form[item.propName], // 输入框输出的绑定的值
                    validator: validateEveryData, // 校验规定函数
                  },
                ]
              : []
          "
        >
          <el-input
            type="textarea"
            :placeholder="item.placeholder"
            v-model.trim="form[item.propName]"
            clearable
            size="small"
          ></el-input>
        </el-form-item>
        <!-- 当类型为下拉框一时,固定下拉选项 -->
        <el-form-item
          v-if="item.itemType =='selectOne'":key="index":label="item.labelName":prop="item.propName":rules="
            item.isRequired
              ? [
                  {
                    required: true, // 是否必填 是
                    trigger: '', // blur 或 change 这里就不指定触发形式了,保留提交时再校验
                    itemType: 'selectOne', // 以后类型,固定下拉框类型
                    labelName: item.labelName, // 以后输入框的名字
                    value: form[item.propName], // 输入框输出的绑定的值
                    validator: validateEveryData, // 校验规定函数
                  },
                ]
              : []
          "
        >
          <el-select
            v-model="form[item.propName]"
            :placeholder="item.placeholder"
            clearable
            size="small"
          >
            <el-option
              v-for="(ite, ind) in item.optionsArr"
              :key="ind"
              :label="ite.label"
              :value="ite.value"
            ></el-option>
          </el-select>
        </el-form-item>
        <!-- 当类型为下拉框二时,属于枚举值(单选)下拉框,须要依据枚举 id 发申请获取枚举值 -->
        <el-form-item
          v-if="item.itemType =='selectTwo'":key="index":label="item.labelName":prop="item.propName":rules="
            item.isRequired
              ? [
                  {
                    required: true, // 是否必填 是
                    trigger: '', // blur 或 change 这里就不指定触发形式了,保留提交时再校验
                    itemType: 'selectTwo', // 以后类型,枚举值单选
                    labelName: item.labelName, // 以后输入框的名字
                    value: form[item.propName], // 输入框输出的绑定的值
                    validator: validateEveryData, // 校验规定函数
                  },
                ]
              : []
          "
        >
          <el-select
            v-model="form[item.propName]"
            :placeholder="item.placeholder"
            clearable
            @visible-change="
              (flag) => {getOptionsArr(flag, item);
              }
            ":loading="loadingSelect"size="small"
          >
            <el-option
              v-for="(ite, ind) in selectTwoOptionsObj[item.propName]"
              :key="ind"
              :label="ite.label"
              :value="ite.value"
            ></el-option>
          </el-select>
        </el-form-item>
        <!-- 当类型为下拉框三时,属于枚举值(多选)下拉框,须要依据枚举 id 发申请获取枚举值 -->
        <el-form-item
          v-if="item.itemType =='selectThree'":key="index":label="item.labelName":prop="item.propName":rules="
            item.isRequired
              ? [
                  {
                    required: true, // 是否必填 是
                    trigger: 'blur', // 这里用 blur,避免首次默认校验触发
                    itemType: 'selectThree', // 以后类型,枚举值多选
                    labelName: item.labelName, // 以后输入框的名字
                    value: form[item.propName], // 输入框输出的绑定的值
                    validator: validateEveryData, // 校验规定函数
                    type: 'number',
                  },
                ]
              : []
          "
        >
          <el-select
            v-model="form[item.propName]"
            :placeholder="item.placeholder"
            clearable
            @visible-change="
              (flag) => {getOptionsArr(flag, item);
              }
            ":loading="loadingSelect"
            multiple
            collapse-tags
            size="small"
          >
            <el-option
              v-for="(ite, ind) in selectTwoOptionsObj[item.propName]"
              :key="ind"
              :label="ite.label"
              :value="ite.value"
            ></el-option>
          </el-select>
        </el-form-item>
        <!-- 当类型为日期范畴 -->
        <el-form-item
          v-if="item.itemType =='dateRange'":key="index":label="item.labelName":prop="item.propName":rules="
            item.isRequired
              ? [
                  {
                    required: true, // 是否必填 是
                    trigger: '',
                    itemType: 'dateRange', // 以后类型,枚举值多选
                    labelName: item.labelName, // 以后输入框的名字
                    value: form[item.propName], // 输入框输出的绑定的值
                    validator: validateEveryData, // 校验规定函数
                  },
                ]
              : []
          "
        >
          <el-date-picker
            v-model="form[item.propName]"
            format="yyyy-MM-dd"
            value-format="yyyy-MM-dd"
            clearable
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="完结日期"
            size="small"
          >
          </el-date-picker>
        </el-form-item>
      </template>
    </el-form>
    <!-- 提交表单和重置表单局部 -->
    <div class="btns">
      <el-button type="primary" @click="submitForm" size="small"
        > 保留 </el-button
      >
      <el-button @click="resetForm" size="small"> 重置 </el-button>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    // 父组件传递过去的表头的数据
    formHeader: {
      type: Array,
      default: () => {return [];
      },
    },
  },
  data() {var validateEveryData = (rule, value, callback) => {//   console.log("callback", callback);
      //   console.log("校验某一项的规定对象", rule);
      //   console.log("用户输出的值", value);

      // 对输入框类型的校验
      if (value) {if ((value + "").length > 0) {
          // 用于回显时候的校验,因为输出的时候是字符串类型的数字,然而回显的时候可能就是数字
          callback(); // cb 函数告知校验后果,必须要加
          return;
        }
      }

      // 对下拉框类型的校验
      if ((rule.itemType == "selectOne") |
        (rule.itemType == "selectTwo") |
        (rule.itemType == "selectThree")
      ) {if (value) {if ((value + "").length > 0) {
            // 留神枚举值是数字类型的,所以这里要转换成为字符串类型的
            callback();
            return;
          }
        }
      }

      // 依据不同的类型给予不同的校验提醒
      switch (rule.itemType) {
        case "text":
          callback(new Error(rule.labelName + "不能为空")); // 文本类型的规定简略,就是得填写
          break;
        case "number":
          callback(new Error(rule.labelName + "请按规定填写")); // 数字类型的规定比拟繁多
          break;
        case "textarea":
          callback(new Error(rule.labelName + "不能为空")); // 文本域类型的规定也简略,就是得填写
          break;
        case "selectOne":
          callback(new Error("请抉择" + rule.labelName)); // 下拉框类型一 得填写
          break;
        case "selectTwo":
          callback(new Error("请抉择" + rule.labelName)); // 下拉框类型二 得填写
          break;
        case "selectThree":
          callback(new Error("请抉择" + rule.labelName)); // 下拉框类型三 多选数组得填写
          break;
        case "dateRange":
          callback(new Error("请抉择" + rule.labelName + "范畴")); // 下拉框类型三 多选数组得填写
          break;

        default:
          break;
      }
    };
    return {
      // 此对象用于存储各个下拉框的数组数据值,其实也能够挂在 vue 的原型上,不过集体认为写在 data 中好些
      selectTwoOptionsObj: {},
      // 用于下拉框加载时的成果
      loadingSelect: false,
      // 绑定的数据
      form: {},
      // 校验规定
      validateEveryData: validateEveryData,
    };
  },
  methods: {
    // 获取下拉框数据
    async getOptionsArr(flag, item) {//   console.log(flag, item);
      // 为 true 时示意开展,这里模仿依据枚举值 id 发申请,获取下拉框的值的
      if (flag) {
        this.loadingSelect = true; // 应用了加载中成果,最好加上一个 try catch 捕捉异样
        // let result = await this.$api.getEnumList({id:item.enumerationId})
        this.getOptionsArrData(item);
      } else {
        // 解决多选下拉框失去焦点校验规定依然存在问题
        if (item.itemType == "selectThree") {//   console.log("敞开时校验多选值", this.form[item.propName]);
          if (this.form[item.propName].length > 0) {
            //  如果至多抉择一个了,阐明符合要求,就再校验一次,这样校验规定就去掉了
            this.$refs.form.validateField(item.propName);
          }
        }
      }
    },
    getOptionsArrData(item) {setTimeout(() => {
        this.loadingSelect = false;
        if (item.enumerationId == "123123123") {this.selectTwoOptionsObj[item.propName] = [
            {
              label: "医生",
              value: 1,
            },
            {
              label: "老师",
              value: 2,
            },
            {
              label: "公务员",
              value: 3,
            },
          ];
        }
        if (item.enumerationId == "456456456") {this.selectTwoOptionsObj[item.propName] = [
            {
              label: "成为百万富翁",
              value: 1,
            },
            {
              label: "长生不老",
              value: 2,
            },
            {
              label: "家人衰弱幸福安全",
              value: 3,
            },
          ];
        }
        if (item.enumerationId == "789789789") {this.selectTwoOptionsObj[item.propName] = [
            {
              label: "乒乓球",
              value: 1,
            },
            {
              label: "羽毛球",
              value: 2,
            },
            {
              label: "篮球",
              value: 3,
            },
          ];
        }
        if (item.enumerationId == "147258369") {this.selectTwoOptionsObj[item.propName] = [
            {
              label: "华为",
              value: 1,
            },
            {
              label: "小米",
              value: 2,
            },
            {
              label: "苹果",
              value: 3,
            },
            {
              label: "三星",
              value: 4,
            },
          ];
        }
        this.$forceUpdate(); // 这里须要强制更新一下,否则渲染不进去下拉框选项}, 300);
    },
    // 数字类型加校验规定
    checkInput(item) {console.log("数字类型的再细分规定,能够依据 item.labelName 再写判断", item);
      if (item.labelName == "年龄") {let reg = /^[1-9]\d*$/;
        if (reg.test(this.form[item.propName] * 1)) {// console.log("符合要求,年龄大于 0 的正整数");
        } else {this.form[item.propName] = null;
        }
      }
      if (item.labelName == "工资") {let reg = /^((0{1}\.\d{1,2})|([1-9]\d*\.{1}\d{1,2})|([1-9]+\d*))$/;
        if (reg.test(this.form[item.propName] * 1)) {// console.log("符合要求,工资保留两位小数");
          this.form[item.propName] = (this.form[item.propName] * 1).toFixed(2);
        } else {this.form[item.propName] = null;
        }
      }
      if ("某个数字类型字段值") {// 加对应规定}
    },
    // 保留提交表单
    async submitForm() {this.$refs.form.validate((valid) => {if (valid) {this.$emit("submitForm", this.form);
        } else {console.log("error submit!!");
          return false;
        }
      });
    },
    // 重置表单
    resetForm() {this.$refs.form.resetFields();
      this.form = {}; // 这里重置完了当前,要从新初始化数据,否则会呈现输出不下来的问题
      this.$emit("resetForm");
    },
  },
};
</script>

<style lang='less' scoped>
.formWrap {
  width: 100%;
  /deep/ .el-form {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    .el-form-item {
      width: 47%;
      margin-bottom: 12px !important;
      .el-form-item__label {
        padding: 0 !important;
        line-height: 24px !important;
      }
      .el-form-item__content {
        // 给文本域类型定高度
        .el-textarea {
          textarea {height: 75px !important;}
        }
        // 给下拉框指定宽度百分比
        .el-select {width: 100% !important;}
        // 工夫选择器指定宽度百分比
        .el-date-editor {
          width: 100% !important;
          .el-range-separator {width: 10% !important;}
        }
        .el-form-item__error {padding-top: 1px !important;}
      }
    }
  }
  .btns {
    width: 100%;
    text-align: center;
    margin-top: 12px;
  }
}
</style>

好忘性不如烂笔头,记录一下吧。欢送批评指正 ^_^

退出移动版