表单校验功能在开发中是很常见的,尤其是后台操作系统,当然面向用户的前端页面也会有表单功能,比如:登录、提交电话等场景。
早在jquery一把梭的年代,表单校验开发起来是灰常方便的,毕竟jquery为我们封装好了各种api和方法,拿来用就好了;到了现阶段,各大框架(element、小程序等)也封装了相应的表单校验方法,也都可以在基础之上再进行进一步封装已满足不同的场景。但是,毕竟场景是无法预估的,有些场景通过原有api封装可能开发起来更加有难度或者没法实现(根本原因可能是因为本人技术太菜了),这种时候只能自己自定义校验并提交了。
近期开发小程序时,就遇到了发送短信功能的场景,对于前端也就是校验手机号和是否勾选的问题。乍看起来并不复杂,但是由于刚接触小程序不久,很多东西不是很熟悉,个人觉得原生提供的校验方法不是很适用。
- 首先从样式方面:
checkbox勾选和标签变色效果,前面我也吐槽过小程序复选框样式问题(小程序初实践总结),此处覆盖css通过实验并没有达到理想效果。
- 其次是逻辑校验问题:
校验和交互基本逻辑如下:
①、手机号校验;
②、手机号校验成功,自动勾选当前项;手机号校验不成功,提示toast;
③、提交表单时,如果有勾选但手机号校验不通过,则提示toast;如果至少有一项手机号校验通过并且勾选当前项,其余均未勾选,则提交符合要求数据;
基于上面两个方面的考虑,决定表单才用自定义方式进行开发校验。
一、基本表单结构
这部分没什么好说的,循环后台返回的标签,做出列表和提交按钮即可,只列出主要部分代码。
<view wx:for="{{iconList}}" wx:key="{{index}}">
<view class="checkbox" data-labelId="{{item.labelId}}" bindtap="{{choseFam}}"></view>
<view>{{item.labelName}}</view>
<input class="inputs" type="digit" maxlength="11" bindinput="inputBlur" data-labelId="{{item.labelId}}" placeholder="请输入手机号" />
</view>
<view class="submit" bindtap="submitCheck">发送短信邀请</view>具体样式略,效果图如下:
二、基本交互和验证
由于业务比较急,写验证的时候饶了很大一个圈,代码逻辑看上去也复杂了很多😭,后面进行代码梳理的时候发现校验逻辑可以简化很多,优化后的校验第三部分会提到,这部分贴出交互和校验代码先~
//===手机号校验
checkTel(num){
let regMes = /^[1][3,5,6,7,8,9][0-9]{9}$/;
if(!regMes.test(num)){
return false
}else{
return true
}
}
//===判断数组中对象是否有重复项
isRepeat(arr, str){
let hash = {};
for(let key in arr){
if(hash[arr[key][str]]){
return true
}
hash[arr[key][str]] = true
}
return false
}
//===自定义checkbox切换,同时给元数据添加切换属性,用于实时监听切换效果
choseFam(e){
let labelId = e.currentTarget.dataset.labelid;
let choseList = this.data.iconList
choseList.map((item)=>{
if(item.labelId == labelid){
item.checked = false
}else{
item.checked = true
}
})
this.setData({
iconList: choseList
})
}
//===输入框校验及校验通过自动勾选
inputBlur (e) {
let labelid = e.currentTarget.dataset.labelid;
let iptValue = e.detail.value;
let fromObj = this.data.famTelObj;
let choseFamlist = this.data.iconList;
// 校验通过时,收集手机号,同时对应红点选中
if(iptValue.length == 11){
if (this.checkTel(iptValue)) {
fromObj[labelid] = iptValue
choseFamlist.map((item)=>{
if(item.labelId == labelid){
item['mobile'] = iptValue
if (!item.checked) {
item.checked = true
}
}
})
} else {
// 校验不通过时,清空对应的值,防止删除了输入值,依然存在的情况
fromObj[labelid] = '‘
delete fromObj[labelid]
choseFamlist.map((item)=>{
if(item.labelId == labelid){
item['mobile'] = ''
}
})
showToast('请输入正确的手机号')
}
this.setData({
famTelObj: fromObj,
iconList: choseFamlist
})
}else{
fromObj[labelid] = ''
delete fromObj[labelid]
choseFamlist.map((item)=>{
if(item.labelId == labelid){
item['mobile'] = ''
}
})
}
}简单说下输入框逻辑,在此处我考虑通过一个对象手机输入框的信息,校验通过时更新数据同时更新列表内容,校验不通过则清空此对象的当前项,并吧列表里当前项清空。(当然后面优化发现,用对象收集信息这个操作完全是多余的,直接控制列表各项就好了)。
最后是表单校验逻辑,此处是优化前逻辑(校验较复杂,可以直接忽略直接看优化后的逻辑😂😁😁),不做过多解释,直接上代码:
submitCheck () {
let choseFamlist = this.data.iconList;
let famObj = this.data.famTelObj;
let choseFamTrue = choseFamlist.filter(item => item.checked);
let famObjArr = Object.values(famObj);
let famObjArrVal = [];
if (famObjArr.length) {
famObjArrVal = famObjArr.filter(item => item)
}
// 提交时校验
if (choseFamTrue.length == 0 && famObjArrVal.length == 0) {
showToast('请选择邀请的家人')
} else if (choseFamTrue.length !== 0 && famObjArrVal.length == 0) {
showToast('请输入正确的手机号')
} else if (choseFamTrue.length == 0 && famObjArrVal.length !== 0) {
showToast('请勾选邀请的家人')
} else {
if(choseFamTrue.length > famObjArrVal.length){
console.log('存在勾选但是未输入手机号情况')
showToast('请输入正确的手机号')
}else if(choseFamTrue.length < famObjArrVal.length){
console.log('输入比选中的多')
let subArr = choseFamlist.filter((item)=>{
return item.checked && item.mobile
})
let checkInputNo = choseFamlist.filter((item)=>{
return item.checked && !item.mobile
})
if(subArr.length){
if(checkInputNo.length){
console.log('存在同一个选中和输入的,但是也存在选中未输入的')
showToast('请输入正确的手机号')
}else{
if(this.isRepeat(subArr, 'mobile')){
console.log('提交项有重复手机号')
showToast('请勿输入重复手机号')
}else{
console.log('只提交勾选和输入都有的且不重复手机号')
this.setData({
submitTelArr: subArr
})
//提交数据,请求接口
this.submitData()
}
}
}else{
console.log('选中和输入的没有相同的')
showToast('请输入正确的手机号')
}
}else{
console.log('勾选和填写一样多')
let checkInputNo = choseFamlist.filter((item)=>{
return item.checked && !item.mobile
})
if(checkInputNo.length){
console.log('存在勾选但是未填写手机号')
showToast('请输入正确的手机号')
}else{
//获取弹窗应该展示的手机号等信息内容
let subArr = choseFamlist.filter((item)=>{
return item.checked && item.mobile
})
if(this.isRepeat(subArr, 'mobile')){
console.log('提交项有重复手机号')
showToast('请勿输入重复手机号')
}else{
console.log('success')
this.setData({
submitTelArr: subArr
})
//提交数据,请求接口
this.submitData()
}
}
}
}话不多说,只看代码就觉得这个逻辑肯定是走了弯路了,让我们直接看优化后的校验逻辑。
三、优化后的校验
基础的手机号、输入框校验、提交表单逻辑都不变,只优化校验部分。由于在处理复选框和输入框时,分别把选中字段和手机号字段添加到了数据列表里,因此我们只要通过数据列表去判断各项即可,不必再单独收集数据去做多余的校验了。
//只写了大的逻辑判断,未做具体数据的处理
submitCheck(){
let choseFamlist = this.data.iconList;
//收集选中同时校验通过的各项
let checkAndVal = choseFamlist.filter((item) => {
return item.checked && item.mobile
});
//收集只选中,但是手机号校验不通过各项
let checkNoVal = choseFamlist.filter((item) => {
return item.checked && !item.mobile
});
//收集未选中,但是手机号校验通过各项
let noCheckAndVal = choseFamlist.filter((item) => {
return !item.checked && item.mobile
});
if(checkAndVal.length){
if(checkNoVal.length){
console.log('有正确的,但是存在勾选未填的')
}else{
if(this.isRepeat(checkAndVal, 'mobile')){
console.log('可提交,但有重复的')
}else{
console.log('提交')
}
}
}else{
if(checkNoVal.length){
console.log('没有正确的,存在勾选未填的')
}else{
if(noCheckAndVal.length){
console.log('没有正确的,存在只输入值的')
}else{
console.log('啥都没有填')
}
}
}
}这样看上去,逻辑就很清晰了,只通过对三种情况的数组进行处理比较即可。
四、输入框输入监听事件
在最初获取输入框输入内容时,是通过bindblur事件获取的,即输入框失去焦点时获取输入内容进行校验,在开发者工具里是没有问题的,但是在真机上回有问题。
初次输入内容,点击提交是可以收集到输入值的;但是当对输入框内容进行修改后,再次直接点击提交,会发现提交的依旧是修改前的内容,如果提交第二次则会提交最新的数据。也就是说,在真机上不能很准确的获取bindblur这种输入框失焦事件,进而导致数据无法及时更新。
解决办法也很简单,把bindblur事件替换成bindinput即可,即实时获取输入框内容,当然对应的对输入框的校验方法也会发生改变,否则就会出现每输入一个数字就会出现“输入内容错误”的提示。
以上就是自定义表单校验功能开发和优化,通过这个表单功能总结也是告诫自己,开发功能时不能过于心急,应该仔细分析数据和逻辑,寻找最简单直接的方法,避免走弯路。
欢迎小伙伴们指出不足指出~