上一篇我们已经完善了主要的验证功能~包括单独验证、不通触发方式、全部交验,但是我只用了输入框组件,但是
- 表单组件当中除了输入框,还有多行输入textarea(这个和input.vue基本一样,这边不再次写了)。单选(radio)、多选(CheckBox)、下拉选择(select)这些个常用标签,其中还是有一些细微差别,我们还要拿出来简单说一下;
- 此外我们上篇的文章中只是使用的基本的必填验证和 async-validator自带的验证规则。本章节会完善自定义验证规则~
废话不多说,开始我们的代码~~
自定义校验
我们在src下新建一个目录utils,放工具JS:
其中
- assist.js 是第一篇文章说道的工具函数 findComponentUpward(向上查找最近的最近的指定组件) 、findComponentsDownward(向下查找所有指定的组件 )。
- constant.js放的是一些常量,使用全大写定义,可以更好管理常量,并且在书写的时候更容易通过编译器只能识别错写的问题。
- validateRules.js 就是表单验证rules的JS文件,里面包括了一些内部处理函数 和方法。向外暴露(export) 对应的form表单的rule规则
让我们来详细看一下validateRules.js :
import * as constant from './constant'
/**
* 去除前后空格
* @param {*} val 字符串
*/
function trim (val) {
return val.replace(/(^\s*)|(\s*$)/g, '')
}
/**
* 字符串长度范围验证
* @param {*} min 最少位数 默认0
* @param {*} max 最多位数 默认10000
*/
function lengthLimit (min = 0, max = 1000000) {
if (arguments.length === 0) {
return
}
const isNumber = [...arguments].every(item => typeof item === 'number')
if (!isNumber) {
throw new Error('lengthLimit函数参数必须为Number')
}
const message = arguments.length === 1 ? `长度必须大于${min}` : `长度必须大于${min}、小于${max}`
return {
type: 'string',
message: message,
validator: (rule, val) => {
const value = trim(val)
return value.length >= min && value.length <= max
},
trigger: constant.BLUR
}
}
// 以下是自定义验证规则
// true/false判断 适合 单选 单多选 select等~
const checked = (rule, value) => value
const selected = (rule, value) => value !== ''
const isFullName = (rule, val) => {
const value = trim(val)
return /\w/.test(value)
}
const hobby = (rule, val) => val.length >= 2
const sexValidate = (rule, val) => val.length > 0
export const loginRules = {
name: [
{
required: true,
message: '用户名不能为空',
trigger: constant.BLUR
},
{
type: 'string',
message: '用户名只能是数字、字母或下划线',
validator: isFullName,
trigger: constant.BLUR
},
lengthLimit(4, 18)
],
checkedArr: [
{
type: 'array',
message: '至少勾选2项',
required: true,
validator: hobby,
trigger: constant.CHANGE
}
],
professional: [
{
type: 'string',
message: '请选择专业',
required: true,
validator: selected,
trigger: constant.CHANGE
}
],
sex: [
{
type: 'number',
message: '请选择性别',
required: true,
validator: sexValidate,
trigger: constant.CHANGE
}
],
own: [
{
type: 'string',
message: '请勾选用户条款',
required: true,
validator: checked,
trigger: constant.CHANGE
}
],
email: [
{
required: true,
message: '邮箱不能为空',
trigger: constant.BLUR
},
{
type: 'email',
message: '邮箱格式不正确',
trigger: constant.BLUR
}
]
}
我们将一些可能会重复使用到的验证规则 写成js文件内部的方法,哪里需要哪里使用;一般项目中会有很多地方都需要使用表单+验证,比如登录、注册、修改/忘记密码等~,将验证规则统一写在一个JS文件中统一管理,按需引用。这样之前的index.vue文件中就可以改成
// 部分代码省略
<script>
import Emitter from '@/mixins/emitter'
import { loginRules } from '@/utils/validateRules'
export default {
mixins: [Emitter],
name: 'index',
data () {
return {
validateRules: loginRules
}
}
接下来我们来完善其他几个表单元素组件的编写~
checkbox 与 checkbox-group
多选框使用场景很多,单个出现使用一般出现在同意协议这种情况~ 成组出现一般在 多条件筛选、兴趣爱好选择等情况。所以我们编写CheckBox组件的时候得将这种情况考虑进去。
首先我们在base文件夹建一个checkbox文件夹,里面建两个文件:checkbox.vue 和 checkbox-group.vue。
我们先看checkbox.vue:
<template>
<label>
<span>
<input
v-if="group"
type="checkbox"
:value="label"
:disabled="disabled"
v-model="model"
@change="handleChange"
>
<input
v-else
type="checkbox"
:disabled="disabled"
:checked="currentValue"
@change="handleChange"
>
</span>
<slot></slot>
</label>
</template>
<script>
import Emitter from '@/mixins/emitter'
import { findComponentUpward } from '@/utils/assist'
export default {
mixins: [Emitter],
name: 'w-checkbox',
props: {
disabled: {
type: Boolean,
default: false
},
value: {
type: [String, Number, Boolean],
default: false
},
label: {
type: [String, Number, Boolean]
},
trueValue: {
type: [String, Number, Boolean],
default: true
},
falseValue: {
type: [String, Number, Boolean],
default: false
}
},
data () {
return {
currentValue: this.value,
model: [],
group: false,
parent: null
}
},
watch: {
value (val) {
if (val === this.trueValue || val === this.falseValue) {
this.updateModel()
} else {
throw new Error('Value should be trueValue or falseValue')
}
}
},
mounted () {
this.parent = findComponentUpward(this, 'w-checkbox-group')
if (this.parent) {
this.group = true
}
if (this.group) {
this.parent.updateModel(true)
} else {
this.updateModel()
}
},
methods: {
handleChange (e) {
if (this.disabled) {
return
}
const checked = e.target.checked
this.currentValue = checked
const value = checked ? this.trueValue : this.falseValue
this.$emit('input', value)
if (this.group) {
this.parent.change(this.model)
} else {
this.$emit('on-change', value)
this.dispatch('form-item', 'on-form-change', value)
}
},
updateModel () {
this.currentValue = this.value === this.trueValue
}
}
}
</script>
chekbox.vue中我们相当于写了两套代码,适配于是否有 checkbox-group.vue父级,如果有代表CheckBox是成组出现的,使用我们之前说过的方法 findComponentUpward。
重点说几点:
- props中的trueValue和falseValue是增强扩展性的,一般数据库中存入的是/否 判断 不会直接是 true/false。可能是 0和1 或者其他字符串,所以用户可以通过这两个属性定义;不用每次在代码里面转换完之后再传入。
- 如果是group模式,通过data中定义的model 和 props中的label ,然后通过
this.parent.change(this.model)
向上级更新,实现双向数据绑定- 如果是在form表单中单独使用checkbox.vue ,使用自定义方法 dispatch 将change的响应暴露给form-item。(form-item添加了对change事件的监听与回调,请直接查看GitHub上面的源码)
接下来我们看checkbox-group.vue文件:
<template>
<div class="checkbox-group">
<slot></slot>
</div>
</template>
<script>
import Emitter from '@/mixins/emitter'
import { findComponentsDownward } from '@/utils/assist'
export default {
mixins: [Emitter],
name: 'w-checkbox-group',
props: {
value: {
type: Array,
default: () => []
}
},
data () {
return {
currentValue: this.value,
childrens: []
}
},
watch: {
value (val) {
this.updateModel(true)
}
},
mounted () {
this.updateModel(true)
},
methods: {
updateModel (update) {
this.childrens = findComponentsDownward(this, 'w-checkbox')
if (this.childrens.length > 0) {
const { value } = this
this.childrens.forEach(child => {
child.model = value
})
}
},
change (data) {
this.currentValue = data
this.$emit('input', data)
this.$emit('on-change', data)
this.dispatch('form-item', 'on-form-change', data)
}
}
}
</script>
- 通过findComponentsDownward方法找到所有的checkbox元素,然后将外部组件上的v-model的属性(checkbox-group中props中的value)赋值给每一个 checkbox.vue中的data的model。(关于多个checkbox在Vue中双向数据绑定的问题可以直接查看官方文档 复选框)
- 使用自定义方法 dispatch 将change的响应暴露给form-item。
radio单选框
单选框在表单中经常出现,例如性别或者 是否是xxx的选择。我们在base下 新建 radio/radio.vue。
<template>
<label>
<input
type="radio"
:name="name"
v-model="model"
:value="currentValue"
:disabled="disabled"
@change="handleChange"
>
<slot></slot>
</label>
</template>
<script>
import Emitter from '@/mixins/emitter'
export default {
mixins: [Emitter],
name: 'w-radio',
props: {
name: {
type: String
},
value: {
type: [String, Number]
},
label: {
type: [String, Number]
},
disabled: {
type: Boolean,
default: false
}
},
data () {
return {
currentValue: this.label,
model: this.value
}
},
methods: {
handleChange (e) {
const val = e.target.value
this.model = val
this.$emit('input', val)
this.dispatch('form-item', 'on-form-change', val)
}
}
}
</script>
单选组件相对来说就简单很多了(偷了个懒o(╯□╰)o,为了增强扩展性,可以和checkbox一样增加trueValue和falseValue两个属性);需要用到多个radio就在引用组件的时候外面套个循环用的标签~
select组件
下拉框也是使用频率非常高的标签;我们在base下 新建 select/select.vue。
<template>
<select v-model="currentValue" @change="handleChange">
<option value>全部</option>
<slot></slot>
</select>
</template>
<script>
import Emitter from '@/mixins/emitter'
export default {
mixins: [Emitter],
name: 'WSelect',
props: {
value: {
type: String
}
},
data () {
return {
currentValue: this.value
}
},
methods: {
handleChange (e) {
const val = e.target.value
this.currentValue = val
this.$emit('input', val)
this.dispatch('form-item', 'on-form-change', val)
}
}
}
</script>
这个组件主要就有一个select标签,内部默认有一个空value的option,默认选择“全部”(根据需要可有可无).
其他正式选项都通过<slot>标签插入。
————————————————————————————————————————————————
总结
简单来说,我们的form表单组件到这里就已经很完整了,效果就是如第一篇文章刚开始那个动图一样。
大家如果有想法,可以去我的GitHub上面查看源码。
有什么问题可以随时给我留言,欢迎一起讨论,共同进步。
其实代码还有很多的不足,我也只是个龟速学习的菜鸟,希望路过的大神可以多几句提点,感激不尽。