【Vue3.0实战逐步深入系列】为问卷主题添加必填项配置及数据有效性校验功能(千字长文,熬夜更新)

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

【千字长文,熬夜更新,原创不易,多多支持,感谢大家】

前言

小伙伴们大家好。今天将带着大家继续来完善我们的问卷调查功能。到目前为止我们的问卷调查功能已经实现了问卷主题配置、问卷结果提交,问卷结果保存及回填,问卷结果展示等基本功能。看上去貌似一个问卷调查功能已经实现了,然而还有一个比较重要的功能没有实现,那就是数据有效性校验功能,想想如果没有数据有效性的校验,打开一个问卷什么都不填就直接提交,那就失去了问卷调查的意义了。因此本文将在前面分享的基础上增加数据有效性校验功能。

需求描述

要添加数据有效性校验,我们需要实现如下几条需求(功能点)

  • 每个问卷主题是否是必填项可由问卷发布者自行配置
  • 如果问卷主题是必填项,则需在问卷标题前添加一个红色星号(*)提示
  • 当用户点击提交按钮时进行数据有效性校验,如果有未填写的问卷主题,则应在相应问卷标题下方给出红色字体的信息提示(数据无效)同时代码停止继续向下执行
  • 当用户将无效数据补充完整后,红色提示信息自动消失
  • 页面在第一次加载的时候所有的数据都是空的,这时候不应该显示红色提示信息,只有点击了提交按钮后才应该提示

需求已经基本明确,下面就按照这些需求一步步来实现数据有效性的校验

配置数据源

上面需求已经明确要求问卷主题是否必填由问卷发布者自行定义,而问卷发布着是不可能去修改代码的,因此我们就得从数据源着手实现。大体思路就是在数据源中为每个问卷主题添加以个required字段用来标识该主题是否是必填项。进而通过该字段来控制页面是否显示红色星号和红色提示信息。修改后的数据源代码如下:

// public/data.json
// 新增required字段 类型为boolean类型
{
    "data":[
        {"id":1,"title":"Vue3.0好学吗?", "required": true}
    ],
    "radios":[
        {"id":1,"title":"您对公司的福利是否满意?","items":["非常满意","满意","基本满意","不满意"]},{"id":2,"title":"您对当前的收入是否满意?","items":["非常满意","满意","基本满意","不满意"], "required": true}
    ],
    "checks":[
        {"id":1,"title":"您还希望公司增加哪些福利?","items":["涨工资","涨饭补","发女朋友","以上都要"], "required": true}
    ],
    "inputs":[
        {"id":1,"title":"您有什么想对公司说的?", "required": true},
        {"id":2,"title":"随便说点啥吧", "required": true}
    ],
    "stars":[
        {"id":1,"title":"请给本次分享打个分吧!","items":["糟糕","失望","一般","很好","非常好"], "required": true}
    ]
}

复制代码

封装header.vue组件

按照上面的第二条需求需要在每个必填的问卷主题前添加一个红色的星号(*)提示必填,因此我们需要修改每个问卷主题组件,在其标题前面添加红色星号提示,同时需要在其下方添加红色字体的文字信息提示。由此分析下来发现每个组件中要修改的代码都是一样的并且是一项重复的工作,假如以后也有类似修改标题的行为,我们还是得修改每个组件做着重复的工作。索性我们就将问卷的标题部分提出来单独封装成一个子组件:

  • 新建一个header.vue组件,将原来组件中的标题部分代码复制过来(< h3> {{ title }}</ h3>)
  • 然后在h3标签中title的前面添加一个span标签,span中包裹着一个红色星号(*),同时为该span标签添加一个v-show指令值为required用于控制是否显示红色星号
  • 在h3的下方添加一个div标签高度15px(目的是在红色文字显示与隐藏时页面不会跳来跳去),同时在该div标签中再添加一个子div用于展示红色文字提示信息,该子div也需添加一个v-show指令用于控制提示信息的展示和隐藏
  • 为该组件定义3个props属性:
    • title:用于展示问卷的标题
    • required:用于控制该文件主题是否是必填项
    • validator:验证器,用于校验当前问卷结果是否是有效数据(默认为true有效),与required属性共同作用来控制红色文字信息是否展示
  • 最后为了页面效果,可以再为红色文字提示信息添加一个闪动的动画效果

完整代码如下:

<template>
	<h3><span v-show="required" class="required">*</span>{{ title }}</h3>
	<div style="height:15px">
		<div v-show="required && !validator" class="validator">此项为必填项,别偷懒哦,请选点啥或写点啥吧!</div>
	</div>
</template>
复制代码
export default{
	props:{
		title:{
			type:String,
			required:true
		},
		required:{
			type:Boolean,
			default:false
		},
		validator:{
			type:Boolean,
			default:true
		}
	}
}
复制代码
.required{
	color:red;
}
.validator{
	font-size:12px;
	color:red;
	animation: valid 2s infinite;
}
@keyframes valid{
	0%{
		opacity: 0;
	}
	25%{
		opacity: 0.5;
	}
	50%{
		opacity: 1;
	}
	75%{
		opacity: 0.5;
	}
	100%{
		opacity: 0;
	}
}
复制代码

修改问卷主题组件

在将标题部分封装成组件后,我们需要修改每个问卷主题组件:

  • 模板部分:将原来的h3标签替换成我们封装好的header组件。需要格外注意的是:在用import导入header组件的时候不要命名为header,因为这会与html5自带的header标签冲突。
  • JavaScript代码部分:添加两个props属性required和validator对应着header.vue中的两个props

以vote.vue组件为例展示修改后的代码,其它组件相同:

<!--...省略-->
<template #header>
	<myheader :required="required" :validator="validator" :title="title" />
</template>
<!--...省略-->
复制代码
//...省略
props:{
	//...省略	
	required:{
		type:Boolean,
		default:false
	},
	validator:{
		type:Boolean,
		default:true
	}
}
//...省略
复制代码

修改Home.vue组件添加数据有效性校验逻辑

在这个组件中实现数据有效性校验功能略显复杂,纠结了很久不知该从何下手,最终经过不断的尝试终于一点一点的找到了思路并实现了数据有效性校验功能,下面来整理一下最初的思路和遇到的问题

  • 由于所有的问卷主题都是通过循环生成的,因此我们并不能通过直接操作dom的方式来获取到具体的某个主题,进而也就不能判断哪个主题填了哪个没填。但也不是就没有办法了,经过仔细分析发现:每个主题在循环生成时都会有一个唯一的key,那么我们就可以借助这个唯一的key将每个主题都保存到一个临时变量validateResult(对象)中,key就作为对象的属性,属性值统一设置成false(默认验证不通过)。放眼望去只有recoverResult这个计算属性是每次都会随着循环调用的,那么我们就将这个对象的初始化操作放在这个计算属性返回的函数中。
  • 当用户开始填写问卷时就会触发每个组件的select事件,因此我们就可以在该事件绑定的方法getValue中根据所选结果来更新validateResult中对应的问卷主题的值(即如果数据有效则将对应的值更新为true表示校验通过)
  • 在提交按钮事件中(submit方法)遍历validateResult,如果所有的属性值都是true则说明验证通过直接提交,否则只要有一个值为false则应该立即停止代码运行阻止提交。
  • 修改模板内容,为每个问卷主题组件添加requiredvalidator属性,用于控制是否必填及是否显示红色文字提示信息

Home.vue组件修改后的代码如下:(注意,以下展示的还不是最终的代码(有bug),所以先不要急着敲,后面还有彩蛋哦)

<template>
<!--...省略-->
<!--仅展示修改部分代码,其它原有代码省略-->
<vote :required="item.required" :validator="validateResult[`vote${item.id}`]" />
<myradio :required="radio.required" :validator="validateResult[`radio${radio.id}`]" />
<mycheck :required="check.required" :validator="validateResult[`check${check.id}`]" />
<myinput :required="inp.required" :validator="validateResult[`input${inp.id}`]" />
<mystar :required="star.required" :validator="validateResult[`star${star.id}`]" />
<!--...省略-->
</template>
复制代码
//仅展示修改部分代码,其它省略
const submit = ()=>{
	for (const key in validateResult){
		if(!validateResult[key]){
			return;
		}
	}
}
//...省略
const getValue = (val, title, flag) =>{
	if(flag.indexOf("vote") >= 0){
		//...省略
		validateResult[flag] = val.supValue > 0 || val.oppValue > 0;
	}else{
		validateResult[flag] = !!(""+val).trim()
	}
}

const recoverResult = (title, flag) => {
	return function(title, flag){
		validateResult[flag] = false;
	}
}

return {
	validateResult
}
复制代码

经过一番折腾终于有了雏形,迫不及待的运行起来看效果发现:诶

  • 标题前面的红色星号有了
  • 结果为空的问卷主题下面的红色文字提示信息也有了,并且还一闪一闪的
  • 点击提交按钮时代码,提交功能也被阻止了

看上去验证功能貌似实现了,该有的功能也都有了。但是,但是,但是,在回归到最初的需求逐条测试下来发现还有这么几个小问题:

  • 当把问卷结果补充完整后,红色文字提示信息仍然存在,并没有自动消失
    • 原因:我们最初定义的validateResult对象并不是一个响应式的对象
    • 解决方案:将validateResult定义成响应式变量即可
  • 当第一次打开问卷页面或页面重新加载并且没有点击提交按钮时,所有的必填项的红色文字提示信息都展示出来了(首次或重新加载但没点提交按钮的情况下,红色文字是不应该展示的,只有点了提交才会提示)
    • 原因: 是因为在页面每次重新加载时都会调用recoverResult计算属性返回的函数,而在该函数中每次都会将所有主题的结果值更新为false,即校验不通过
    • 解决方案: 定义一个响应式变量init初始值为true,表示当前页面是第一次打开或重新加载并且没有点击过提交按钮。当点击提交按钮后将该值更新为false。然后用initvalidateResult两个属性共同控制提示信息的展示与否(即当init为true时,即使validateResult值为false也不显示红色提示信息)
  • 当某个非必填的问卷主题结果为空时(其它必填项校验通过),点击提交按钮仍然无法提交
    • 原因: 这是因为我们在校验数据有效性时,并没有根据该主题是否是必填项来校验,而是一棒子打死统统都进行有效性校验(非必填项是不需要校验的)
    • 解决方案: 分别在getValuerecovcerResult两个方法中添加一个required参数,然后根据该参数来进行数据有效性的校验,即当required值为false时默认让该主题对应的结果值为true表示校验通过。

最终完善后的代码如下:

<template>
<!--...省略-->
<!--仅展示修改部分代码,其它原有代码省略-->
<!--添加init验证,如果是页面初始加载则不用显示红色提示信息-->
<vote :required="item.required" :value="recoveryResult(item.title, `vote${vote.id}`, vote.required) || {
        supValue: 0,
        oppValue: 0,
      }" :validator="validateResult[`vote${item.id}`] || init" @select="getValue($event,item.title,`vote${item.id}`,item.required)" />
<myradio :required="radio.required" :value="recoveryResult(radio.title,`radio${radio.id}`,radio.required) || ''" :validator="validateResult[`radio${radio.id}`] || init" @select="getValue($event,radio.title,`radio${radio.id}`,radio.required)" />
<mycheck :required="check.required" :value="recoveryResult(check.title,`check${check.id}`,check.required) || []" :validator="validateResult[`check${check.id}`] || init" @select="getValue($event,check.title,`check${check.id}`,check.required)" />
<myinput :required="inp.required" :value="recoveryResult(inp.title,`input${inp.id}`,inp.required) || ''" :validator="validateResult[`input${inp.id}`] || init" @select="getValue($event,inp.title,`input${inp.id}`,inp.required)" />
<mystar :required="star.required" :value="recoveryResult(star.title,`star${star.id}`,star.required) || 0" :validator="validateResult[`star${star.id}`] || init" @select="getValue($event,star.title,`star${star.id}`,star.required)" />
<!--...省略-->
</template>
复制代码
//仅展示修改部分代码,其它省略
const state = reactive({
	validateResult:{},
	init:true
});
const submit = ()=>{
	state.init = false;//点击提交按钮将该值置为false
	for (const key in state.validateResult){
		if(!state.validateResult[key]){
			return;
		}
	}
}
//...省略
const getValue = (val, title, flag, required) =>{
	if(flag.indexOf("vote") >= 0){
		//...省略
		state.validateResult[flag] = required ? val.supValue > 0 || val.oppValue > 0 : true;
	}else{
		//如果是必填则根据所选结果进行校验,否则直接更新为true表示校验通过
		state.validateResult[flag] =  required ? !!(""+val).trim(): true;
	}
}

const recoverResult = (title, flag, required) => {
	return function(title, flag){
		state.validateResult[flag] = !required;
	}
}
复制代码

效果展示

到此一个完善的数据有效性校验功能就实现了,最终效果如下:

在这里插入图片描述

总结

本次分享实现了问卷结果数据有效性校验功能,看似简单的数据校验,实际上实现起来还是略显复杂,进过一番折腾和不断尝试花了将近一天的时间才将该功能完善。

本次分享就到这里了,喜欢的小伙伴欢迎点赞评论加关注哦!

猜你喜欢

转载自juejin.im/post/7034892667137818632