vue本身没有form验证框架,veevalidate是比较流行的验证框架,使用上也很方便。我在实际使用过程中碰到一个问题,就是select控件的验证和input控件有所区别。我的form里有姓名输入框和所在省选择控件,都是必填项。表单打开时,显示如下:

这个表单是用veevalidate和bootstrapvue实现的。每个输入控件获得焦点后,如果没有输入任何东西将提示出错信息如下:

注意到state控件没有相同的行为,尽管代码是一样的。原因是Bootstrapvue没有在select上监听click和onblur实际。解决的办法如下:
template

Script,就是注册onblur事件,在事件处理函数里把状态进行修改和验证

完整的代码如下:
<template>
<ValidationObserver
:ref="`contactForm${formId}`"
v-slot="{invalid }"
tag="form"
class="w-100 bg-main-gray px-3"
@submit.prevent="submit"
>
<div class="row">
<div class="col-12 col-lg-6">
<label
:for="`${formId}-name`"
class="mt-3"
>
<span class="text-danger">
*
</span>
Name
</label>
<ValidationProvider
v-slot="{errors,touched,failed }"
name="Name"
:rules="{
regex:/^[\sa-zA-Z0-9()@&\-,.#]*$/,
required:true,
minMaxLength: [4,200]
}"
>
<b-form-input
:id="`${formId}-name`"
v-model="data.name"
autofocus
tabindex="0"
:state="touched ? !failed :null"
maxlength="200"
type="text"
/>
<div class="w-100 text-danger">
{{errors[0] }}
</div>
</ValidationProvider>
</div>
<div class="col-12 col-lg-6">
<label
:for="`${formId}-address`"
class="mt-3"
>
<span class="text-danger">
*
</span>
Address
</label>
<ValidationProvider
v-slot="{errors,touched,failed }"
name="Address"
:rules="{
regex:/^[\sa-zA-Z0-9()&\-,.#@]*$/,
required:true,
minMaxLength: [3,100]
}"
>
<b-form-input
:id="`${formId}-address`"
v-model="data.address"
:state="touched ? !failed :null"
maxlength="100"
tabindex="0"
/>
<div class="w-100 text-danger">
{{errors[0] }}
</div>
</ValidationProvider>
</div>
</div>
<div class="row">
<div class="col-12 col-lg-6">
<div class="row">
<div class="col-6">
<label
:for="`${formId}-city`"
class="mt-3"
>
<span class="text-danger">
*
</span>
City
</label>
<ValidationProvider
v-slot="{errors,touched,failed }"
name="City"
:rules="{
regex:/^[\sa-zA-Z0-9()&]*$/,
required:true,
minMaxLength: [2,50]
}"
>
<b-form-input
:id="`${formId}-city`"
v-model="data.city"
tabindex="0"
:state="touched ? !failed :null"
type="text"
maxlength="50"
/>
<div class="w-100 text-danger">
{{errors[0] }}
</div>
</ValidationProvider>
</div>
<div class="col-6">
<label
:for="`${formId}-state`"
class="mt-3"
>
<span class="text-danger">
*
</span>
State
</label>
<ValidationProvider
v-slot="{errors,touched }"
name="State"
:rules="{required:true }"
>
<b-form-select
:id="`${formId}-state`"
v-model="data.state"
:state="touched ? !isBlank(data.state) :null"
:options="states"
text-field="name"
value-field="abbreviation"
:required="true"
tabindex="0"
/>
<div class="w-100 text-danger">
{{errors[0] }}
</div>
</ValidationProvider>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="form-col">
<label
:for="`${formId}-zip`"
class="mt-3"
>
<span class="text-danger">
*
</span>
ZIP
</label>
<ValidationProvider
v-slot="{errors,touched,failed }"
name="ZIP"
:rules="{
zip:/^((\d{5})(-(\d{4}))?)|(([A-Z]\d[A-Z])(\s?)(\d[A-Z]\d))$/,
required:true,
minMaxLength: [3,20]
}"
>
<b-form-input
:id="`${formId}-zip`"
v-model="data.zip"
:state="touched ? !failed :null"
maxlength="20"
tabindex="0"
/>
<div class="w-100 text-danger">
{{errors[0] }}
</div>
</ValidationProvider>
</div>
<div class="form-col state" />
</div>
</div>
<div class="row">
<div class="col-12 col-lg-6">
<label
:for="`${formId}-email`"
class="mt-3"
>
<span class="text-danger">
*
</span>
Email Address
</label>
<ValidationProvider
v-slot="{errors,touched,failed }"
name="Email"
:rules="{
email:true,
required:true,
minMaxLength: [7,200]
}"
>
<b-form-input
:id="`${formId}-email`"
v-model="data.email"
:state="touched ? !failed :null"
type="text"
maxlength="200"
tabindex="0"
/>
<span class="w-100 text-danger">
{{errors[0] }}
</span>
</ValidationProvider>
</div>
<div class="col-12 col-lg-6">
<label
:for="`${formId}-phone`"
class="mt-3"
>
<span class="text-danger">
*
</span>
Phone Number
</label>
<ValidationProvider
v-slot="{errors,touched,failed }"
name="Phone"
:rules="{
phone:/^(([1-9]\d{9})|(\([1-9]\d{2}\) ?\d{3}-\d{4})|([1-9]\d{2}[-.]\d{3}[-.]\d{4}))$/,
required:true,
minMaxLength: [3,15]
}"
>
<b-form-input
:id="`${formId}-phone`"
v-model="data.phone"
:state="touched ? !failed :null"
tabindex="0"
maxlength="15"
/>
<span class="w-100 text-danger">
{{errors[0] }}
</span>
</ValidationProvider>
</div>
</div>
<div class="row">
<div class="col-12">
<label
:for="`${formId}-comments`"
class="mt-3"
>
<span class="text-danger">
*
</span>
Message
</label>
<ValidationProvider
v-slot="{errors,touched,failed }"
name="Comments"
:rules="{required:true,minMaxLength: [2,500] }"
>
<b-form-textarea
:id="`${formId}-comments`"
v-model="data.comments"
:state="touched ? !failed :null"
rows="4"
maxlength="500"
tabindex="0"
/>
<span class="w-100 text-danger">
{{errors[0] }}
</span>
</ValidationProvider>
</div>
</div>
<div class="row my-3">
<div class="col-12 d-flex justify-content-between">
<div
v-if="allowAttachment"
class="d-flex flex-nowrap align-items-center flex-grow-1"
>
<span class="mb-0">
Attachments:
</span>
<b-form-file
:id="`${formId}-attachment`"
v-model="data.attachment"
accept=".jpeg, .jpg, .png, .pdf"
placeholder="Choose file"
browse-text=""
/>
</div>
<span v-else />
<BButton
variant="primary"
:disabled="invalid"
class="d-flex flex-nowrap ml-1"
@click="submit"
@keypress.enter="submit"
>
Send
<b-img
v-if="sending"
class="ml-2"
width="15px"
alt="loading img"
:src="loadingGifUrl"
/>
</BButton>
</div>
</div>
<b-toast
:id="`success-toast-${formId}`"
variant="success"
solid
>
<template v-slot:toast-title>
<div class="d-flex flex-grow-1 align-items-baseline">
Thank you for your feedback!
</div>
</template>
</b-toast>
</ValidationObserver>
</template>
<script>
import {ValidationProvider,extend,ValidationObserver }from 'vee-validate';
import {required,email,regex }from 'vee-validate/dist/rules';
import {
BButton,
BFormFile,
BFormInput,
BImg,
BFormSelect,
BFormTextarea
}from 'bootstrap-vue';
import Vuefrom 'vue';
import axios from 'axios';
import {isBlank }from '@/main/ui/js/util/utils';
import loadingGifUrlfrom '@/main/ui/components/images/loadingImg';
import {EventBus }from '@/main/ui/js/event-bus';
extend('required', {
...required,
message:'{_field_} is required'
});
extend('regex', {
...regex,
message:'{_field_} can not contain special characters'
});
extend('zip', {
...regex,
message:'Invalid Zip Code'
});
extend('phone', {
...regex,
message:'Invalid phone number'
});
extend('email', {
...email,
message:'Invalid email address'
});
extend('minMaxLength', {
validate (value, { min, max }) {
return value.length >= min && value.length <= max;
},
params: ['min','max'],
message:'{_field_} needs to be between {min} and {max} characters long'
});
let validation = BFormSelect.extend({
mounted () {
let _this =this;
this.$el.onblur = () => {
_this.$parent.flags.touched =true;
_this.$parent.validate();
};
}
});
let ValidateSelect = BFormSelect.extend(validation);
export default {
name:'ContactForm',
components: {
BFormFile,
BButton,
BImg,
BFormInput,
BFormTextarea,
'b-form-select':ValidateSelect,
ValidationProvider,
ValidationObserver
},
props: {
allowAttachment: {
type:Boolean,
default:false
},
data: {
type:Object,
required:true,
default:function () {
return {};
}
},
paramName: {
type:String,
required:true
},
url: {
type:String,
required:true
},
propertyId: {
type:String,
default:null
}
},
data:function () {
return {
loadingGifUrl: loadingGifUrl,
states: [{name:'- none -',abbreviation:'' }, ...window.states],
sending:false,
formId:this.paramName
};
},
methods: {
submit () {
if (this.sending) {
// prevent double submit
return;
}
let vm =this;
this.sending =true;
let form = { ...this.data };
let data =new FormData();
data.append('attachment',form.attachment);
form.attachment =undefined;
data.append(this.paramName,JSON.stringify(form));
if (this.propertyId) {
data.append('property.id',this.propertyId);
}
function resetForm () {
Vue.set(vm,'data', {
name:'',
address:'',
phone:'',
email:'',
city:'',
state:'',
zip:'',
comments:'',
attachment:null
});
vm.$refs[`contactForm${vm.formId}`].reset();
EventBus.$emit('resetForm',vm.paramName);
}
axios
.post(`${window.baseURL}${this.url}`,data, {
headers: {
'Content-Type':'multipart/form-data'
}
})
.then(function () {
vm.sending =false;
vm.$bvToast.show(`success-toast-${vm.formId}`);
resetForm();
})
.catch(function (reply) {
vm.sending =false;
vm.$bvModal.msgBoxOk(reply.response.data.message, {
title:'Unknown Server Error Occurred',
size:'sm',
buttonSize:'sm',
okVariant:'danger',
headerClass:'p-2 border-bottom-0',
footerClass:'p-2 border-top-0',
centered:true
});
});
},
isBlank:isBlank
}
};
</script>
网友评论