如何在Vue自定义指令中防止中文拼音输入影响数字验证?

有一个很难绷的bug

import { Directive } from 'vue';

const getInput = (el: HTMLElement): HTMLInputElement | null => (el instanceof HTMLInputElement ? el : el.querySelector('input'));
let inputHandler = () => {};
/**
 * 解决el-input 数字类型可以输入e + - 符号问题
 */
export const elInputNumber: Directive = {
  mounted(el: HTMLElement, { arg, value }) {
    const input: HTMLInputElement = <HTMLInputElement>getInput(el);
    if (input) {
      // 小数正则
      const decimal: string = arg ? `(\\.\\d{0,${arg}})?` : '';
      // 整数正则
      const integer: string = value ? `(0|[1-9]\\d{0, ${value - 1}})` : '\\d*';
      const regExp: RegExp = new RegExp(integer + decimal, 'g');
      inputHandler = () => {
        // 替换所有的非数字项
        // 如果输入的数字不符合正则表达式,则替换为''
        input.value =
          input.value
            .toString()
            .trim()
            .replace(/[^\d.]/g, '')
            ?.match?.(regExp)?.[0] ?? '';
      };
      // 在文本框输入的时候触发
      input.addEventListener('input', inputHandler, true);
      // input.addEventListener('blur', inputHandler, true);
    }
  },
  unmounted(el: HTMLElement) {
    // 解除绑定的时候去除事件
    const input: HTMLInputElement = <HTMLInputElement>getInput(el);
    input.removeEventListener('input', inputHandler, true);
  }
};

这是一段自定义指令 只能输入数字 用在了el-input中
正常来看是可以拦住的,但是由于它会把非数字替换成空串,导致我在不小心输入中文时,如:你好
会在输入框依次出现 “nihao你好” 在中文出来之前 拼音已经出现在输入框了,导致原本输入的数字被替换为空

关键问题在于 输入中文后,之前绑定的参数似乎失去了绑定效果image.png 上面是输入框的内容 下面是v-model绑定的数据

尝试过把input事件改成blur 也有其他bug出现如 “123你好” 失焦后输入框变成 123 但实际数据不变,可能是对于函数中的input来说 实际内容没变,导致数据没有更新
element自带的方法无法满足产品的要求,如不允许输入加减号等
给输入框@input方法可以解决 但每个input都给这个方法过于麻烦
希望可以满足只能输入数字的同时 不受以上bug影响

阅读 10.5k
avatarAI BotBETA

回答

在Vue自定义指令中,为了防止中文拼音输入影响数字验证,你可以通过优化正则表达式和事件处理逻辑来解决这个问题。具体方法是:

  1. 改进正则表达式:确保正则表达式既能匹配有效的数字(包括小数),又能正确处理输入过程中的中间状态(如拼音)。
  2. 使用compositionstartcompositionend事件:这两个事件可以帮助你检测用户是否在输入拼音(或其他复合文本),从而在拼音输入过程中不进行数字验证。

下面是修改后的自定义指令代码:

import { Directive } from 'vue';

const getInput = (el: HTMLElement): HTMLInputElement | null => (el instanceof HTMLInputElement ? el : el.querySelector('input'));
let inputHandler = () => {};
let isComposing = false; // 用于标记是否处于拼音输入状态

/**

  • 解决el-input 数字类型可以输入e + - 符号问题,并防止中文拼音输入影响数字验证
    */

export const elInputNumber: Directive = {
mounted(el: HTMLElement, { arg, value }) {

const input: HTMLInputElement = <HTMLInputElement>getInput(el);
if (input) {
  // 小数正则
  const decimal: string = arg ? `(\\.\\d{0,${arg}})?` : '';
  // 整数正则
  const integer: string = value ? `(0|[1-9]\\d{0, ${value - 1}})` : '\\d*';
  const regExp: RegExp = new RegExp(`^${integer}${decimal}$`, 'g'); // 修改为正则表达式,确保整个输入匹配

  inputHandler = () => {
    if (!isComposing) {
      // 替换所有的非数字项(除小数点外),如果输入的数字不符合正则表达式,则替换为空
      input.value = input.value
        .toString()
        .trim()
        .replace(/[^\d.]/g, '') // 只保留数字和点
        .match(regExp)?.[0] ?? ''; // 确保整个输入符合正则

      // 如果需要,可以在这里触发v-model的更新(例如,使用v-model绑定的变量和自定义事件)
      // 例如:el.__vue__.$emit('input', input.value); // 假设el是绑定v-model的组件的DOM元素
    }
  };

  // 监听拼音输入的开始和结束
  input.addEventListener('compositionstart', () => {
    isComposing = true;
  }, true);

  input.addEventListener('compositionend', () => {
    isComposing = false;
    // 拼音输入结束后,重新验证输入
    inputHandler();
  }, true);

  // 在文本框输入的时候触发
  input.addEventListener('input', inputHandler, true);
}

},
unmounted(el: HTMLElement) {

// 解除绑定的时候去除事件
const input: HTMLInputElement = <HTMLInputElement>getInput(el);
input.removeEventListener('input', inputHandler, true);
input.removeEventListener('compositionstart', () => { isComposing = true; }, true);
input.removeEventListener('compositionend', () => { isComposing = false; inputHandler(); }, true);

}
};


在这个修改中,我们增加了`compositionstart`和`compositionend`事件监听器,以跟踪用户是否在进行拼音输入。在拼音输入过程中(`isComposing`为`true`时),不进行数字验证和替换操作。拼音输入结束后,重新触发`inputHandler`进行验证。
2 个回答

在Vue自定义指令中处理中文拼音输入对数字验证的影响确实是一个棘手的问题。拼音输入法在输入过程中会临时显示拼音字符,这些字符随后会被转换成实际的汉字。如果你的指令在拼音阶段就进行了验证和替换,那么就会导致你遇到的问题。

为了解决这个问题,你可以考虑以下几个方案:

  1. 延迟验证

    • 不在input事件触发时立即进行验证和替换,而是设置一个短暂的延迟(例如100毫秒)。在延迟期间,如果用户继续输入,则取消之前的延迟并重新设置。只有当用户停止输入一段时间后,才进行验证和替换。
    • 这可以通过使用setTimeoutclearTimeout来实现。
  2. 使用compositionstartcompositionend事件

    • 这两个事件分别表示拼音输入的开始和结束。你可以在compositionstart时暂停验证,然后在compositionend时进行验证和替换。
    • 这样可以确保只有在拼音输入完成后才进行验证,从而避免了拼音字符对验证的干扰。
  3. 结合Vue的v-modelwatch

    • 而不是直接在input事件中进行处理,你可以使用Vue的watch来观察v-model绑定的数据的变化。
    • watch回调中,你可以进行数字验证和替换。由于watch是在数据变化后触发的,因此它不会受到拼音输入过程中的临时字符的影响。

下面是一个使用compositionstartcompositionend事件的修改后的指令示例:

import { Directive } from 'vue';

const getInput = (el: HTMLElement): HTMLInputElement | null => (el instanceof HTMLInputElement ? el : el.querySelector('input'));

export const elInputNumber: Directive = {
  mounted(el: HTMLElement, { arg, value }) {
    const input: HTMLInputElement = getInput(el) as HTMLInputElement;
    if (!input) return;

    let isComposing = false;
    const decimal = arg ? `(\\.\\d{0,${arg}})?` : '';
    const integer = value ? `(0|[1-9]\\d{0, ${value - 1}})` : '\\d*';
    const regExp = new RegExp(`^${integer}${decimal}$`);

    const validateInput = () => {
      if (!isComposing) {
        const matched = input.value.trim().match(regExp);
        input.value = matched ? matched[0] : '';
      }
    };

    input.addEventListener('input', validateInput);
    input.addEventListener('compositionstart', () => { isComposing = true; });
    input.addEventListener('compositionend', () => {
      isComposing = false;
      validateInput(); // 确保在拼音输入完成后进行验证
    });

    // 也可以添加 blur 事件处理器来确保在失焦时进行验证
    // input.addEventListener('blur', validateInput);
  },
  unmounted(el: HTMLElement) {
    const input: HTMLInputElement = getInput(el) as HTMLInputElement;
    if (input) {
      input.removeEventListener('input', validateInput);
      input.removeEventListener('compositionstart', () => {});
      input.removeEventListener('compositionend', validateInput);
      // input.removeEventListener('blur', validateInput);
    }
  }
};

// 注意:上面的 validateInput 函数需要在指令作用域内定义,
// 或者你可以使用闭包来封装它,以便在事件处理器中访问。

在这个示例中,我使用了compositionstartcompositionend事件来跟踪拼音输入的状态,并在拼音输入完成后进行验证和替换。这样,拼音字符就不会干扰数字的验证了。同时,我也保留了input事件处理器来处理普通的输入情况。

我在你原来的基础上做了修改

import { Directive } from 'vue';

const getInput = (el: HTMLElement): HTMLInputElement | null => (el instanceof HTMLInputElement ? el : el.querySelector('input'));
let inputHandler = () => {};

/**
 * 解决el-input 数字类型可以输入e + - 符号问题
 */
export const elInputNumber: Directive = {
  mounted(el: HTMLElement, { arg }) {
    const input: HTMLInputElement = <HTMLInputElement>getInput(el);
    if (input) {
      // 小数正则
      const decimal: string = arg ? `(\\.\\d{0,${arg}})?` : '(\\.\\d*)?';
      // 整数正则
      const integer: string = '\\d*';
      const regExp: RegExp = new RegExp(`^${integer}${decimal}$`);
      inputHandler = () => {
        // 替换所有的非数字和小数点项
        input.value = input.value
          .replace(/[^\d.]/g, '') // 只保留数字和小数点
          .replace(/\.{2,}/g, '.') // 只保留一个小数点
          .replace(/^(\d*\.\d*).*$/, '$1'); // 保留第一个有效的小数部分
        // 如果输入的数字不符合正则表达式,则替换为''
        if (!regExp.test(input.value)) {
          input.value = input.value.slice(0, -1);
        }
      };
      // 在文本框输入的时候触发
      input.addEventListener('input', inputHandler, true);
    }
  },
  unmounted(el: HTMLElement) {
    // 解除绑定的时候去除事件
    const input: HTMLInputElement = <HTMLInputElement>getInput(el);
    input.removeEventListener('input', inputHandler, true);
  }
};
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏