Problem description

Writing code in my humble opinion is:

  • Learn the rules (see official documentation)
  • Use rules (further understand official documents as you use them)
  • Finally, customize new rules based on the original underlying official document rules (encapsulate new rules for reuse)

Therefore, this article describes the idea of customizing new rules based on the original el-form rules and secondary encapsulation, as well as the attached code that can be used directly. Let’s take a look at the renderings:

rendering

Thought analysis

The end result is configuration “write code”, just like Echarts, write different configurations, produce different effects, naturally is configuration, so you need to think about what needs to be configured in advance. Of course, you also need to consider the data echo.

  • Configure form item types (add validation rules to components)
  • Configure the name of the form item
  • Configure fields for form entries
  • Configures whether the form entry is mandatory
  • Configure the unit of the input box (if any)
  • Configure placeholder text hints
  • Configure drop-down box options data data (if it is a fixed drop-down box can be passed)
  • If the drop-down box is of enumerated value type, you need to send a request for the drop-down box options array data
  • , etc…

I want to mention form item types a little more here

Set the type of form item ~ input box

First of all, we need to know the type of the form item. Here we only use three large types to make it easier to understand. Of course, large types also include small types. As for the other types, once you understand these types, you can write your own.

  • Input box type
    • Type of text input field (verification must be filled in, not empty)
    • Type of numeric input box (validates the numeric type of input, for example, requires a positive integer, requires two decimal digits, etc.)
  • Drop-down box type
    • Fixed drop-down box type of options (here directly write dead, can be passed, such as gender drop-down box, only male and female types of options)
    • Enumerates radio drop-down box types for multiple options (requiring pre-request for data, or visible-change event for data)
    • Enumerating radio multiple drop-down box types for multiple options (ditto)
  • Time selector range type
    • Note that the binding results in an array

Finally, don’t forget the echo logic

Example of el form header data

The child component form data is dynamically rendered based on the form headers passed in from the parent component. V-for with v-if, so let’s take a quick look at the formHeader data structure, which we’ll see in the following code

// Header array data
      formHeader: [{itemType: "text".// Input box type
          labelName: "Name".// Enter the box name
          propName: "name".// Enter the field name of the box
          isRequired: true.// Mandatory
          placeholder: "Name please".// Input box placeholder hint plus, which can be used to inform users of rules
        },
        {
          itemType: "number".labelName: "Age".propName: "age".isRequired: true.unit: "year".// The numeric type must have units
          placeholder: "Please enter age (positive integer greater than 0)"}, {itemType: "selectOne".// Drop down box type 1, fixed options can be written in the configuration, such as gender only male and female
          labelName: "Gender".propName: "gender".isRequired: true.placeholder: "Please select a gender.".optionsArr: [{label: "Male".value: 1}, {label: "Female".value: 2,},],},],Copy the code

The complete code

Suggest copy and paste, run and run, so that the effect is more obvious, more easy to understand.

After all: No words,show codes

The parent component passes configuration data

<template>
  <div class="myWrap">
    <h2>Fill out the form</h2>
    <br />
    <my-form
      ref="myForm"
      :formHeader="formHeader"
      @submitForm="submitForm"
      @resetForm="resetForm"
    ></my-form>
    <h2>Form data is displayed</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 {
      // Header array data
      formHeader: [
        /** * 3 types of input box * 1. Textarea * * drop down select * 1 from type 2. Fixed configuration of el-option selectOne * 2. Enumerated value of el-option single selectTwo * 2. Enumeration value of el-option multiple selectThree * * Time selector type 1 * 1. Two time pickers, select a range * *, etc., and other types. Here are three types that can be written to mimic other types. If it is a complex form that requires linkage, it is recommended to write it one by one * after all, excessive encapsulation will lead to bad code maintenance (personal opinion) * * */
        {
          itemType: "text".// Input box type
          labelName: "Name".// Enter the box name
          propName: "name".// Enter the field name of the box
          isRequired: true.// Mandatory
          placeholder: "Name please".// Input box placeholder hint plus, which can be used to inform users of rules
        },
        {
          itemType: "number".labelName: "Age".propName: "age".isRequired: true.unit: "year".// The numeric type must have units
          placeholder: "Please enter age (positive integer greater than 0)"}, {itemType: "number".labelName: "Wages".propName: "salary".isRequired: true.unit: "Yuan/month".// The numeric type must have units
          placeholder: "Please enter your monthly salary (greater than 0 and leave two decimal places)"}, {itemType: "textarea".labelName: "Note".propName: "remark".isRequired: true.placeholder: "Please fill in the remarks"}, {itemType: "selectOne".// Drop down box type 1, fixed options can be written in the configuration, such as gender only male and female
          labelName: "Gender".propName: "gender".isRequired: true.placeholder: "Please select a gender.".optionsArr: [{label: "Male".value: 1}, {label: "Female".value: 2,},],}, {itemType: "selectTwo".// Type 2 of the drop-down box, enumeration value option. When clicking the drop-down option, send a request according to the enumeration ID to obtain the enumeration value
          labelName: "Alternative Occupations".propName: "job".isRequired: true.placeholder: "Please choose an occupation.".enumerationId: "123123123"}, {itemType: "selectTwo".// Type 2 of the drop-down box, enumeration value option. When clicking the drop-down option, send a request according to the enumeration ID to obtain the enumeration value
          labelName: "Desire".propName: "wish".isRequired: true.placeholder: "Please choose a wish.".enumerationId: "456456456"}, {itemType: "selectThree".// Type 3 of the drop-down box, enumeration value multiple options, click the drop-down option according to the enumeration ID request, obtain the enumeration value
          labelName: "Hobby".propName: "hobby".isRequired: true.placeholder: "Please choose a hobby.".enumerationId: "789789789"}, {itemType: "selectThree".// Type 3 of the drop-down box, enumeration value multiple options, click the drop-down option according to the enumeration ID request, obtain the enumeration value
          labelName: "Want to buy a cell phone".propName: "wantPhone".isRequired: true.placeholder: "Please select mobile phone".enumerationId: "147258369"}, {itemType: "dateRange".// Date range type
          labelName: "Date".propName: "date".isRequired: true,}]}; },mounted() {
    // The enumeration drop - down box must be requested before it can be correctly displayed
    // Get the value of the corresponding drop-down box in advance. Attention! Attention!
    this.formHeader.forEach((item) = > {
      if ((item.itemType == "selectTwo") | (item.itemType == "selectThree")) {
        this.$refs.myForm.getOptionsArrData(item); }}); },methods: {
    showData() {
      let apiData = {
        name: "Sun Wukong".age: 500.salary: 6666.66.remark: "The Holy Man in Heaven is also.".gender: 1.// 1 is male
        job: 1.// 1 doctor 2 teacher 3 civil servant
        wish: 3.// 1 Become a millionaire 2 Live forever 3 Family health, happiness and peace
        hobby: [1.2.3].// 1 table tennis 2 badminton 3 basketball
        wantPhone: [1.2.4].// 1 Huawei 2 Xiaomi 3 Apple 4 Samsung
        date: ["2018-06-06"."2022-05-05"]};setTimeout(() = > {
        this.$refs.myForm.form = apiData;
      }, 300);
    },
    submitForm(form) {
      console.log("Form submitted.", form);
    },
    resetForm() {
      console.log("The form resets."); ,}}};</script>
<style lang='less' scoped>
.myWrap {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  padding: 25px;
  overflow-y: auto;
}
</style>
Copy the code

Encapsulated child components are rendered dynamically based on the passed configuration data

<template>
  <div class="formWrap">
    <el-form ref="form" label-position="top" :model="form" label-width="80px">
      <template v-for="(item, index) in formHeader">
        <! -- When the type is plain text field -->
        <el-form-item
          v-if="item.itemType == 'text'"
          :key="index"
          :label="item.labelName"
          :prop="item.propName"
          :rules=" item.isRequired ? [{required: true, // If required trigger: 'blur', // Trigger mode, lose focus itemType: 'text', // Current type, text input box labelName: Item. labelName, // The name of the current input box value: form[item.propName], // The binding value of the input box validator: validateEveryData, // The validation rule function},] : []"
        >
          <el-input
            :placeholder="item.placeholder"
            v-model.trim="form[item.propName]"
            clearable
            size="small"
          ></el-input>
        </el-form-item>
        <! -- When type is numeric input box -->
        <el-form-item
          v-if="item.itemType == 'number'"
          :key="index"
          :label="item.labelName"
          :prop="item.propName"
          :rules=" item.isRequired ? [{required: true, // If required trigger: 'blur', // Trigger mode, lose focus itemType: 'number', // Current type, text input box labelName: Item. labelName, // The name of the current input box value: form[item.propName], // The binding value of the input box validator: validateEveryData, // The validation rule function},] : []"
        >
          <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>
        <! -- Text field input box -->
        <el-form-item
          v-if="item.itemType == 'textarea'"
          :key="index"
          :label="item.labelName"
          :prop="item.propName"
          :rules=" item.isRequired ? [{required: true, // Mandatory trigger: 'blur', // trigger mode, lose focus itemType: 'textarea', // Current type, text field input box labelName: Item. labelName, // The name of the current input box value: form[item.propName], // The binding value of the input box validator: validateEveryData, // The validation rule function},] : []"
        >
          <el-input
            type="textarea"
            :placeholder="item.placeholder"
            v-model.trim="form[item.propName]"
            clearable
            size="small"
          ></el-input>
        </el-form-item>
        <! -- Fixed dropdown options when type is dropdown
        <el-form-item
          v-if="item.itemType == 'selectOne'"
          :key="index"
          :label="item.labelName"
          :prop="item.propName"
          :rules=" item.isRequired ? [{{required: true, // whether it is required to trigger: '', // blur or change LabelName: item.labelName, // The name of the current input box value: Form [item.propName], // Input box input binding value validator: validateEveryData, // validation rule function},] : []"
        >
          <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>
        <! -- If the type is drop-down box 2, it belongs to enumeration value (single) drop-down box, you need to send a request to obtain enumeration value according to enumeration ID -->
        <el-form-item
          v-if="item.itemType == 'selectTwo'"
          :key="index"
          :label="item.labelName"
          :prop="item.propName"
          :rules=" item.isRequired ? [{{required: true, // whether it is required to trigger: '', // blur or change LabelName: item.labelName, // The name of the current input box Value: form[item.propName], // The binding value of the input box validator: },] : []"
        >
          <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>
        <! -- If the type is dropdown box 3, it belongs to enumeration (multiple options) dropdown box, you need to send a request to obtain enumeration value based on enumeration ID -->
        <el-form-item
          v-if="item.itemType == 'selectThree'"
          :key="index"
          :label="item.labelName"
          :prop="item.propName"
          :rules=" item.isRequired ? [{required: true, // Whether this is required trigger: 'blur', // use blur to prevent the first default verification to trigger itemType: 'selectThree', // The current type, enumeration value optional labelName: Item. labelName, // The name of the current input box value: form[item.propName], // The binding value of the input box validator: validateEveryData, // The validation rule function 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>
        <! -- When type is date range -->
        <el-form-item
          v-if="item.itemType == 'dateRange'"
          :key="index"
          :label="item.labelName"
          :prop="item.propName"
          :rules=" item.isRequired ? [{required: true, // If required trigger: '', itemType: 'dateRange', // Current type, enumeration value optional labelName: Item. labelName, // The name of the current input box value: form[item.propName], // The binding value of the input box validator: validateEveryData, // The validation rule function},] : []"
        >
          <el-date-picker
            v-model="form[item.propName]"
            format="yyyy-MM-dd"
            value-format="yyyy-MM-dd"
            clearable
            type="daterange"
            range-separator="To"
            start-placeholder="Start Date"
            end-placeholder="End Date"
            size="small"
          >
          </el-date-picker>
        </el-form-item>
      </template>
    </el-form>
    <! Submit the form and reset the form section
    <div class="btns">
      <el-button type="primary" @click="submitForm" size="small"
        >Save < / el - button ><el-button @click="resetForm" size="small">reset</el-button>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    // The header data passed by the parent component
    formHeader: {
      type: Array.default: () = > {
        return[]; }},},data() {
    var validateEveryData = (rule, value, callback) = > {
      // console.log("callback", callback);
      // console.log(" validate a rule object ", rule);
      // console.log(" user input value ", value);

      // Validates the input field type
      if (value) {
        if ((value + "").length > 0) {
          // Is used to verify the output, since the input is a string number, but the output may be a number
          callback(); // the cb function tells the verification result and must be added
          return; }}// Verify the drop-down box type
      if (
        (rule.itemType == "selectOne") |
        (rule.itemType == "selectTwo") |
        (rule.itemType == "selectThree")) {if (value) {
          if ((value + "").length > 0) {
            // Note that the enumeration value is numeric, so it is converted to a string
            callback();
            return; }}}// Different verification prompts are given according to different types
      switch (rule.itemType) {
        case "text":
          callback(new Error(rule.labelName + "Can't be empty.")); // The rules for the text type are simple and must be filled in
          break;
        case "number":
          callback(new Error(rule.labelName + "Please fill in according to the rules")); // There are many rules for numeric types
          break;
        case "textarea":
          callback(new Error(rule.labelName + "Can't be empty.")); // The rules for the text field type are also simple, but must be filled in
          break;
        case "selectOne":
          callback(new Error("Please select" + rule.labelName)); // Type 1 of the drop-down box must be filled in
          break;
        case "selectTwo":
          callback(new Error("Please select" + rule.labelName)); // Type 2 of the drop-down box must be filled in
          break;
        case "selectThree":
          callback(new Error("Please select" + rule.labelName)); // Drop down box type three multiple selection array to fill in
          break;
        case "dateRange":
          callback(new Error("Please select" + rule.labelName + "Scope")); // Drop down box type three multiple selection array to fill in
          break;

        default:
          break; }};return {
      // This object is used to store the array data values of each drop-down box. It can also be hung on the prototype of vue, but I think it is better to write in data
      selectTwoOptionsObj: {},
      // The effect is used when the drop-down box is loaded
      loadingSelect: false.// Bind data
      form: {},
      // Check rules
      validateEveryData: validateEveryData,
    };
  },
  methods: {
    // Get dropdown data
    async getOptionsArr(flag, item) {
      // console.log(flag, item);
      // If the value is true, it indicates expansion, which is simulated by sending a request based on the enumeration value id to obtain the value of the drop-down box
      if (flag) {
        this.loadingSelect = true; // Use the loading effect, preferably with a try catch
        // let result = await this.$api.getEnumList({id:item.enumerationId})
        this.getOptionsArrData(item);
      } else {
        // Solve the problem that the multi-select drop-down box loses focus check rule
        if (item.itemType == "selectThree") {
          // console.log(" Check multiple options when closed ", this.form[item.propname]);
          if (this.form[item.propName].length > 0) {
            // If at least one of them is selected, then the validation rule is removed
            this.$refs.form.validateField(item.propName); }}}},getOptionsArrData(item) {
      setTimeout(() = > {
        this.loadingSelect = false;
        if (item.enumerationId == "123123123") {
          this.selectTwoOptionsObj[item.propName] = [
            {
              label: "The doctor".value: 1}, {label: "Teacher".value: 2}, {label: "Civil servant".value: 3,},]; }if (item.enumerationId == "456456456") {
          this.selectTwoOptionsObj[item.propName] = [
            {
              label: "Become a millionaire.".value: 1}, {label: "Live forever".value: 2}, {label: "The family is healthy, happy and safe.".value: 3,},]; }if (item.enumerationId == "789789789") {
          this.selectTwoOptionsObj[item.propName] = [
            {
              label: "Table tennis".value: 1}, {label: "Badminton".value: 2}, {label: "Basketball".value: 3,},]; }if (item.enumerationId == "147258369") {
          this.selectTwoOptionsObj[item.propName] = [
            {
              label: "Huawei".value: 1}, {label: "Millet".value: 2}, {label: The word "apple".value: 3}, {label: "Samsung".value: 4,},]; }this.$forceUpdate(); // We need to force an update here, otherwise we won't be able to render the dropdown option
      }, 300);
    },
    // Number type plus verification rule
    checkInput(item) {
      console.log("Subdivision rule for numeric types, judged by item.labelName.", item);
      if (item.labelName == "Age") {
        let reg = /^[1-9]\d*$/;
        if (reg.test(this.form[item.propName] * 1)) {
          // console.log(" valid, positive integer older than 0 ");
        } else {
          this.form[item.propName] = null; }}if (item.labelName == "Wages") {
        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(" yes, keep two decimal digits ");
          this.form[item.propName] = (this.form[item.propName] * 1).toFixed(2);
        } else {
          this.form[item.propName] = null; }}if ("Field value of a numeric type") {
        // add the corresponding rule}},// Save the submission form
    async submitForm() {
      this.$refs.form.validate((valid) = > {
        if (valid) {
          this.$emit("submitForm".this.form);
        } else {
          console.log("error submit!!");
          return false; }}); },// Reset the form
    resetForm() {
      this.$refs.form.resetFields();
      this.form = {}; // After the reset, you need to reinitialize the data, otherwise there will be a problem with input not up
      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{// Set the height of the text field type.el-textarea {
          textarea {
            height: 75px ! important; }} // Specify the width percentage for the dropdown box.el-select {
          width: 100% ! important; } // The time selector specifies the width percentage.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>
Copy the code

A good memory is better than a bad pen. Write it down. Welcome criticism ^_^