艺灵设计

全部文章
×

Vue项目优化之用Element-UI表单组件实现动态校验,提升用户体验

作者:艺灵设计 - 来源:http://www.yilingsj.com - 发布时间:2022-08-29 17:55:56 - 阅: - 评:0 - 积分:0

摘要:上周在优化小程序中的优惠券功能,然后我在后台添加优惠券时发现页面用户体验极不友好。前端缺少必填提醒和相关校验,有强迫症的我又花费了一些时间对页面做了相关优化。我抛砖引玉分享下自己的思路和解决方案,如果看官有更好的思路或想法,还请不吝赐教。

一、起因

上周我在做小程序中的优化优惠券需求,优惠券是通过后台添加的。后台添加优惠券的页面长这个样子↓↓↓后台添加优惠券界面

确定必填项只有2个???

事实证明,之前的开发同事,确实是漏掉了一些必填标识前端校验

1.1、暴露出问题

我按照正常流程随意填写信息。

当填写到优惠券折扣这里,我故意填写为一个比较大的数字99999999。其他选项的地方也随意填写一些值,最后点击提交。

loading出现片刻后,弹窗显示“提交成功”字样。接着自动跳转到列表页并成功显示了刚才提交的信息。如下图↓↓↓成功添加优惠券后在列表中有展示

添加优惠券的流程就这样结束了,但问题很多,于是就有了这篇文章。

顺便提一嘴,在刚才的提交过程中,之所以随意填写信息,目的有三个。

  1. 看后端有没有校验(说不定负数也能通过哦~)
  2. 为了更好的在小程序端做布局兼容(比如:超长文本)
  3. 优化这个页面,补全遗漏的必要校验。

经过上面的提交,暴露出很多问题。有些是比较容易处理的,有些则复杂些。比如存在多条件选择的地方。

但问题不大,咱们一个个来解决。

二、优化前提:先找出必填项

我在蓝湖上找到了该页面对应的设计稿,发现跟当前页面有些区别。一是少了几个必填项*标识,二是有些字段有调整。蓝湖设计稿如下图↓↓↓蓝湖设计稿中有必填标识

2.1、前端界面竟然跟设计稿不一样???

不知道看官会不会有一个疑问:为啥前端界面跟设计稿不一样?

从前面的对比图中确实能暴露出这个问题,而产生这个问题的原因也可能是多样的。比如说:业务需求发生了多次调整、部门间有修改时只是口头传达、我看到的蓝湖设计稿版本不是最新等等。

原因众多,这就有点儿尴尬了。

但不管怎么改,必填字段在后端接口中肯定是有的!

顺着这条线索,我进行了简单的自测。

经自测验证,发现前端确实在“领取时间”、“使用门槛”、“有效期”这三个地方忽略了校验。至此,我们已经找出了缺少的必填项。

三、开始优化

一共是三处校验,还是有些坑在里面的。

三处优化,我采用三种不同的方法来实现。艺灵我先抛砖引玉一下,看官也可以想想自己曾经是否也遇到过这样问题,当时又是怎样解决的。

3.1、第一处:校验领取时间字段

先找到<template>中的相关代码位置,具体见下方。

<el-form-item label="券描述">
  <el-input v-model="counponDetail.note" placeholder="请输入" class="int"></el-input>
</el-form-item>
<el-form-item label="领取时间" prop="getTime">
  <el-date-picker
    v-model="getTime"
    type="datetimerange"
    class="int"
    format="yyyy-MM-dd HH:mm:ss"
    value-format="yyyy-MM-dd HH:mm:ss"
    range-separator="至"
    start-placeholder="开始日期"
    end-placeholder="结束日期"
    @change="changeReceiveTime"
  >
  </el-date-picker>
</el-form-item>

再查看data(){}中的counponDetailrules

export default {
  data() {
    return {
      counponDetail: {
        rule: 1, // 使用对象
        goods_type: 1, // 适用范围
        use_type: 1, // 有效期
        limit_num: 0,
        num: 0,
      }, // 优惠券
      rules: {
        name: [{ required: true, message: '请输入优惠券名称', trigger: 'blur' }],
        time: [{ required: true, message: '请输入领取时间', trigger: 'blur' }],
        value: [{ required: true, message: '请输入折扣', trigger: 'blur' }],
      }, // 验证规则
    }
  }
}

这里问题已经出现了。

  1. 问题一:虽然template中添加了prop="getTime"属性,但counponDetail中不存在getTime字段。
  2. 问题二:rules规则中也没有出现getTime,自然无法校验。
  3. 问题三:rules中校验了time,看提示是校验领取时间,莫非就是前面的校验时间?但前后没有任何关联,故校验失效。

3.1.1、优化之后

查看了提交接口时需要传递的字段,我做了如下修改。

<!-- 优化思路:
      1、修改prop="getTime"为prop="get_start"
      2、counponDetail中添加2个必填字段;
      3、rules中配置对应的规则
      4、监听changeReceiveTime事件,并给counponDetail中新增的2个字段赋值 -->
<el-form-item label="领取时间" prop="get_start">
  ...... // 无修改
</el-form-item>

<script>
  export default {
    data() {
      return {
        ...... // 此处省略一些代码
        counponDetail: {
          ...... // 此处为原参数

          /* 新增2个参数↓↓↓ */
          get_start: '', // 领取开始时间
          get_end: '', // 领取结束时间
          /* 新增2个参数↑↑↑ */
        }, // 优惠券
        rules: {
          ...... // 此处为原参数

          /* 新增1个校验规则↓↓↓ */
          get_start: [{ required: true, message: '请选择领取时间', trigger: ['blur', 'change'] }],
          /* 新增1个校验规则↑↑↑ */
        }, // 验证规则
      }
    },
    methods: {
      /**
        * @author: mouwan (634326084@qq.com)
        * @description: 更改领取时间
        * @param {Array,Null} 选择领取时间
        * @return {*}
        * @Date: 2021-04-15 15:14:06
        */
      changeReceiveTime(e) {
        console.log('选择了领取时间', e) // 注意看打印的值
        const newArr = e || ['', ''] // 清空后的值为null,所以要默认给个数组,防止报错
        this.counponDetail.get_start = newArr[0]
        this.counponDetail.get_end = newArr[1]
      },
    }
  }
</script>

思路如下:

  1. 把原来的prop="getTime"修改为prop="get_start",为第三步校验做准备
  2. counponDetail中新增2个字段,分别是:get_start(领取开始时间)、get_end(领取结束时间)
  3. rules中配置get_start规则,与第一步形成一个闭环
  4. 监听选择日期事件changeReceiveTime,有值时其值是一个长度为2的数组。第一个值是开始时间,第二个值是结束时间。把每次修改的值分别赋值给counponDetail.get_startcounponDetail.get_end

现在,我们就可以愉快的进行校验了。

3.1.2、小坑:清空日期范围的值后,返回的结果是null

注意这里有一个潜在的坑,每个新入职的同事都会掉坑中,真是一坑一个准!

什么坑呢?就是选择日期范围后的结果是一个Array数组类型,这个大家都知道。但点击清空按钮后,程序就报错了。

那为什么会报错呢?

因为现在的结果是一个null!一个null!是null

重要的事情说三遍,如果看官还是掉坑中,那我也没办法了~

3.1.2.1、复现分析

有值时数组长度为2,第一个值是起始时间,用[0]可取到值;第二个值是结束时间,用[1]可取其值。

但由于点击清空按钮后,返回的值是一个null

此时null不是数组,所以用数组的索引写法null[0]来取值,自然就 报错了。

动态gif演示图如下↓↓↓ 清空日期范围后返回的是null

3.2、第二处:校验使用门槛

UI设计稿如下图↓↓↓ 使用门槛选项

功能分析:这是一个二选一的功能,用户可以选择“订单金额满xx元可用”,或者是“无门槛”。

当选择“订单金额xx元可用”时,需要对输入的内容做校验,包含是否为空、只能输入数字、最小值和最大值的校验。

当选择“无门槛”时,则不需要做校验。

吐槽一下,前同事在这里竟然未做任何校验,可以输入任意内容!真是省事啊!!!动态gif如下图↓↓↓ 使用门槛处竟然可以输入任意内容

经多部门商议后,此处和优惠券折扣一样,最大值为999999.99

再吐个槽,上面的优惠券折扣那里也是可以输入任意内容。[:尴尬]

吐槽归吐槽,优化归优化,继续走起。

3.2.1、优化前的代码

先来看下优化前的代码。

<el-form-item label="使用门槛" prop="threshold">
  <el-radio-group v-model="thresholdType">
  <el-radio :label="1">
    订单金额
    <el-input class="setInput" v-model="counponDetail.threshold" placeholder="请输入"></el-input>元可用
  </el-radio>
  <el-radio v-model="radioThreshold" :label="2">无门槛</el-radio>
  </el-radio-group>
</el-form-item>

看遍整个.vue文件也没有发现有对counponDetail.threshold的值进行校验的地方。所以上面那个gif动图中可以输入汉字和字符串也就不足为怪了。

3.2.2、优化后的代码

<!-- 优化思路:
  1、将el-input组件换为el-input-number,从根源上限制了值只能为数字
  2、将radioThreshold改为thresholdType
  3、counponDetail中新增一个对应的参数
  4、rules规则中添加校验
  5、定义最大值,方便多处调用 -->
<el-form-item label="使用门槛" prop="threshold">
  <el-radio-group v-model="thresholdType">
  <el-radio :label="1">
    订单金额
    <el-input-number class="setInput" :controls="false" :min="0" :max="max" v-model="counponDetail.threshold" placeholder="请输入">
    </el-input-number>元可用
  </el-radio>
  <el-radio v-model="thresholdType" :label="2">无门槛</el-radio>
  </el-radio-group>
</el-form-item>

<script>
  export default {
    data() {
      return {
        ...... // 此处省略一些代码
        counponDetail: {
          ...... // 此处为原参数

          /* 新增1个参数↓↓↓ */
          threshold: undefined, // 使用门槛,注意初始值是undefined,避免界面中显示0
          /* 新增1个参数↑↑↑ */
        }, // 优惠券
        rules: {
          ...... // 此处为原参数

          /* 新增1个校验规则↓↓↓ */
          threshold: [{ required: true, message: '订单金额不能为空', trigger: ['blur', 'change'] }],
          /* 新增1个校验规则↑↑↑ */
        }, // 验证规则
        max: 999999.99, // 最大值(此处为新增)
      }
    }
  }
</script>

思路如下:

  1. <el-input>组件换为InputNumber 计数器组件,从根源上限制了值只能为数字
  2. radioThreshold改为thresholdType,这里没必要多定义一个变量
  3. counponDetail中新增1个字段,即:thresholdType(使用门槛)、注意其初始值是undefined,因为只有这样才能避免界面中显示0
  4. rules中配置threshold规则
  5. 定义最大值max,方便多处调用

经过上述几个步骤的优化,我们就成功的解决了问题。

如果看官真的这样做了,还是会有问题的。因为当用户选择无门槛选项时,“订单金额不能为空”的校验生效了!如下图↓↓↓选择无门槛选项后订单满xx元的校验仍然生效

那怎么办呢?

3.2.2.1、Watch上场

答案是:watch监听

watch: {
  // 监听门槛类型 (1:满xx元; 2:无门槛)
  thresholdType(val) {
    console.log('监听门槛类型=', val)
    // 选择无门槛时,移除验证↓↓↓
    this.rules.threshold[0].required = val === 1
  },
}

现在,我们可以随意切换“无门槛”和“订单金额x元可用”这两个选项了。动态gif图如下↓↓↓ 动态校验使用门槛字段

上图表达的意思为:当选中“无门槛”时,不需要做校验,左侧的红*自动取消了。当切换到“订单金额x元可用”时,左侧红*出现,而且“订单金额不能为空”的提示字样也会出现。

至此,我们完成了第二处的优化与校验。

3.3、第三处:校验有效期

UI设计稿如下图↓↓↓ 有效期也是一个二选一的功能

功能分析:这里又是一个二选一的功能,用户可以选择“固定时间”或“领取后N天有效”。

当选择“固定时间”时,是一个日期范围的选择,这个在前面3.1小节有提及过,等会儿可以照搬。

当选择“领取后N天有效”时,是一个整数类型的校验,刚好3.2小节说过。

也就是说,前面两处的优化完全是为了第三处的优化做准备。看官读到这里时,是不是感觉难度一下子降低了很多呀。

3.3.1、优化前的代码

先来看下优化前的代码。

<el-form-item label="有效期" prop="discountAmount">
  <el-radio-group v-model="counponDetail.use_type" class="flex column">
  <el-radio :label="1" style="line-height: 40px; max-width: 380px">
    固定时间
    <el-date-picker
      v-model="userTime"
      type="datetimerange"
      class="int"
      format="yyyy-MM-dd HH:mm:ss"
      value-format="yyyy-MM-dd HH:mm:ss"
      range-separator="至"
      start-placeholder="开始日期"
      end-placeholder="结束日期"
    >
    </el-date-picker>
  </el-radio>
  <el-radio :label="2" style="line-height: 40px; max-width: 380px">
    领取后N天内有效 领取后
    <el-input class="setInput m-t-6" v-model="counponDetail.use_limit" placeholder="请输入有效期"></el-input>
    天内有效
  </el-radio>
  </el-radio-group>
</el-form-item>

问题分析

  1. el-form-item添加了prop="discountAmount",但rules规则中并未设置相关校验,所以这里并未体现校验的用途。
  2. 日期范围这里使用了新的变量userTime,同样未做校验,也没有做清空值的兼容处理,存在报错的情况。
  3. 有效期counponDetail.use_limit未做任何处理,可输入任意内容。

一波小分析完毕,下面我将采用一种比较骚的操作来解决这个问题,而这个骚操作同样适用于动态校验或更复杂的场景。

这个骚操作是什么呢?它就是嵌套表单

3.3.2、优化后的代码

<!-- 优化思路:
      1、外层el-form-item添加required,实现左侧红*号
      2、内层用2个el-form-item分别把选项包裹起来,并添加各自的ref,此举可做到单独校验的目的。这是重点!!!
      3、changeUserTime事件可处理日期范围为空的情况
      4、el-input组件换为el-input-number可限制天数为整数 -->
<el-form-item label="有效期" required>
  <el-radio-group v-model="counponDetail.use_type" class="flex column">
  <el-form-item class="children__el-form-item" prop="use_start"  ref="form_use_start">
    <el-radio :label="1" style="line-height: 40px; max-width: 380px">
      固定时间
      <el-date-picker
        v-model="userTime"
        type="datetimerange"
        class="int"
        format="yyyy-MM-dd HH:mm:ss"
        value-format="yyyy-MM-dd HH:mm:ss"
        range-separator="至"
        start-placeholder="开始日期"
        end-placeholder="结束日期"
        @change="changeUserTime"
      >
      </el-date-picker>
    </el-radio>
  </el-form-item>
  <el-form-item class="children__el-form-item" prop="use_limit" ref="form_use_limit">
    <el-radio :label="2" style="line-height: 40px; max-width: 380px">
      领取后N天内有效 领取后
      <el-input-number
        class="setInput m-t-6"
        v-model="counponDetail.use_limit"
        :controls="false"
        :min="0"
        :max="365"
        placeholder="请输入有效期"
      ></el-input-number>
      天内有效
    </el-radio>
  </el-form-item>
  </el-radio-group>
</el-form-item>

<script>
  export default {
    data() {
      /* 此处代码为新增,是在data(){}中,位置没有放错 ↓↓↓ */
      // 校验有效天数
      const validatorInt = (rule, value, callback) => {
        if (!value) {
          return callback('请输入有效天数')
        }
        if (!regRules.int.test(value)) {
          return callback('只能输入正整数!')
        }
        callback()
      }
      /* 此处代码为新增 ↑↑↑ */

      return {
        ...... // 此处省略一些代码
        counponDetail: {
          ...... // 此处为原参数

          /* 新增3个参数↓↓↓ */
          use_start: '', // 有效期 开始时间
          use_end: '', // 有效期 结束时间
          use_limit: undefined, // 领取后xxx天内有效,注意初始值是undefined,避免界面中显示0
          /* 新增3个参数↑↑↑ */
        }, // 优惠券
        rules: {
          ...... // 此处为原参数

          /* 新增2个校验规则↓↓↓ */
          use_start: [{ required: true, message: '请选择有效期时间', trigger: ['blur', 'change'] }],
          use_limit: [
            { required: true, message: '请设置有效天数', trigger: ['blur', 'change'] },
            { validator: validatorInt, trigger: ['blur', 'change'] },
          ],
          /* 新增1个校验规则↑↑↑ */
        }, // 验证规则
      }
    }
  }
</script>

思路如下

  1. 外层el-form-item添加required,实现左侧红*号
  2. 内层用2个el-form-item分别把选项包裹起来,并添加各自的ref,此举做到单独校验的目的。这是重点!!!
  3. changeUserTime事件可处理日期范围为空的情况
  4. el-input组件换为el-input-number可限制天数为整数
  5. data(){}中新增一个validatorInt方法,供下面的rules使用
  6. data(){}中的counponDetail变量新增了3个参数
  7. rules中新增了2个对应的规则校验

现在我们再来点击提交按钮,可以看到有效期的校验已经生效了。如下图↓↓↓ 有效期的校验生效了

怎么样?是不是很简单?

3.3.3、新问题:明明没有勾选“领取后N天有效”,为什么也做校验了???

等下!眼尖的看官可能已经发现新的问题了--- 我没有勾选“领取后N天有效”,为什么也做校验了???

确实是没有勾,但也确实校验了。

之所以会校验是因为我们在relus中定义了use_limit的规则。

这。。。。。。好尴尬啊!

不要慌,即将进入高能部分--动态校验,各位看官看好了。

3.3.4、动态校验完美解决问题

<!-- 下方是<script>中的部分代码↓↓↓ -->
data() {
  // 校验有效天数
  const validatorInt = (rule, value, callback) => {
    /* 此处为新增↓↓↓ */
    // 当用户选择固定时间时,直接点提交按钮,放行有效期的校验
    if (this.counponDetail.use_type === 1) {
      return callback()
    }
    /* 此处为新增↑↑↑ */

    if (!value) {
      return callback('请输入有效天数')
    }
    if (!regRules.int.test(value)) {
      return callback('只能输入正整数!')
    }
    callback()
  }
},
watch: {
  // 监听有效期 (1:固定时间; 2:领取后n天有效)
  'counponDetail.use_type': {
    handler(val) {
      // 动态设置校验规格
      this.rules.use_start[0].required = val === 1
      this.rules.use_limit[0].required = !(val === 1)
      /* 动态清空校验↓↓↓ */
      if (val === 1) {
        // 选择了固定时间
        this.$refs.couponForm?.validateField('use_start')
        this.$refs.form_use_limit?.clearValidate() // 移动“领取后N天”的校验
      } else if (val === 2) {
        // 选择了领取后N天
        this.$refs.couponForm?.validateField('use_limit')
        this.$refs.form_use_start?.clearValidate() // 移除“固定时间”的校验
      }
      /* 动态清空校验↑↑↑ */
    },
  },
},

好了,现在再啥都不填写时直接点击“提交”按钮,已经不会再校验“领取后N天有效”了。来回切换“固定时间”和“领取后N天内有效”的选项,也能自动进行相关校验了。动态gif图如下 使用watch监听来实现动态校验有效期

至此,我们便解决了这文章开头提到的三个优化问题。

四、本文小结

关于表单校验,一般设置prop + rules即可实现前端校验。

如果是复杂一点儿的,像上面的使用门槛有效期,均是有多个选项供选择。此时我们可以根据实际情况决定是否需要使用嵌套表单来实现更自由的校验。

关键时刻,watch监听也会派上用场!

以上便是艺灵我对本文的总结,如果看官有更好的思路或想法,还请不吝赐教,谢谢。

转载声明:
  若亲想转载本文到其它平台,请务必保留本文出处!
本文链接:/xwzj/2022-08-29/vue-element-ui-form-dynamic-validate.html

若亲不想直保留地址,含蓄保留也行。艺灵不想再看到有人拿我的技术文章到他的地盘或者是其它平台做教(装)程(B)而不留下我的痕迹。文章你可以随便转载,随便修改,但请尊重艺灵的劳动成果!谢谢理解。

亲,扫个码支持一下艺灵呗~
如果您觉得本文的内容对您有所帮助,您可以用支付宝打赏下艺灵哦!

Tag: Vue 前端 项目优化 Element-UI 表单组件 动态校验 用户体验 rules validateField clearValidate

上一篇: 每月免费领6GB流量,拿好不谢   下一篇: 微信小程序踩坑系列之扫普通链接二维码打开小程序

评论区