基于Vue的数字输入框指令

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

需求如何

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

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

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

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

如何使用

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

以下需求为 允许输入负号,允许输入最大值为 99999999,允许输入最小值为 -99999999,小数保留位数 2

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

源码共享

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

vue3 版本

ts 复制代码
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 版本

typescript 复制代码
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
    }
}

最后

欢迎转载、评论、点赞

相关推荐
元直数字电路验证4 小时前
ASP.NET Core Web APP(MVC)开发中无法全局配置 NuGet 包,该怎么解?
前端·javascript·ui·docker·asp.net·.net
西部森林牧歌5 小时前
Arbess零基础学习,使用Arbess+GitLab实现Vue.js项目构建并主机部署
vue.js·gitlab·arbess·tiklab devops
rexling15 小时前
【Spring Boot】Spring Boot解决循环依赖
java·前端·spring boot
我有一棵树5 小时前
Vue 项目中全局样式的正确写法:不要把字体和主题写在 #app 上
前端·javascript·vue.js
Luna-player5 小时前
npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本,解决方法
前端·npm·node.js
Yeniden5 小时前
【设计模式】# 外观模式(Facade)大白话讲解!
java·设计模式·外观模式
悢七5 小时前
windows npm打包无问题,但linux npm打包后部分样式缺失
linux·前端·npm
Yeniden5 小时前
【设计模式】 组合模式(Composite)大白话讲解
java·设计模式·组合模式
Felicity_Gao6 小时前
uni-app App升级功能实现
前端·学习·uni-app