C V大法:Vue2 基于el-input,实现只能输入数字(整数、小数)功能

2,765 阅读1分钟

CV.jpg

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

C V 系列特点:

  1. 只关注功能的实现。让各位看官复制粘贴即可安心使用
  2. 代码注释会写详细,尽可能做到一行一注释,把细节都说清楚
  3. 涉及第三方库等等不做赘述,用到的库会给各位看官放上链接,方便各位深入了解

功能演示

input.gif

话不多说 上代码

使用时

<!-- 整数 + 事件 -->
 <MjInput
    v-model="source.num"
    :min="1"
    :max="999999"
    placeholder="请填写"
    @blur="source.num=checkMm(source.num, 1, 999999)"
/>
<!-- 浮点 + 插槽 + 事件 -->
<MjInput
    v-model="price"
    class="money"
    type="float"
    clearable
    @blur="priceJudge"
    @keyup.enter.native="queryData(1,'search')"
>
    <i slot="prefix" class="unit"></i>
</MjInput>

封装组件

父组件

<template>
    <!-- $attrs $listeners 保证可以用el-input的写法使用封装好的组件 -->
    <component :is="show_component" v-model="curVal" v-bind="$attrs" v-on="$listeners">
        <!-- 没有这个不能使用el-input的插槽 -->
        <template v-for="(index, name) in $slots" v-slot:[name]>
            <slot :name="name"></slot>
        </template>
    </component>
</template>

<script>
import Int from './int' // int 整数类型
import Float from './float' // float 浮点类型

// 组件常量 不放在data里 没必要被vue劫持
const COMPONENTENUM = {
    int: 'Int',
    float: 'Float'
}

export default {
    name: 'MjInput',
    components: { Int, Float },
    props: {
        value: { // 父组件 v-model 传入的值
            type: [String, Number],
            default: ''
        },
        type: { // 要渲染的组件,注意这里直接劫持了传入的type属性 并且不会在往子组件传递 默认为int整数
            type: String,
            default: 'int'
        }
    },
    data() {
        return {
            curVal: '' // 当前的数据
        }
    },
    computed: {
        show_component() {
            return COMPONENTENUM[this.type]
        }
    },
    watch: {
        value: {
            handler(val) {
                // 因为''会被判断为false所以不能直接使用 !val,
                // 因为是传入的值,子组件要保持单向数据流,不能直接修改值,所以深拷贝一下赋给data中的变量
                (val !== null && val !== undefined) && (this.curVal = JSON.parse(JSON.stringify(val)))
            },
            // 立刻的,即时的;确认是否以当前的初始值执行handler的函数
            immediate: true
        },
        // 监听当前值的变化 提交给父组件
        curVal(val) {
            // v-model语法糖传入的value  默认emit触发的是'input'
            this.$emit('input', val)
        }
    }
}
</script>

子组件 ==> 浮点类

<template>
    <!-- 小数点组件 -->
    <!-- $attrs $listeners 保证可以用el-input的写法使用封装好的组件 -->
    <el-input v-model.trim="curVal" v-bind="$attrs" @input="inputEvent" v-on="$listeners">
        <!-- 没有这个不能使用el-input的插槽 -->
        <template v-for="(index, name) in $slots" v-slot:[name]>
            <slot :name="name"></slot>
        </template>
    </el-input>
</template>

<script>

export default {
    name: 'FloatInput',
    props: {
        value: { // 父组件 v-model 传入的值
            type: [String, Number],
            default: ''
        }
    },
    data() {
        return {
            curVal: '' // 当前的数据
        }
    },
    watch: {
         value: {
            handler(val) {
                // 因为''会被判断为false所以不能直接使用 !val,
                // 因为是传入的值,子组件要保持单向数据流,不能直接修改值,所以深拷贝一下赋给data中的变量
                (val !== null && val !== undefined) && (this.curVal = JSON.parse(JSON.stringify(val)))
            },
            // 立刻的,即时的;确认是否以当前的初始值执行handler的函数
            immediate: true
        },
        // 监听当前值的变化 提交给父组件
        curVal(val) {
            // v-model语法糖传入的value  默认emit触发的是'input'
            this.$emit('input', val)
        }
    },
    methods: {
        inputEvent(value) {
            if (!value) return
            // 解决macos 输入。在特定情况下识别为.
            value = value.replace(/。/g, '.');
            
            // 先把非数字的都替换掉,除了数字和.
            value = value.replace(/[^\d.]/g, '');
            
            // 保证只有出现一个.而没有多个.
            value = value.replace(/\.{2,}/g, '.');
            
            // 必须保证第一个为数字而不是.
            value = value.replace(/^\./g, '');
            
            // 保证.只出现一次,而不能出现两次以上
            value = value.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.');
            
            // 只能输入两个小数
            value = value.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3');
            
            // 开头只能输入一个0
            value = value.replace(/^0+\d+?/g, '0');

            this.$nextTick(() => {
                if (Number.isNaN(value)) value = ''
                this.curVal = value
            })
        }
    }
}
</script>

子组件 ==> 整数类

<template>
    <!-- $attrs $listeners 保证可以用el-input的写法使用封装好的组件 -->
    <el-input v-model.number.trim="curVal" v-bind="$attrs" v-on="$listeners" @input="inputEvent" >
        <!-- 没有这个不能使用el-input的插槽 -->
        <template v-for="(index, name) in $slots" :slot="name">
            <slot :name="name"></slot>
        </template>
    </el-input>
</template>

<script>

export default {
    name: 'IntInput',
    props: {
        value: { // 父组件 v-model 传入的值
            type: [String, Number],
            default: ''
        },
        isStartZero: { // 开头是否可以输入多个零
            type: Boolean,
            default: false
        }
    },
    data() {
        return {
            curVal: '' // 当前的数据
        }
    },
    watch: {
          value: {
            handler(val) {
                // 因为''会被判断为false所以不能直接使用 !val,
                // 因为是传入的值,子组件要保持单向数据流,不能直接修改值,所以深拷贝一下赋给data中的变量
                (val !== null && val !== undefined) && (this.curVal = JSON.parse(JSON.stringify(val)))
            },
            // 立刻的,即时的;确认是否以当前的初始值执行handler的函数
            immediate: true
        },
        // 监听当前值的变化 提交给父组件
        curVal(val) {
            // v-model语法糖传入的value  默认emit触发的是'input'
            this.$emit('input', val)
        }
    },
    methods: {
        inputEvent(value) {
            if (!value) return
            
            // 非数字的替换为空
            value = value.replace(/[^\d]/g, '');
            // 是否传入了开头可以出现的0的标识
            !this.isStartZero && (value = value.replace(/^0+\d+?/g, '0'))
            
            this.$nextTick(() => {
                this.curVal = value
            })
        }
    }
}
</script>

总结

  1. <component></component>包裹一下,统一使用时的组件名字,统一用法
  2. $attrs $listeners保证可以用el-input的写法使用封装好的组件
  3. watch中监听value,为了保证数据实时可变。并在查看详情时可以更好的回显所以也加上了immediate: true
  4. inputEvent函数中使用 this.$nextTick因为要确保在当前的dom变化完在赋值

欢迎查看更多C V系列 C V 大法