vue自定义指令v-input:限制输入框只能输入数字、字母、中文等规则【附源码】

1,221 阅读3分钟

指令封装

directives/input/index.js

/*
  限制输入框只能输入数字、字母、中文等规则

  使用指令:v-input

  修饰符参数说明:
    v-input.num 只能输入数字,默认不传修饰符,会自动限制只能输入数字
    v-input.intp 只能输入正整数
    v-input.num_alp 只能输入数字和字母
    v-input.num_alp_blank 只能输入数字、字母、空格
    v-input.num_alp_sym 只能输入数字和字母、英文符号、空格
    v-input.float 只能输入数字和小数点  v-input.float="2" 表示小数位数为2,默认小数位数为2,v-input.float="2"可以简写为v-input.float
    v-input.no_emoji 不能输入表情符号

*/
// 只能输入数字
function num(el) {
  el.value = el.value.replace(/\D+/g, '')
}

// 只能输入正整数
function intp(el) {
  const value = el.value.replace(/\D+/g, '') // 去掉非数字字符
  el.value = /^[1-9][0-9]*$/.test(value) ? value : value.replace(/^0+/, '') // 确保为正整数,去掉前导零
}

// 只能输入数字和字母
function num_alp(el) {
  el.value = el.value.replace(/[^A-Za-z0-9]/g, '')
}

// 只能输入数字、字母、空格
function num_alp_blank(el) {
  const regex = /[^a-zA-Z0-9 ]/g
  el.value = el.value.replace(regex, '')
}

// 只能输入数字、字母、英文符号、空格
function num_alp_sym(el) {
  const regex = /[^a-zA-Z0-9`~!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/? ]/g
  el.value = el.value.replace(regex, '')
}

// 只能输入数字和小数点,n表示小数位数
function float(el, n) {
  let value = el.value
  value = value.replace(/[^\d.]/g, '') // 能数字和小数点
  value = value.replace(/^\./g, '') // 去掉开头的点
  value = value.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.') // 处理多个点的情况
  if (n && Number(n) > 0) {
    const d = new Array(Number(n)).fill('\\d').join('') // 构建正则表达式
    const reg = new RegExp(`^(\\-)*(\\d+)\\.(${d}).*$`, 'ig')
    value = value.replace(reg, '$1$2.$3') // 限制小数位数
  }
  // if (value && !value.includes('.')) {
  //   value = Number(value).toString() // 去掉开头多个0
  // }
  el.value = value
}

// 限制表情:😀😂❤️🌟🎉🌍🐶☺
function no_emoji(el) {
  const regex =
    /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E6}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{263A}]+/gu
  el.value = el.value.replace(regex, '')
}

// 这里扩展限制的类型
const map = { num, intp, num_alp, num_alp_blank, num_alp_sym, float, no_emoji }

export default {
  bind(el, binding, vnode) {
    el = el.querySelector('.el-input__inner') || el.querySelector('.el-textarea__inner') || el
    let lock = false // 标记是否需要锁定输入框
    let isHandling = false // 标记是否正在处理
    let lastValue = null
    // input事件处理函数
    const handler = () => {
      if (lock) return // 如果当前为锁定状态,则不进行处理
      if (isHandling) return // 如果已经在处理中,则不进行处理
      if (el.value === lastValue) return // 输入内容没有变化,则不进行处理
      isHandling = true // 设置标记为处理中
      const modifiers = Object.keys(binding.modifiers)
      const newModifier = modifiers[0] || 'num'
      map[newModifier](el, binding.value || 2)
      lastValue = el.value // 记录当前输入框的值
      Promise.resolve().then(() => {
        // 异步处理,场景:火狐浏览器中,需要在最后派发input事件
        el.dispatchEvent(new Event('input'))
      })
      isHandling = false // 处理完毕后设置标记为非处理状态
    }
    el.addEventListener('input', handler)
    // compositionstart和compositionend事件解决的bug场景:限制只能输入数字的输入框,先输入数字,再切换为中文输入法,输入字母时,会将原来的数字删掉
    el.addEventListener('compositionstart', () => {
      lock = true
    })
    el.addEventListener('compositionend', () => {
      lock = false
      el.dispatchEvent(new Event('input'))
    })
    // 当指令与元素解绑时,移除事件监听器
    vnode.context.$once('hook:destroyed', () => {
      el.removeEventListener('input', handler)
    })
  }
}

directives/index.js

import input from './input'

const directives = { input }

Object.keys(directives).forEach(key => {
  Vue.directive(key, directives[key])
})

main.js

import './directives'

注意事项:

  1. 指令的封装不要用keyup事件,当限制只能输入数字时,中文输入法下,输入字母,鼠标点击输入法提示的中文,会将中文输入至文本框中,原因是鼠标点击事件不会触发keyup,用input事件会规避掉这个问题
  2. 使用input事件时,控制台会提示堆栈溢出,需要借助isHandling,执行handler时,先判断是否需要处理
  3. 当限制只能输入数字时,先输入数字,在切换为中文输入法,输入字母时,会将原来的数字删掉,这个问题可以借助compositionstart和compositionend事件解决

使用

image.png

组件封装

CustomInput/index.vue

<!--
  使用时,直接用 <CustomInput /> 替代 <el-input />

  limit:限制输入类型,可选值:num、intp、numAlp、numAlpBlank、numAlpSym、float、noEmoji,默认为num
  decimals:当limit为float类型时,限制小数位数

  支持插槽:
    <CustomInput v-model="subItem.regionHigh" limit="float" maxlength="12" placeholder="请输入" clearable>
      <template slot="prepend">H</template>
      <template slot="append">cm</template>
    </CustomInput>

  支持拓展:
    1. 定义限制类型的函数
    2. 在map对象中添加此类型
    3. 完善新类型的注释
-->

<template>
  <el-input v-model="inputValue" v-bind="$attrs" v-on="$listeners" @input="handleInput">
    <span v-for="{ name, text } of slotList" :slot="name" :key="text">{{ text }}</span>
  </el-input>
</template>

<script>
const map = {
  num,
  intp,
  numAlp,
  numAlpBlank,
  numAlpSym,
  float,
  noEmoji
}
export default {
  name: 'CustomInput',
  model: { prop: 'value', event: 'changeValue' },
  props: {
    value: { type: [String, Number], default: '' },
    limit: { type: String, default: 'num' }, // 限制输入类型,可选值:num、intp、numAlp、numAlpBlank、numAlpSym、float、noEmoji,默认为num
    decimals: { type: Number, default: 2 } // float类型限制小数位数
  },
  data() {
    const { value } = this
    return { inputValue: value || value === 0 ? String(value) : '', slotList: [] }
  },
  watch: {
    // 手动设置value时,触发handleInput,将不符合限制的字符过滤掉
    value: {
      handler(val) {
        this.handleInput(String(val))
      },
      immediate: true
    }
  },
  created() {
    this.initSlot()
  },
  methods: {
    handleInput(val) {
      if (!this.limit) return
      const filteredInput = map[this.limit](val, this.decimals)
      this.$emit('changeValue', filteredInput)
      this.inputValue = filteredInput
    },
    initSlot() {
      this.$nextTick(() => {
        const { prepend = [], append = [] } = this.$slots
        const prependList = prepend.map((item) => ({ name: 'prepend', text: item.text }))
        const appendList = append.map((item) => ({ name: 'append', text: item.text }))
        const slotList = prependList.concat(appendList)
        this.slotList = slotList
      })
    }
  }
}
// 只能输入数字
function num(val) {
  const newVal = val.replace(/[^0-9]/g, '')
  return newVal
}
// 只能输入正整数
function intp(val) {
  let newVal = val.replace(/[^0-9]/g, '') // 去掉非数字字符
  newVal = /^[1-9][0-9]*$/.test(newVal) ? newVal : newVal.replace(/^0+/, '') // 确保为正整数,去掉前导零
  return newVal
}
// 只能输入数字和字母
function numAlp(val) {
  const newVal = val.replace(/[^A-Za-z0-9]/g, '')
  return newVal
}
// 只能输入数字、字母、空格
function numAlpBlank(val) {
  const newVal = val.replace(/[^a-zA-Z0-9 ]/g, '')
  return newVal
}
// 只能输入数字、字母、英文符号、空格
function numAlpSym(val) {
  const regex = /[^a-zA-Z0-9`~!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/? ]/g
  const newVal = val.replace(regex, '')
  return newVal
}
// 只能输入数字和小数点,n表示小数位数
function float(val, n) {
  let newVal = val.replace(/[^\d.]/g, '') // 能数字和小数点
  newVal = newVal.replace(/^\./g, '') // 去掉开头的点
  newVal = newVal.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.') // 处理多个点的情况
  if (n && Number(n) > 0) {
    const d = new Array(Number(n)).fill('\\d').join('') // 构建正则表达式
    const reg = new RegExp(`^(\\-)*(\\d+)\\.(${d}).*$`, 'ig')
    newVal = newVal.replace(reg, '$1$2.$3') // 限制小数位数
  }
  if (newVal && !newVal.includes('.')) {
    // value = value.replace(/^0+/, '')
    // value = Number(value).toString() // 去掉开头多个0
  }
  return newVal
}
// 限制不可输入表情
function noEmoji(val) {
  // 限制表情:😀😂❤️🌟🎉🌍🐶☺
  const regex = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E6}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{263A}]+/gu
  const newVal = val.replace(regex, '')
  return newVal
}
</script>

注意事项

  1. 使用时需要传入limit,限制类型;当limit为float时,需要传入decimals,限制小数位数
  2. 支持插槽

使用

image.png

源码

www.npmjs.com/package/xtt…

/xtt-tools/directives-v2/input/index.js