效果
优点
- 避免了 el-form-item 规则提示信息过长换行显示不全问题
- 聚焦直接显示规则状态变化,得以及时提醒用户输入
- 输入过程中,直观感受到所有规则以及校验情况
- 失焦隐藏提示,样式仍存在提示用户
- 直接触发表单校验,只有样式变化,整体画面更简洁
需求
- 聚焦组件显示提示,失焦隐藏(非必填情况聚焦不显示,不满足规则再显示)
- 首次聚焦,基于当前组件值展示校验结果
- 值变化,每个规则都要对只进行校验
- 存在任意一个规则错误,红框展示,全部正确,蓝框展示
解析思路
- 先说个题外话,在满足需求下,封装组件还有几个
技术要求
- 原有页面替换使用方便
- 使用习惯尽量保持不变
- 原有功能不缺失
- UI 大大这需求,明显是针对 el-form-item 组件;先看看如何满足技术要求
- 封装一个组件承接规则校验和提示处理(不满足)
- 在 el-form-item 基础上进行改造,并命名为新组件,替换使用(满足)
- 解析需求
- 采用 el-popover 进行提示,非必填情况通过 disabled 处理
- 首次聚焦主动触发规则校验函数
- 值变化,触发规则校验,直接复用 el-form-item 逻辑
- 状态变化直接复用 el-form-item 逻辑
实现
el-form-item 改造
- 采用继承重写部分功能,达到低成本实现需求
- 通过设置 extends:FormItem、name:form-item-tip,让 form-item-tip 拥有 FormItem 能力
- script 中选项 API,与 FormItem 一致的名称会被覆盖,不一致的会被合并处理
- template 只能被覆盖,无法被合并处理
<template>
<div class="el-form-item">
.....
</div>
</template>
<script>
import { FormItem } from 'element-ui'
export default {
extends: FormItem,
name: 'form-item-tip'
}
</script>
收集提示信息
- 由于聚焦显示所有规则提示,需要收集到 rule 中所有 message,但是自定义规则的 message 是通过 callback 返回的,不执行的情况下无法得知,这里
扩展
了自定义规则的 message 字段 - 为保证能动态变化,自定义规则的 message 字段可设置数组、函数类型
rules:{
FucName: [
{
required: true,
message: '名称不能为空',
trigger: ['change'],
},
{
validator: this.validLen,
trigger: ['change'],
message: ['支持长度为6-10个字符'],
},
{
validator: this.validAll,
trigger: ['change'],
message: () => (['支持中文、字母、数字']),
}
]
}
- 收集message
getErrorInfo() {
// tip 状态
const mesErrorObj = {}
this.tipList.forEach((item) => {
mesErrorObj[item.message] = item.isError
})
// 获取最新规则数组
const curRules = this.getRules()
this.tipList = curRules.reduce((acc, cur) => {
const message = cur.message
if (Array.isArray(message)) {
return this.handlerArray(acc, message, mesErrorObj)
}
if (typeof message === 'function') {
return this.handlerArray(acc, message(), mesErrorObj)
}
return [
...acc,
{
// 优先使用上一次状态
isError: mesErrorObj[message] === undefined ? true : mesErrorObj[message],
message,
},
]
}, [])
},
- 注:一个自定义规则 a 若返回三种提示信息,这种需要拆分为三个自定义规则,因为 a 返回规则1不符合时,规则2、3 是否符合无从得知(
使用时需要额外注意
)
提示模板
- 支持插槽修改提示内容,内置简单展示方式
<template>
<div class="el-form-item">
.....
<div class="el-form-item__content" :style="contentStyle">
<el-popover trigger="focus">
<!-- 支持插槽 -->
<slot name="tip" :tipList="tipList">
<div class="tip-wrap">
<div class="tip-item" v-for="(item, index) in tipList" :key="index">
<i :class="[item.isError ? 'el-icon-error icon-error' : 'el-icon-success icon-success']"></i>
<span>{{ item.message }}</span>
</div>
</div>
</slot>
<slot slot="reference"></slot>
</el-popover>
</div>
</div>
</template>
聚焦触发校验
- 聚焦组件会触发 el-popover 的 show 事件,在事件内进行处理即可
- 收集提示信息
- 触发 el-form-item 内部校验方法
- 更新 Popover 位置,避免出现错位
<el-popover @show="showPopover" ref="popoverRef"></el-popover>
updatePopover() {
this.$nextTick(() => {
this.$refs.popoverRef.updatePopper()
})
},
showPopover() {
this.getErrorInfo()
this.validate('', noop)
this.updatePopover()
},
非必填情况
- 采用 disabled 达到 el-popover 禁用不显示,情况如下
- 未设置规则
- 设置规则 && 规则为非必填 && 组件 v-model 为空
<el-popover :disabled="popoverDisable" ref="popoverRef"></el-popover>
computed: {
popoverDisable() {
return !this.tipList.length || (this.tipList.length && !this.isRequired && this.fieldValueIsEmpty)
},
fieldValueIsEmpty() {
return (
this.fieldValue === '' || this.fieldValue === null || this.fieldValue === undefined || (Array.isArray(this.fieldValue) && this.fieldValue.length === 0)
)
},
},
watch: {
popoverDisable(nVal, oVal) {
if (!nVal && oVal) {
// 从禁用到展示 -- 需要更新位置
this.updatePopover()
} else {
// 从展示到禁用 -- 规则都符合
this.validateState = 'success'
}
},
},
校验以及结果展示
- el-form-item 校验函数名为 validate,需要在其内部加一些代码
- el-popover 禁用情况下,不再进行校验直接通过
- 去除自定义规则情况下的自定义字段 message(否则 async-validator 无法运行得出正确结果)
- 校验结果需要通过 tipAllMessage 函数对提示信息进行处理
- 基于 async-validator 处理结果更新提示信息状态 --- tipAllMessage 函数
- async-validataor 结果(
特殊处理
情况:必填情况下,值为空)- 包含
- 必填规则 message
- 自定义规则执行过程中 callback 中有的 message,
- 不包含
- 不符合的其他配置规则 message
- 包含
- async-validataor 结果(
// 重写
validate(trigger, callback = noop) {
this.validateDisabled = false
const rules = this.getFilteredRule(trigger)
.....
if (this.popoverDisable) {
callback('', null)
return
}
...
if (rules && rules.length > 0) {
rules.forEach((rule) => {
delete rule.trigger
if (rule.validator) {
delete rule.message
}
})
}
....
validator.validate(model, { firstFields: false }, (errors, invalidFields) => {
....
this.tipAllMessage(errors)
})
},
// 更新提示信息状态
tipAllMessage(errors) {
let errorMes = Array.isArray(errors) ? errors.map((item) => item.message) : []
if (this.isRequired && this.fieldValueIsEmpty) {
// 必填情况下,值为空,特殊处理
errorMes = this.tipList
.filter((item) => {
// 其他配置规则 --- 留下
if (!item.isValidator) {
return true
}
// async-validataor 结果中有的 --- 留下
if (errorMes.includes(item.message)) {
return true
}
return false
})
.map((item) => item.message)
}
// errorMes 中存在的没通过,否则通过 --- 更新 tipList 中 isError 字段
},
体验
最后
如果对你开发某些功能有所帮助,麻烦多点赞评论收藏😊
如果对你实现某类业务有所启发,麻烦多点赞评论收藏😊
大家觉得这种交互方式好嘛,欢迎留言交流哦!
- 如果不好,说说你的看法
- 如果好,你有什么好的实现方式嘛