美文网首页
Input输入限制及格式化(PC&Mobile)

Input输入限制及格式化(PC&Mobile)

作者: 细只 | 来源:发表于2019-06-14 10:12 被阅读0次
    • 场景
    1. 限制用户输入,如纯数字、纯金额等。
    2. 格式化用户输入,如身份证、银行卡、手机号等。
      示例地址
    • 现象
    1. input事件触发时,键入内容未显示在页面上,此时重新更替value值达到限制输入。
    • 实现 - 原生JS / Vue页面 / Vue组件 / Vue指令
    1. 原生JS演示( 数字输入框 )
    <body>
        <fieldset>
            <legend>原生JS演示</legend>
            <label for="js-input">数字输入框</label>
            <input id="js-input" type="tel">
        </fieldset>
        <script>
            let value = ''
            let regExp = new RegExp(`^(0|[1-9]\\d*)$`)
            document.getElementById('js-input').addEventListener('input', e => {
                 if (e.target.value && !(regExp.test(e.target.value))) {
                    e.target.value = value
                 } else {
                    value = e.target.value
                }
            })
        </script>
    </body>
    
    1. Vue页面演示 ( 数字输入框 )
    <body>
        <div id="app">
            <fieldset>
                <legend>Vue演示</legend>
                <label for="number">数字输入框</label>
                <input id="number" v-model="number" type="tel" @input="_input">
            </fieldset>
        </div>
        <script>
            new Vue({
                el: '#app',
                data () {
                    return {
                        number: '',
                        value: '',
                    }
                },
                beforeCreate () {
                    this.regExp = {
                        number: new RegExp(`^(0|[1-9]\\d*)$`)
                    }
                },
                methods: {
                    _input (e) {
                        if (e.target.value && !(this.regExp.number.test(e.target.value))) {
                            this.number = this.value
                        } else {
                            this.value = this.number
                        }
                    }
                }
            })
        </script>
    </body>
    
    1. Vue组件演示 ( 数字输入框 )
    <template>
        <input :value="value" type="tel" @input="_input" :placeholder="placeholder" :maxlength="maxlength">
    </template>
    <script>
    export default {
        name: 'input-number',
        props: {
            value: {
                type: String,
                default: ''
            },
            placeholder: {
                type: String,
                default: '请输入'
            },
            maxlength: {
                type: [String, Number],
                default: '9'
            }
        },
        data () {
            return {
                val: this.value
            }
        },
        created () {
            this.regExp = new RegExp(`^(0|[1-9]\\d*)$`)
        },
        methods: {
            _input (e) {
                if (e.target.value && !this.regExp.test(e.target.value)) {
                    e.target.value = this.val
                } else {
                    this.val = e.target.value
                }
                this.$emit('input', this.val)
            }
        }
    }
    
    1. Vue指令演示
    const directives = {
        number: {
            inserted: (el, binding, vnode) => {
                let input = el.tagName.toUpperCase() === 'INPUT' ? el : null
                if (!input) {
                    for (let i of el.children) {
                        if (i.tagName.toUpperCase() === 'INPUT') input = i
                    }
                }
                if (input) {
                    let regExp = new RegExp(`^(0|[1-9]\\d*)$`)
                    let value = ''
                    const handleInput = e => {
                        if (e.target.value && !(regExp.test(e.target.value))) {
                            e.target.value = value
                        } else {
                            value = e.target.value
                        }
                    }
                    on(input, 'input', handleInput)
                    on(input, 'change', handleInput)
                }
            }
        }
    }
    

    因为v-model本质是input事件并且先于自定义指令触发,导致最后一个输入的非数字字符无法更新到v-model绑定的字段,故采用修饰符lazy转变为使用 change 事件进行同步。

    <input type="tel" v-model.lazy="dnumber" v-number></input>
    
    • 延伸 - 金额输入 / 格式化输入
    1. 金额输入 ( input[type=number] )
    <template>
        <input :value="value" type="number" @input="_input" :placeholder="placeholder">
    </template>
    
    <script>
    export default {
        name: 'input-money',
        props: {
            value: {
                type: String,
                default: ''
            },
            placeholder: {
                type: String,
                default: '请输入'
            },
            maxlength: {
                type: [String, Number],
                default: 9
            },
            point: {
                type: [String, Number],
                default: 2
            }
        },
        data () {
            return {
                val: this.value
            }
        },
        created () {
            this.regExp = new RegExp(`^(0|[1-9]\\d{0,${this.maxlength - 1}})(\\.\\d{0,${this.point}})?$`)
        },
        methods: {
            _input (e) {
                if (!e.target.value && this.val.length === 1) {
                    this.val = ''
                } else {
                    if ((e.target.value && !(this.regExp.test(e.target.value))) || isNaN(e.target.valueAsNumber)) {
                        e.target.value = this.val
                    } else {
                        this.val = e.target.value
                    }
                }
                this.$emit('input', this.val)
            }
        }
    }
    </script>
    

    金额输入涉及到小数点,当type=tel时部分手机自带键盘没有小数点按钮,故使用type=number,弊端是无法控制光标。

    1. 格式化输入 (例:身份证格式化)
      在input事件中,通过正则表达式格式化输入的身份证号码,更换e.target.value。
    created () {
        this.format = format[this.type]
    },
    mounted () {
        this.$refs.input.value = this.format.regExp(this.val)
    },
    methods: {
        _input (e) {
            let selectionEnd = e.target.selectionEnd
            let _val = this.val
            let _format = this.format.regExp(_val)
            let val = e.target.value.replace(/[^\d]/g, '')
            // let char = /\d/.test(e.target.value.substr(selectionEnd - 1, 1))
            // 删除后,前后的value相同,说明删除的是空格,需要处理成把空格前面的数字也删除掉
            if (_val === val && e.target.value.length < _format.length) {
                this.val = (_format.substr(0, selectionEnd - 1) + _format.substr(selectionEnd, _format.length)).replace(/[^\d]/g, '')
                // selectionEnd -= 1
            } else {
                this.val = val
                // selectionEnd -= (char ? 0 : 1)
            }
            if (this.type === 'idCard' && this.val.length === 17) {
                let lastChar = e.target.value.substr(e.target.value.length - 1, 1).toUpperCase()
                if (lastChar === 'X') {
                    this.val += lastChar
                    // selectionEnd += 1
                }
            }
            e.target.value = this.format.regExp(this.val)
            // if (e.target.value.length > _format.length) {
                // selectionEnd += e.target.value.substr(selectionEnd - 1, 1) === ' ' ? 1 : 0
            // } else {
                // selectionEnd -= e.target.value.substr(selectionEnd - 1, 1) === ' ' ? 1 : 0
            // }
            // this.selectionEnd = selectionEnd
            // e.target.setSelectionRange(this.selectionEnd, this.selectionEnd)
            this.$emit('input', this.val)
        },
        _keydown (e) {
            // this.selectionEnd = e.target.selectionEnd
        },
         _keyup (e) {
            // if (isMobile()) e.target.setSelectionRange(this.selectionEnd, this.selectionEnd)
        }
    }
    

    代码注释块为光标处理,在下面单独说明。

    • 光标控制
      当输入不符合的内容或格式化内容时,input事件进行限制处理时会导致光标位置错乱,需手动进行设置光标的位置,利用HTMLInputElement.setSelectionRange方法进行设置。
    1. setSelectionRange(selectionStart, selectionEnd [, selectionDirection]);


      setSelectionRange兼容性
    • 扩展 - Vue指令应用第三方UI框架 ( 以iview为例 )
    const directives = {
        iviewNumber: {
            inserted: (el, binding, vnode) => {
                let input = el.tagName.toUpperCase() === 'INPUT' ? el : null
                if (!input) {
                    for (let i of el.children) {
                        if (i.tagName.toUpperCase() === 'INPUT') input = i
                    }
                }
                if (input) {
                    let regExp = new RegExp(`^(0|[1-9]\\d*)$`)
                    let value = input.value || ''
                    const handleInput = e => {
                        if (e.target.value && !(regExp.test(e.target.value))) {
                            e.target.value = value
                        } else {
                            value = e.target.value
                        }
                        vnode.componentInstance.$emit('input', value)
                    }
                    const handleKeydown = e => {
                        value = e.target.value
                    }
                    on(input, 'input', handleInput)
                    on(input, 'keydown', handleKeydown)
                }
            }
        }
    }
    export default directives
    

    input动态设置的value值通过keydown事件设置定义的value初始值。

    <iview-input v-model="iviewInput" v-iview-number type="tel">
        <span slot="prepend">纯数字输入框</span>
    </iview-input>
    
    • 总结
      利用input事件可以对输入内容进行限制,使用HTMLInputElement.setSelectionRange方法对输入时的光标进行控制,在使用Vue时可以方便地抽取纯数字组件、金额组件、身份证格式化组件等,在使用第三方UI框架时,也可以方便地自定义指令对基础输入组件进行限制及格式化输入。
      github地址

    相关文章

      网友评论

          本文标题:Input输入限制及格式化(PC&Mobile)

          本文链接:https://www.haomeiwen.com/subject/teqefctx.html