likes
comments
collection
share

UI大大:element表单交互不太行,按照我设计的实现一下

作者站长头像
站长
· 阅读数 12

效果

UI大大:element表单交互不太行,按照我设计的实现一下

优点

  1. 避免了 el-form-item 规则提示信息过长换行显示不全问题
  2. 聚焦直接显示规则状态变化,得以及时提醒用户输入
  3. 输入过程中,直观感受到所有规则以及校验情况
  4. 失焦隐藏提示,样式仍存在提示用户
  5. 直接触发表单校验,只有样式变化,整体画面更简洁

需求

  1. 聚焦组件显示提示,失焦隐藏(非必填情况聚焦不显示,不满足规则再显示)
  2. 首次聚焦,基于当前组件值展示校验结果
  3. 值变化,每个规则都要对只进行校验
  4. 存在任意一个规则错误,红框展示,全部正确,蓝框展示

解析思路

  • 先说个题外话,在满足需求下,封装组件还有几个技术要求
    • 原有页面替换使用方便
    • 使用习惯尽量保持不变
    • 原有功能不缺失
  • 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

UI大大:element表单交互不太行,按照我设计的实现一下

// 重写
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 字段
},

体验

最后

如果对你开发某些功能有所帮助,麻烦多点赞评论收藏😊

如果对你实现某类业务有所启发,麻烦多点赞评论收藏😊

大家觉得这种交互方式好嘛,欢迎留言交流哦!

  • 如果不好,说说你的看法
  • 如果好,你有什么好的实现方式嘛

UI大大:element表单交互不太行,按照我设计的实现一下