小程序自定义表单校验

2,524 阅读7分钟

表单校验功能在开发中是很常见的,尤其是后台操作系统,当然面向用户的前端页面也会有表单功能,比如:登录、提交电话等场景。

早在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即可,即实时获取输入框内容,当然对应的对输入框的校验方法也会发生改变,否则就会出现每输入一个数字就会出现“输入内容错误”的提示。

以上就是自定义表单校验功能开发和优化,通过这个表单功能总结也是告诫自己,开发功能时不能过于心急,应该仔细分析数据和逻辑,寻找最简单直接的方法,避免走弯路。

欢迎小伙伴们指出不足指出~