二次封装`el-form`新增校验错误自动滚动到错误表单项位置

475 阅读2分钟

二次封装el-form新增校验错误自动滚动到错误表单项位置

通过二次封装的方式新增了errorScroll属性,开启后,当表单校验失败时,会自动滚动到第一个错误表单项位置。 重写el-formvalidate方法,添加校验失败是滚动逻辑,用法与el-form一致。 resetFieldsclearValidatevalidateField方法也通过重写调用原来方法保持与el-form一致。 el-form$attrs$listeners会透传到el-form,所以不需要再在my-el-form中定义$attrs$listeners,直接使用el-form$attrs$listeners即可。

my-el-form.vue

<template>
  <el-form
    ref="originalForm"
    v-bind="$attrs"
    v-on="$listeners"
    class="my-el-form"
  >
    <slot />
  </el-form>
</template>

<script>
import { isHidden, isInViewport } from './utils'

export default {
  name: "MyElForm",
  props: {
    // 是否开启表单校验错误时滚动到错误项
    errorScroll: {
      type: Boolean,
      default: null
    }
  },
  methods: {
    errorScrollToField(field) {
      const el = field.$el
      // 判断是否在可视区,不在可视区滚动到可视区
      if (!isInViewport(el)) {
        el.scrollIntoView({
          behavior: 'smooth',
          block: 'center',
          inline: 'center'
        })
      }
    },
    handleErrorScroll(errors) {
      const fields = this.$refs.originalForm.fields
      for (let i = 0; i < fields.length; i++) {
        const field = fields[i]
        // 找到第一个错误非隐藏元素的表单项
        if (errors[field.prop] && !isHidden(field.$el)) {
          // 执行滚动逻辑
          this.errorScrollToField(field)
          break
        }
      }
    },
    // 重写 resetFields
    resetFields() {
      this.$refs['originalForm'].resetFields()
    },
    // 重写 clearValidate
    clearValidate(props = []) {
      this.$refs['originalForm'].clearValidate(props)
    },
    // 重写 validate
    validate(callback) {
      let promise
      // 判断是否有回调,如果没有,则返回promise。保持用法一致
      if (typeof callback !== 'function' && window.Promise) {
        promise = new window.Promise((resolve, reject) => {
          callback = function(valid, invalidFields) {
            if (valid) {
              resolve(valid)
            } else {
              reject(invalidFields)
            }
          }
        })
      }
      this.$refs['originalForm'].validate((valid, invalidFields) => {
        callback(valid, invalidFields)
        if (!valid && this.errorScroll) {
          // 滚动到第一个错误表单
          this.handleErrorScroll(invalidFields)
        }
      })
      return promise
    },
    // 重写 validateField
    validateField(props, cb) {
      this.$refs['originalForm'].validateField(props, cb)
    }
  }
}
</script>

utils

/**
 * 判断元素是否在可视区域
 * @param el
 * @returns {boolean}
 */
export function isInViewport(el) {
  const rect = el.getBoundingClientRect();
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  );
}

/**
 * 判断元素是否隐藏
 * @param el
 * @returns {boolean}
 */
export function isHidden(el){
  return el.offsetParent === null;
}

关于scrollIntoView的使用

scrollIntoView 是一个可以在浏览器中使元素滚动到视口内的方法。它属于 Element 接口,因此任何 DOM 元素都可以调用这个方法。以下是 scrollIntoView 的基本用法:

基本

element.scrollIntoView();

参数

  • behavior: 滚动动画的行为,可以是 autosmooth
  • block: 指定垂直方向上的滚动行为,可以是 start, center, end, 或 nearest
  • inline: 指定水平方向上的滚动行为,与 block 类似。
element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });

请注意,scrollIntoView 方法在不同的浏览器中可能有不同的支持情况,尤其是对于参数的支持。 现代浏览器(如 Chrome, Firefox, Safari)通常支持带有参数的版本,但在一些旧版或低版本的浏览器中,可能只能使用无参数的版本。 如果你在使用 scrollIntoView 时遇到兼容性问题,可以考虑使用 JavaScript 库或 polyfill 来增强其跨浏览器的兼容性。