基于Vue的数字输入框指令(v-number)

微信图片_20251101214645_169_447.jpg

需求如何

最近用到了 数字输入框,需求需要满足:

  1. 设置输入的小数位数
  2. 设置是否支持输入负号
  3. 支持最值(最大值、最小值)
  4. 设置边界超出处理 (1、超出是否替换成最值 2、超出最值无法进一步输入)

首先来看下配置属性有哪些:

  • max - 输入数字最大值
  • min - 输入数字最小值
  • digit - 设置小数位数
  • negative 是否支持输入符号
  • isReplace - 超出最值 是采用替换成对应最值

如何使用

我们再来看下我们可以如何使用:

<template>
  <div class="hello">
    <input type="text" v-number="{ negative: true, max: 99999999, min: -99999999 , digit:2 }">
  </div>
</template>

源码共享

我每行代码尽量写了注释,尽量的解释逻辑,下面来看下我们指令(number)的源码,指令名字我取为 number

vue3 版本

type CustomInputElement = HTMLInputElement & {
  old: string | null // 记录的旧值
  digitReg: RegExp | null  // 小数位数,默认 为 2位小数
  formatVal: ((newValue: string, is?: boolean) => string) | null // 格式化值
  inputHandler: (() => void) | null // 输入事件
  blurHander: (() => void) | null // 失去焦点事件
  focusHander: (() => void) | null // 获取焦点事件
}

type Binding = {
    value?: { // 不存在 则使用默认值
        max?: number  //  最大值
        min?: number  //  最小值
        digit?: number  // 小数位数
        negative?: boolean  // 是否支持输入负号
        isReplace?: boolean // 输入值超出最值,是否进行替换成最值
    }
}
export default {
    // 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用
    mounted(el: CustomInputElement, binding: Binding) {
        // input 输入框 元素 兼容
        if (el.tagName !== 'INPUT') {
            const inputEl = el.querySelector('input')
            if (inputEl) {
                el = inputEl as CustomInputElement
            } else {
                // 如果找不到 input 元素,可以选择抛出错误或提前返回
                console.warn('VNumber directive: No input element found')
                return
            }
        }
        // 记录旧值
        el.old = ''
         // 创建小数位数正则并缓存
        el.digitReg = new RegExp( `^-?\d*(\.?\d{0,${binding.value?.digit ?? 2}})`,'g')
        // 其它非法值进行处理 函数
        el.formatVal = function (newValue: string, is) {
            newValue = newValue.replace(/[^d.]/, '')    // 首位 不是数字 或 小数点 就干掉
            newValue = newValue.replace(/^0+(d)/, '$1') // 第一位0开头,0后面为数字,则过滤掉,取后面的数字
            newValue = newValue.replace(/^./, '0.')     // 如果输入的第一位为小数点,则替换成 0. 实现自动补全
            newValue = newValue.match(el.digitReg!)?.[0] || '' // 最终匹配得到结果 以数字开头,只有一个小数点,而且小数点后面只能有0到2位小数
            // 如果是失去焦点
            if (is) newValue = newValue.replace(/(d+).$/, '$1').replace(/^-$/, '')
            return newValue
        }
        // 输入事件
        el.inputHandler = function () {
            // max 最大值, min 最小值, digit 小数位数, negative 是否支持输入负号, isReplace 超出最值是否进行替换成最值
            const { max, min, digit, negative, isReplace } = binding.value || {}
            // 最新的输入框内的值
            let newValue: string | number = el.value
            const val = el.value
            // 最新值是否是负数
            const isNegative = newValue.includes('-')
            // 负号非法值匹配 检查 -数字-
            const invalidVal = newValue.match(/-/g)
            // 是否负号非法值
            const isNegativeInvalid = invalidVal && invalidVal.length === 2 && newValue.replace(/-/g, '').length // -数字-
            // 如果是负号非法值 进行处理 -数字- => 数字
            if (isNegativeInvalid) newValue = newValue.replace(/-/g, '')
            // 其它非法值进行处理
            newValue = el.formatVal!(newValue)
            // 如果不是负号非法值
            if (!isNegativeInvalid) {
                if (val.match(/-/g)?.length === 2) newValue = val.split('-').join('')
                // 如果是负数 并且支持负号,还原值
                if (isNegative && negative) newValue = '-' + newValue
            }
            // 如果是 值 数字. 并且小数位数 是 0(整数)
            if (newValue.slice(-1) === '.' && digit === 0) {
                // 整数功能
                newValue = String(Number(newValue))
            }
            // 输入值超出最值 , isReplace 为 true 就 替换,否则就还原上次输入的值
            if (max !== undefined && Number(newValue) > max) {
                newValue = isReplace ? max : String(newValue).slice(0, -1)
            } else if (min !== undefined && Number(newValue) < min) {
                newValue = isReplace ? min : String(newValue).slice(0, -1)
            }
            // 判断是否需要更新,避免进入死循环
            if (newValue !== el.value) {
                el.value = newValue === '-0' ? '0' : String(newValue)
                el.dispatchEvent(new Event('input')) // 通知v-model更新
            }
        }
        // 失去焦点事件
        el.blurHander = function () {
            // 边界值处理
            if(el.value === '-0') {
                el.value = '0'
                el.dispatchEvent(new Event('input'))
                return
            } 
            if(el.value === '-') {
                el.value = ''
                el.dispatchEvent(new Event('input'))
                return
            }
            if(el.old !== el.value){
                const isNegative = el.value.includes('-')
                el.value = (isNegative ? '-' : '') + el.formatVal!(el.value, true)
                el.dispatchEvent(new Event('input')) // 通知v-model更新
            }
        }
        el.focusHander = function () {
            el.old = el.value 
        }
        el.addEventListener('input', el.inputHandler)
        el.addEventListener('blur', el.blurHander)
        el.addEventListener('focus', el.focusHander)
    },
    // 绑定元素的父组件卸载后调用
    unmounted(el:CustomInputElement) {
        // 移除事件监听器
        el.removeEventListener('input', el.inputHandler!)
        el.removeEventListener('blur', el.blurHander!)
        el.removeEventListener('focus', el.focusHander!)

        // 清理自定义属性
        el.old = null
        el.digitReg = null
        el.formatVal = null
        el.inputHandler = null
        el.blurHander = null
        el.focusHander = null
    }
}

Vue2 版本

ype CustomInputElement = HTMLInputElement & {
  old: string | null // 记录的旧值
  digitReg: RegExp | null  // 小数位数,默认 为 2位小数
  formatVal: ((newValue: string, is?: boolean) => string) | null // 格式化值
  inputHandler: (() => void) | null // 输入事件
  blurHander: (() => void) | null // 失去焦点事件
  focusHander: (() => void) | null // 获取焦点事件
}

type Binding = {
    value?: { // 不存在 则使用默认值
        max?: number  //  最大值
        min?: number  //  最小值
        digit?: number  // 小数位数
        negative?: boolean  // 是否支持输入负号
        isReplace?: boolean // 输入值超出最值,是否进行替换成最值
    }
}
export default {
    // 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用
    bind(el: CustomInputElement, binding: Binding) {
        // input 输入框 元素 兼容
        if (el.tagName !== 'INPUT') {
            const inputEl = el.querySelector('input')
            if (inputEl) {
                el = inputEl as CustomInputElement
            } else {
                // 如果找不到 input 元素,可以选择抛出错误或提前返回
                console.warn('VNumber directive: No input element found')
                return
            }
        }
        // 记录旧值
        el.old = ''
         // 创建小数位数正则并缓存
        el.digitReg = new RegExp( `^-?\d*(\.?\d{0,${binding.value?.digit ?? 2}})`,'g')
        // 其它非法值进行处理 函数
        el.formatVal = function (newValue: string, is) {
            newValue = newValue.replace(/[^d.]/, '')    // 首位 不是数字 或 小数点 就干掉
            newValue = newValue.replace(/^0+(d)/, '$1') // 第一位0开头,0后面为数字,则过滤掉,取后面的数字
            newValue = newValue.replace(/^./, '0.')     // 如果输入的第一位为小数点,则替换成 0. 实现自动补全
            newValue = newValue.match(el.digitReg!)?.[0] || '' // 最终匹配得到结果 以数字开头,只有一个小数点,而且小数点后面只能有0到2位小数
            // 如果是失去焦点
            if (is) newValue = newValue.replace(/(d+).$/, '$1').replace(/^-$/, '')
            return newValue
        }
        // 输入事件
        el.inputHandler = function () {
            // max 最大值, min 最小值, digit 小数位数, negative 是否支持输入负号, isReplace 超出最值是否进行替换成最值
            const { max, min, digit, negative, isReplace } = binding.value || {}
            // 最新的输入框内的值
            let newValue: string | number = el.value
            const val = el.value
            // 最新值是否是负数
            const isNegative = newValue.includes('-')
            // 负号非法值匹配 检查 -数字-
            const invalidVal = newValue.match(/-/g)
            // 是否负号非法值
            const isNegativeInvalid = invalidVal && invalidVal.length === 2 && newValue.replace(/-/g, '').length // -数字-
            // 如果是负号非法值 进行处理 -数字- => 数字
            if (isNegativeInvalid) newValue = newValue.replace(/-/g, '')
            // 其它非法值进行处理
            newValue = el.formatVal!(newValue)
            // 如果不是负号非法值
            if (!isNegativeInvalid) {
                if (val.match(/-/g)?.length === 2) newValue = val.split('-').join('')
                // 如果是负数 并且支持负号,还原值
                if (isNegative && negative) newValue = '-' + newValue
            }
            // 如果是 值 数字. 并且小数位数 是 0(整数)
            if (newValue.slice(-1) === '.' && digit === 0) {
                // 整数功能
                newValue = String(Number(newValue))
            }
            // 输入值超出最值 , isReplace 为 true 就 替换,否则就还原上次输入的值
            if (max !== undefined && Number(newValue) > max) {
                newValue = isReplace ? max : String(newValue).slice(0, -1)
            } else if (min !== undefined && Number(newValue) < min) {
                newValue = isReplace ? min : String(newValue).slice(0, -1)
            }
            // 判断是否需要更新,避免进入死循环
            if (newValue !== el.value) {
                el.value = newValue === '-0' ? '0' : String(newValue)
                el.dispatchEvent(new Event('input')) // 通知v-model更新
            }
        }
        // 失去焦点事件
        el.blurHander = function () {
            // 边界值处理
            if(el.value === '-0') {
                el.value = '0'
                el.dispatchEvent(new Event('input'))
                return
            } 
            if(el.value === '-') {
                el.value = ''
                el.dispatchEvent(new Event('input'))
                return
            }
            if(el.old !== el.value){
                const isNegative = el.value.includes('-')
                el.value = (isNegative ? '-' : '') + el.formatVal!(el.value, true)
                el.dispatchEvent(new Event('input')) // 通知v-model更新
            }
        }
        el.focusHander = function () {
            el.old = el.value 
        }
        el.addEventListener('input', el.inputHandler)
        el.addEventListener('blur', el.blurHander)
        el.addEventListener('focus', el.focusHander)
    },
    // 绑定元素的父组件卸载后调用
    unbind (el:CustomInputElement) {
        // 移除事件监听器
        el.removeEventListener('input', el.inputHandler!)
        el.removeEventListener('blur', el.blurHander!)
        el.removeEventListener('focus', el.focusHander!)

        // 清理自定义属性
        el.old = null
        el.digitReg = null
        el.formatVal = null
        el.inputHandler = null
        el.blurHander = null
        el.focusHander = null
    }
}

最后

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]