纯Vue手撸简单的表单验证 验证失败回滚动画

1,205 阅读1分钟

纯Vue手撸简单的表单验证 验证失败回滚动画

其实我们在碰到比较长的表单的时候, 一个个字段的判断。 其实是比较麻烦的。一方面复用性不高,而且不是很优雅。所以趁着工作时间空闲之余 。写了一下

代码如下

Form表单组件

// Form.Vue
<template>
  <div class="Form">
    <slot></slot>
  </div>
</template>
<script>
import scrollIntoView from "smooth-scroll-into-view-if-needed";
export default {
  name: 'Form',
  data () {
    return {
      fields: [] // 储存当前的 form-item的实例
    }
  },
  // 类似 React的createContext();
  // 适用于 嵌套比较深的组件传值 比如爷孙组件;
  provide () {
    return {
      labelWidth: this.labelWidth,
      rules: this.rules,//校验规则
      model: this.model
    }
  },
  created () {
  // 监听当前实例上的$emit 触发的form-item-add事件
  // 这里的item代表的就是子组件的this实例
    this.$on('form-item-add', item => {
      if (item) {
        this.fields.push(item)
      }
    })
  },
  beforeDestroy () {
    this.$off('form-item-add')
    this.fields = []
  },
  methods: {
   // 手动调用 this.$refs.[form].valideForm() 进行验证
   // 碰到第一个验证失败的 直接返回
    valideForm () {
     for (let i = 0; i < this.fields.length; i++) {
        const that = this.fields[i];
        if (!that.validation()) {
          if (this.scrollToFirstError) {
               scrollIntoView(that.$refs.form_item, {
        		    behavior: "smooth",
        			scrollMode: "if-needed"
      		  });
          }
          return false;
        }
      }
      return true;
    },
  },
  props: {
    model: {
      type: Object,
      default: () => {
        return {}
      }
    },
    //是否自动回滚到第一个错误选项;
     scrollToFirstError: {
      type: Boolean,
      default: false
    },
    rules: {
      type: Object,
      default: () => {
        return {}
      }
    },
    labelWidth: {
      type: String,
      default: '6rem'
    }
  }
}
</script>

FormItem 表单子组件

// FormItem.vue
<template>
  <div
    class="form-item"
    :class="{ 'is-error': !validateState }"
   ref="form_item"
    v-show="show"
  >
    <span
      class="label"
      :style="getLabelWidth"
      :class="{ required: required }"
      >{{ label }}</span
    >
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'FormItem',
  inject: ['labelWidth', 'rules', 'model'],//接受父组件注入的值
  data () {
    return {
      validateState: true,
       show: true
    }
  },
  mounted () {
    this.setRules()
  },
  beforeDestroy () {
    this.$off('form-blur')
  },
  methods: {
    setRules () {
      // 当prop有值的时候 调用父组件的$emit事件
      // 我们的父组件也就是 Form.vue
      if (this.prop) {
        this.$parent.$emit('form-item-add', this)
        this.$on('form-blur', e => {
          this.validateState = this.validation()
        })
      }
    },
    
    validation () {
    //如果表单组件显示就会验证,不显示不进行验证
    // isNull 主要是判断是否为空的函数 成功返回true 不成功返回false
     if (this.show) {
      // 当校验规则为空的时候并且不必填 直接返回true
        if (!this.getRules && !this.required) {
          return true
        } else if (this.required && !this.getRules) {
         // 如果只有必填 只需判断是否为空
          this.validateState = isNull(this.getFieldValue, this.label)
        } else if (!this.required && this.getRules) {
         // 不必填但是有校验规则 如果有值的话调用rules的校验函数
          this.validateState =
            !this.getFieldValue ||
            this.getRules.call(this.$parent, this.getFieldValue)
        } else {
         //  必填又有校验规则 判断是否为空和rules的校验函数
          this.validateState =
            isNull(this.getFieldValue, this.label) &&
            this.getRules.call(this.$parent, this.getFieldValue)
        }
        return this.validateState;
      }else{
      	return true;
      }
     
      return this.validateState
    }
  },
  computed: {
    getLabelWidth () {
      return {
        width: this.labelWidth
      }
    },
    //获取表单的绑定的值
    getFieldValue () {
      return this.model[this.prop].replace(/\s+/, '')
    },
    getRules () {
      return this.rules[this.prop]
    }
  },
  props: {
    label: {
      type: String,
      default: ''
    },
    prop: {
      type: String,
      default: ''
    },
    required: {
      type: Boolean,
      default: false
    }
  }
}
</script>

表单的input组件

<template>
  <div class="formInput form-content">
    <slot name="label">
      <div class="label" v-if="label">
        {{ label }}
      </div>
    </slot>
    <div class="input">
      <input
        :type="type"
        :value="value"
        @blur="handleBlur"
        :maxLength="maxLength"
        @input="handleInput"
        :disabled="getDisabled"
      />
      <slot name="button"></slot>
    </div>
  </div>
</template>
<script>
export default {
  name: 'FormInput',
  methods: {
    handleInput ({ target }) {
     // 调用数据双向绑定的input事件
      this.$emit('input', target.value)
    },
    handleBlur ({ target }) {
     // 触发父组件的form-blur的事件
      this.$parent.$emit('form-blur', target.value)
    }
  },
  computed: {
    getDisabled () {
      return this.data.disabled
    }
  },
  beforeDestroy () {
    this.$parent.$off('form-blur')
  },
  props: {
    value: {},
    label: {
      type: String,
      default: ''
    },
    data: {
      type: Object,
      default: () => {
        return {}
      }
    },
    disabled: {
      type: Boolean,
      default: false
    },
    type: {
      type: String,
      default: 'text'
    },
    maxLength: {
      type: [Number, String],
      default: 1000
    }
  }
}
</script>

使用示例

<Form :model="form" labelWidth='9rem' :rules="rules" ref="form">
        <FormItem
          v-for="(item, i) in getForm"
          :key="'formItem' + i"
          :label="item.label"
          :prop="item.prop"
          :required="item.require"
        >
          <component :is="item.type" v-model="form[item.prop]" :data='item.data||{}'></component>
        </FormItem>
</Form>
<button @click='submit'>点击提交</button>
// valideEmail和valideIdCard 主要是提交校验规则的函数 成功返回true 不成功返回false
export default {
  data () {
    return {
      rules: {
        email (value) {
          return valideEmail(value)
        },
        idcardnum (value) {
          return valideIdCard(value)
        }
      },
      form: {
        email: '',
        idcardnum: '',
        country: 'China',
      }
    }
  },
  methods: {
    submit () {
      this.$nextTick(() => {
        const flag = this.$refs.form.valideForm()
        if (flag) {
          ...
        }
      })
    }
  },
  computed: {
    getForm () {
      return [
       {
          require: true,
          label: '邮箱',
          type: 'FormInput',
          prop: 'email'
        },
        {
          require: true,
          label: '身份证号',
          type: 'FormInput',
          prop: 'idcardnum'
        },
        {
          require: true,
          label: '国家地区',
          type: 'FormSelect',
          prop: 'country',
          data: {
            option_data: country,//国家列表
            change (e) {
            
            }
          }
        },
      ]

 }