纯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) {
}
}
},
]
}