el-input 的最强数字输入指令 —— v-hasNumber

在实际项目中,我们经常会遇到输入框只能输入数字、限制小数位、范围、补零、是否允许负数等场景

如果这些逻辑都散落在业务代码里,不仅繁琐,还难以维护。

于是,这里提供一个高可定制的 Vue 指令v-hasNumber,帮你轻松搞定所有数字输入规则。


🔧 使用方式

在el-input上直接绑定指令,并传入配置项:

<el-input v-hasNumber="{ precision: 3 }" />


📑 配置参数说明



💻 指令内容:(hasNumber.ts)

/* eslint-disable */

// | 字段         | 类型   | 默认值     | 说明   |

// | ----------- | ------ | --------- | ---- |

// | `precision` | number | 8         | 小数位数 |

// | `maxInt`    | number | 10        | 整数位数 |

// | `min`       | number | -Infinity | 最小值  |

// | `max`       | number | Infinity  | 最大值  |

// | `zero`      | boolean | true     | 是否补零|

// | `negative`  | boolean | false    | 是否强制负数 |

// | `equal`     | boolean | true     | 等号输入 |

import type { App, DirectiveBinding } from 'vue';

function setup(el: HTMLElement) {

  const input = el instanceof HTMLInputElement ? el : el.querySelector('input');

  if (!input || input.dataset.hasNumberBound === '1') return;

  input.dataset.hasNumberBound = '1';

  // 添加composition状态标记

  let isComposing = false;

  // 监听输入法开始事件

  input.addEventListener('compositionstart', () => {

    isComposing = true;

  });

  // 监听输入法结束事件

  input.addEventListener('compositionend', () => {

    isComposing = false;

    // 输入法结束时手动触发一次input处理

    const event = new Event('input', { bubbles: true });

    input.dispatchEvent(event);

  });

  input.addEventListener('keydown', (e) => {

    const config = (el as any)._numberConfig;

    if (!config) return;

    const { precision, hasPrecision, min, equal } = config;

    if (min >= 0 && e.key === '-') e.preventDefault();

    if (hasPrecision && precision === 0 && e.key === '.') e.preventDefault();

    if (!equal && e.key === '=') e.preventDefault();

  });

  input.addEventListener('input', () => {

    // 如果正在使用输入法,不处理

    if (isComposing) return;

    const config = (el as any)._numberConfig;

    if (!config) return;

    const input = el instanceof HTMLInputElement ? el : el.querySelector('input');

    let val = input!.value || '';

    const { maxInt, precision, hasPrecision, defaultPrecision, equal } = config;

    const isNegative = val.startsWith('-');

    const allowChars = equal ? /[^0-9.=]/g : /[^0-9.]/g;

    val = val.replace(allowChars, '');

    if (equal) {

      const firstEqualIndex = val.indexOf('=');

      if (firstEqualIndex !== -1) {

        val =

          val.slice(0, firstEqualIndex + 1) +

          val

            .slice(firstEqualIndex + 1)

            .replace(/=/g, '');

      }

    }

    let [intPart = '', decPart = ''] = val.split('.');

    intPart = intPart.slice(0, maxInt);

    const effPrec = hasPrecision ? precision! : defaultPrecision;

    decPart = effPrec > 0 ? decPart.slice(0, effPrec) : '';

    let newVal = (isNegative ? '-' : '') + intPart;

    if (val.includes('.')) {

      newVal += '.' + decPart;

    }

    if (newVal !== input!.value) {

      const pos = input!.selectionStart ?? newVal.length;

      input!.value = newVal;

      try {

        input!.setSelectionRange(pos, pos);

      } catch {}

      input!.dispatchEvent(new Event('input', { bubbles: true }));

    }

  });

  input.addEventListener('blur', () => {

    // blur时重置composition状态

    isComposing = false;

    const config = (el as any)._numberConfig;

    if (!config) return;

    const input = el instanceof HTMLInputElement ? el : el.querySelector('input');

    const { hasPrecision, precision, min, max, zero, negative } = config;

    let val = input!.value;

    if (!val || val === '-' || val === '.') {

      input!.value = '';

      input!.dispatchEvent(new Event('input', { bubbles: true }));

      return;

    }

    let num = parseFloat(val);

    if (isNaN(num)) {

      input!.value = '';

    } else {

      if (num < min) num = min;

      if (num > max) num = max;

      if (negative && num > 0) num = -num;

      const truncateDecimal = (num: number, precision: number) => {

        const [intPart, decPart = ''] = num.toString().split('.');

        return precision <= 0 || !decPart ? intPart : `${intPart}.${decPart.slice(0, precision)}`;

      };

      input!.value = hasPrecision

        ? (zero ? num.toFixed(precision!) : truncateDecimal(num, precision!))

        : num.toString();

    }

    input!.dispatchEvent(new Event('input', { bubbles: true }));

  },true);

}

// 每次 updated 或 mounted 都刷新配置

function updateConfig(el: HTMLElement, binding: DirectiveBinding) {

  const value = binding.value || {};

  const hasPrecision = Object.prototype.hasOwnProperty.call(value, 'precision');

  (el as any)._numberConfig = {

    maxInt: value.maxInt ?? 10,

    precision: hasPrecision ? value.precision! : undefined,

    hasPrecision,

    defaultPrecision: 8,

    min: value.min ?? -Infinity,

    max: value.max ?? Infinity,

    zero: value.zero === true,

    negative: value.negative === true,

    equal: value.equal === true,

  };

}

export function hasNumber(app: App<Element>) {

  app.directive('hasNumber', {

    mounted(el, binding) {

      setup(el);

      updateConfig(el, binding);

    },

    updated(el, binding) {

      updateConfig(el, binding);

    }

  });

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容