前端 如何实现一个基于vue2.0的数字输入框指令

422 阅读4分钟

1.jpg

前言

前端项目中,输入框是常见的,数字输入框更是常见,我们也许用惯了UI框架或是第三方提供的数字输入框,其实我们内心也想拥有自己的一个数字输入框指令,进可以(灵活使用),退可以守(灵活扩展),一切尽在掌握之中,不尽于被动。

需求

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

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

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

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

使用

我们再来看下我们想如何使用:

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

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

源码及示例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue2 数字输入框指令</title>
</head>
<body>
    <div id="app">
        <div class="item">
            <div class="desc">
                默认状态
                <ul>
                    <li>输入的小数位数digit:2</li>
                    <li>输入数字最大值max:无限制</li>
                    <li>输入数字最小值min:无限制</li>
                    <li>是否允许输入负号negative:不允许</li>
                    <li>超出最值 是否允许采用替换成对应最值isReplace:不允许</li>
                </ul>
            </div>
            <div class="input-wrap"><input type="text" v-number></div>
        </div>
        <div class="item">
            <div class="desc">
                自定义状态1
                <ul>
                    <li>输入的小数位数digit:0</li>
                    <li>输入数字最大值max:999</li>
                    <li>输入数字最小值min:-999</li>
                    <li>是否允许输入负号negative:允许</li>
                    <li>超出最值 是否允许采用替换成对应最值isReplace:允许</li>
                </ul>
            </div>
            <div class="input-wrap"><input type="text" v-number="{digit:0,min:-999,max:999,negative:true,isReplace:true}"></div>
        </div>
        <div class="item">
            <div class="desc">
                自定义状态2
                <ul>
                    <li>输入的小数位数digit:0</li>
                    <li>输入数字最大值max:999</li>
                    <li>输入数字最小值min:0</li>
                    <li>是否允许输入负号negative:不允许</li>
                    <li>超出最值 是否允许采用替换成对应最值isReplace:不允许</li>
                </ul>
            </div>
            <div class="input-wrap"><input type="text" v-number="{digit:0,min:-999,max:999,negative:false,isReplace:false}"></div>
        </div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    /**
     * 
        数字输入框,需求满足:

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

        配置属性:

        max - 输入数字最大值
        min - 输入数字最小值
        digit - 设置小数位数
        negative 是否支持输入符号
        isReplace - 超出最值 是采用替换成对应最值
     * 
     * 
     */
    new Vue({
        el:'#app',
        data:{
        },
        directives:{
            number: {
                bind (el, binding) {
                     // input 输入框 元素 兼容
                    if (el.tagName !== 'INPUT') el = el.querySelector('input')
                    el.old = '' // 记录旧值
                    el.handler = function () {
                        if(el.lock)return
                        const { max, digit, min, negative, isReplace } = binding.value || {}
                         // 小数位数正则
                        const digitReg = new RegExp(`^\\d*(\\.?\\d{0,${digit === undefined ? 2 : digit}})`, 'g')
                        // 最新值是否是负数
                        const isNegative  = el.value.includes('-')
                        // 最新的输入框内的值
                        const val = el.value
                         // 其它非法值进行处理
                        let newValue = el.value.replace(/[^\d.]/, '')
                            .replace(/,/g, '')
                            .replace(/^0+(\d)/, '$1') // 第一位0开头,0后面为数字,则过滤掉,取后面的数字
                            .replace(/^\./, '0.') // 如果输入的第一位为小数点,则替换成 0. 实现自动补全
                            .match(digitReg)[0] || '' // 最终匹配得到结果 以数字开头,只有一个小数点,而且小数点后面只能有0到2位小数
                        // 负数 并且 允许输入符号
                        if (isNegative && negative) {
                            if (val.match(/-/g)?.length === 2) newValue = val.split('-').join('')
                            newValue = '-' + newValue
                        }

                        if (newValue.slice(-1) === '.' && digit === 0) {
                            newValue = Number(newValue)
                        }
                        // 输入值超出最值 , isReplace 为 true 就 替换,否则就还原上次输入的值
                        if (max !== undefined && newValue > max) {
                            newValue = isReplace ? max : String(newValue).slice(0, -1)
                        } else if (min !== undefined && newValue < min) {
                            newValue = isReplace ? min : String(newValue).slice(0, -1)
                        } else { // 输入值未超出最值
                            el.old = newValue
                        }
                        // 判断是否需要更新,避免进入死循环\
                        if (newValue !== el.value) {
                            el.value = newValue
                            el.dispatchEvent(new Event('input')) // 通知v-model更新
                        }
                    }
                    el.blurHander = function (e) {
                        const { digit } = binding.value || {}
                        const digitReg = new RegExp(`^\\d*(\\.?\\d{0,${digit === undefined ? 2 : digit}})`, 'g')
                        if (el.value === '-') {
                            el.value = ''
                            el.dispatchEvent(new Event('input')) // 通知v-model更新
                            return
                        }
                        let newValue = el.value.replace(/[^\d.]/, '')
                            .replace(/^0+(\d)/, '$1') // 第一位0开头,0后面为数字,则过滤掉,取后面的数字
                            .replace(/^\./, '0.') // 如果输入的第一位为小数点,则替换成 0. 实现自动补全
                            .match(digitReg)[0] || '' // 最终匹配得到结果 以数字开头,只有一个小数点,而且小数点后面只能有0到2位小数
                        if (newValue.slice(-1) === '.' && digit !== 0 && digit !== undefined) {
                            newValue = Number(newValue)
                            el.value = newValue
                            el.dispatchEvent(new Event('input')) // 通知v-model更新
                        }
                    }
                    el.compositionstart = function(e){
                        el.lock = true
                        e.preventDefault();
                        e.stopPropagation();
                    }
                    el.compositionend = function(e){
                        el.lock = false
                        el.value = parseFloat(el.value)
                        el.dispatchEvent(new Event('input'))
                        e.preventDefault();
                        e.stopPropagation();
                    }
                    el.addEventListener('input', el.handler)
                    el.addEventListener('blur', el.blurHander)
                    el.addEventListener('compositionstart', el.compositionstart)
                    el.addEventListener('compositionend', el.compositionend)
                },
                unbind (el) {
                    el.removeEventListener('input', el.handler)
                    el.removeEventListener('blur', el.blurHander)
                    el.removeEventListener('compositionstart', el.compositionstart)
                    el.removeEventListener('compositionend', el.compositionend)
                }
            }
        }
    })
</script>
</html>

指令的大致实现过程就监听输入,根据输入值进行各种场景的处理,compositionstartcompositionend来处理 输入中文的特殊场景,经过自测和项目中的使用,基本满足需求,如果不满足你的需求,可以复制下来进行扩展,实现思路 可以用于React、Vue3等其他,万变不离其宗。

结语

我们项目中出现该场景的情况还是比较多的,希望对大家有用,如有疑问,欢迎留言,有更好的 idea,欢迎一起讨论,感谢

24年往期文章:
前端 同时多次调用同个接口,如何只触发一次而不是触发多次
前端 页面不同组件内调用相同接口导致重复调用,如何只触发一次而不是触发多次
前端 保存前繁琐的静态校验,如何化繁为简