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

需求如何
最近用到了 数字输入框,需求需要满足:
- 设置输入的小数位数
- 设置是否支持输入负号
- 支持最值(最大值、最小值)
- 设置边界超出处理 (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
}
}
最后
欢迎转载、评论、点赞