效果

使用

注意:使用element el-form el-form-item 包裹组件后,会自动添加范围校验规则。另,由于我不需要其他功能,所以没有处理input的各种事件。

<el-form ref="volatilityFormRef" :disabled="!volatility.checked" label-width="7rem" :model="volatility.data" :rules="volatility.rules" size="medium">
    <el-form-item label="波动范围" prop="range">
        <input-number-range v-model="volatility.data.range" :disabled="!volatility.checked" unit="%"></input-number-range>
    </el-form-item>
</el-form>

组件代码

<template>
  <el-row v-auto-range-validate class="input-number-range" :class="{ 'is-disabled': disabled || formDisabled }" justify="space-between" type="flex">
    <el-input v-model.trim="value[0]" v-number :disabled="disabled" placeholder="最小值" :size="formSize || size" type="text">
      <template v-if="unit" v-slot:append>
        <span>{{ unit }}</span>
      </template>
    </el-input>
    <span :class="{ 'is-disabled': disabled || formDisabled }">{{ separator }}</span>
    <el-input v-model.trim="value[1]" v-number="numberType" :disabled="disabled || formDisabled" placeholder="最大值" :size="formSize || size" type="text">
      <template v-if="unit" v-slot:append>
        <span>{{ unit }}</span>
      </template>
    </el-input>
  </el-row>
</template>

<script>
export default {
  name: "InputNumberRange",
  directives: {
    "auto-range-validate": {
      inserted(el, binding, vnode) {
        const _this = vnode.context
        let formItem = _this
        while (formItem && formItem.$options.name !== "ElFormItem") {
          formItem = formItem.$parent
          if (!formItem) break
        }
        if (!formItem) return
        let form = formItem.$parent
        while (form && form.$options.name !== "ElForm") {
          form = form.$parent
          if (!form) break
        }
        if (!form) return
        // 大小和禁用状态
        _this.formSize = form.size
        _this.formDisabled = form.disabled
        const prop = formItem.prop
        if (!prop) return
        let rules = form.rules
        if (!rules) form.rules = rules = { [prop]: [] }
        if (!rules[prop]) rules[prop] = []
        if (!Array.isArray(rules[prop])) rules[prop] = [rules[prop]]
        rules[prop].push({
          validator: (rule, value, callback) => {
            const msg = _this.validate()
            callback(msg ? new Error(msg) : "")
          },
          trigger: "blur",
        })
      },
    },
  },
  model: {
    prop: "value",
    event: "onChange",
  },
  emits: ["onChange"],
  props: {
    value: {
      type: [Array],
      default: () => [null, null],
    },
    disabled: {
      // 禁用
      type: Boolean,
      default: false,
    },
    size: {
      // 大小
      type: String,
      default: "",
      validator: (val) => ["mini", "small", "medium"].includes(val),
    },
    separator: {
      // 分隔符
      type: String,
      default: "至",
    },
    unit: {
      // 单位
      type: String,
      default: "",
    },
    precision: {
      // 精度
      type: Number,
      default: null,
    },
    numberType: {
      // 数字类型
      type: String,
      default: "decimal",
      validator: (val) => ["integer", "decimal", "positive-integer", "positive-decimal"].includes(val),
    },
  },

  data() {
    return {
      formSize: "",
      formDisabled: false,
    }
  },
  watch: {
    value: {
      handler(val) {
        this.$nextTick(() => {
          if (!Array.isArray(val) || val.length !== 2) {
            // 第一次 修正value值
            this.$emit("onChange", [null, null])
          }
        })
      },
      immediate: true,
    },
  },
  methods: {
    validate() {
      const min = this.value[0]
      const max = this.value[1]
      if (!min && !max) return ""
      if (!min) return "请输入最小值"
      if (!max) return "请输入最大值"
      if (parseFloat(min) > parseFloat(max)) return "最小值不能大于最大值"
      if (this.precision !== 0 && !this.precision) return
      this.$emit("onChange", [parseFloat(min).toFixed(this.precision), parseFloat(max).toFixed(this.precision)])
    },
  },
}
</script>
<style lang="stylus" scoped>
.input-number-range
  background-color #fff
  border 1px solid #dcdfe6
  border-radius 4px

  .is-disabled
    background-color #eef0f6
    border-color #e4e7ed
    color #c0c4cc
    cursor not-allowed
    /deep/.el-input-group__append
      background-color inherit
      color inherit

  // 取消element原有的input框样式
  /deep/.el-input
    .el-input__inner
      border 0
      margin 0
      padding 0 15px
      background-color transparent
      text-align center
  /deep/.el-input-group__append
    background-color #fff
    border unset
</style>

另一个自定义指令

// 注意自行注册
/**
 * @name v-number
 * @description 数值输入框指令,用于限制输入框只能输入特定类型的数值,具体类型由指令参数指定。
 * @param {string} binding.value - 可选值:'integer'(整数), 'decimal'(小数), 'positive-integer'(正整数), 'positive-decimal'(正小数)。
 *    - 'integer': 应用整数验证规则,输入框能输入正整数、负整数和0,非法字符会被自动过滤,仅允许输入数字和负号。
 *    - 'decimal': 应用小数验证规则,输入框可输入包含小数点的有理数(正负皆可),非法字符会被过滤,允许输入数字、负号和小数点。
 *    - 'positive-integer': 应用正整数验证规则,输入框只能输入正整数,非数字字符会被自动过滤,只允许输入数字。
 *    - 'positive-decimal': 应用正小数验证规则,输入框只能输入大于等于0的有理数,非法字符会被过滤,允许输入数字和小数点。
 * @type {{inserted: number.inserted, unbind: number.unbind}}
 */
export const number = {
  inserted: function (el, binding) {
    const inputEl = el.querySelector("input")
    if (inputEl) {
      // 用于存储当前使用的正则表达式
      let currentReg
      // 用于存储当前输入内容的替换(过滤)正则表达式
      let replaceReg
      // 根据初始参数设置正则表达式和替换规则
      switch (binding.value) {
        case "integer":
          currentReg = /^-?(0|[1-9]\d*)$/
          replaceReg = /[^\d-]/g
          break
        case "positive-integer":
          currentReg = /^(0|[1-9]\d*)$/
          replaceReg = /[^\d]/g
          break
        case "positive-decimal":
          currentReg = /^(0|[1-9]\d*)(\.\d*)?$/
          replaceReg = /[^\d.]/g
          break
        default: // decimal
          currentReg = /^-?(0|[1-9]\d*)(\.\d*)?$/
          replaceReg = /[^\d.-]/g
      }

      const inputHandler = function () {
        let value = inputEl.value
        value = value.replace(replaceReg, "")
        if (!currentReg.test(value) && value !== "") {
          inputEl.value = ""
        } else {
          inputEl.value = value
        }
      }

      inputEl.addEventListener("input", inputHandler)

      // 存储解绑函数,以便在元素销毁时调用
      el.__unbindVNumber = function () {
        inputEl.removeEventListener("input", inputHandler)
      }
    }
  },
  unbind: function (el) {
    if (el.__unbindVNumber) {
      el.__unbindVNumber()
      delete el.__unbindVNumber
    }
  },
}

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐