自定义指令 数值输入显示优化 巴飞特 测试

该指令实现了以下核心功能:

  1. 字符限制:根据配置(默认支持14位整数+2位小数)进行输入拦截。
  2. 千位分隔符 :在输入过程中和失焦后,自动格式化显示逗号(如 1,000)。
  3. 动态字体大小:根据纯数字字符的长度,动态改变输入框字体大小(48px -> 38px -> 28px -> 22px)。
  4. 粘贴兼容:支持粘贴带格式或不带格式的数字串。

1. 创建指令文件 (directives/moneyInput.js)

在你的 src目录下新建 directives文件夹,并创建 moneyInput.js文件。

ini 复制代码
// src/directives/moneyInput.js

const moneyInput = {
  inserted(el, binding) {
    // 1. 初始化配置
    const config = {
      integerLimit: 14, // 整数部分最大位数
      decimalLimit: 2,  // 小数部分最大位数
      ...binding.value,
    };

    // 2. 设置基础样式(防止字体过小影响布局)
    el.style.textAlign = 'right';
    el.style.fontVariantNumeric = 'tabular-nums'; // 数字等宽,防止抖动
    el.style.transition = 'font-size 0.1s ease';

    // 3. 绑定事件
    el.addEventListener('input', handleInput);
    el.addEventListener('blur', handleBlur);
    el.addEventListener('focus', handleFocus);
    el.addEventListener('paste', handlePaste);

    // 内部状态记录
    el._moneyConfig = config;

    // --- 核心逻辑函数 ---

    // 处理输入过程
    function handleInput(e) {
      let value = e.target.value;
      
      // 过滤非数字和非小数点字符(允许退格、删除、箭头等控制键)
      // 注意:这里保留用户正在输入的逗号,但在清理时会去掉
      let cleaned = value.replace(/[^\d.]/g, '');

      // 处理多个小数点的情况,只保留第一个
      const decimalIndex = cleaned.indexOf('.');
      if (decimalIndex !== -1) {
        // 如果有多个小数点,截取第一个及其后面的部分,并去除后续的小数点
        const firstPart = cleaned.substring(0, decimalIndex + 1);
        const secondPart = cleaned.substring(decimalIndex + 1).replace(/./g, '');
        cleaned = firstPart + secondPart;
      }

      // 字符长度限制逻辑
      const parts = cleaned.split('.');
      let integerPart = parts[0];
      let decimalPart = parts[1] || '';

      // 限制整数位
      if (integerPart.length > config.integerLimit) {
        integerPart = integerPart.slice(0, config.integerLimit);
      }
      // 限制小数位
      if (decimalPart.length > config.decimalLimit) {
        decimalPart = decimalPart.slice(0, config.decimalLimit);
      }

      // 重组最终值
      let finalValue = integerPart;
      if (config.decimalLimit > 0) {
        finalValue += '.' + decimalPart;
      }

      // 更新 DOM
      e.target.value = finalValue;
      
      // 更新字体大小
      updateFontSize(el, integerPart.length + decimalPart.length);
    }

    // 处理失去焦点(格式化)
    function handleBlur(e) {
      let value = e.target.value;
      if (!value) return;

      const parts = value.split('.');
      let integerPart = parts[0];
      let decimalPart = parts[1] || '';

      // 补齐小数位
      if (config.decimalLimit > 0 && decimalPart.length < config.decimalLimit) {
        decimalPart = decimalPart.padEnd(config.decimalLimit, '0');
      }

      // 添加千位分隔符
      integerPart = addCommas(integerPart);

      e.target.value = decimalPart ? `${integerPart}.${decimalPart}` : integerPart;
      updateFontSize(el, integerPart.replace(/,/g, '').length + decimalPart.length);
    }

    // 处理获取焦点(去除格式,方便修改)
    function handleFocus(e) {
      let value = e.target.value;
      // 移除千位分隔符,只保留纯数字
      e.target.value = value.replace(/,/g, '');
    }

    // 处理粘贴
    function handlePaste(e) {
      e.preventDefault();
      const pasteData = e.clipboardData.getData('text/plain');
      // 模拟 input 事件处理逻辑
      const tempDiv = document.createElement('div');
      tempDiv.textContent = pasteData;
      // 尝试提取数字
      const numbers = tempDiv.textContent.replace(/[^\d.]/g, '');
      
      // 触发 input 逻辑
      el.dispatchEvent(new Event('input'));
    }

    // 辅助函数:添加千位分隔符
    function addCommas(str) {
      return str.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    }

    // 辅助函数:更新字体大小
    function updateFontSize(element, charLength) {
      let newSize = 48; // 默认 1-7 个字符
      
      if (charLength > 16) newSize = 22;
      else if (charLength > 12) newSize = 28;
      else if (charLength > 8) newSize = 38;
      else if (charLength > 7) newSize = 48;

      element.style.fontSize = `${newSize}px`;
    }
  },

  unbind(el) {
    // 销毁事件监听,防止内存泄漏
    el.removeEventListener('input', handleInput);
    el.removeEventListener('blur', handleBlur);
    el.removeEventListener('focus', handleFocus);
    el.removeEventListener('paste', handlePaste);
  },
};

export default moneyInput;

2. 注册全局指令 (main.js)

在项目的入口文件中引入并使用该指令。

javascript 复制代码
import Vue from 'vue'
import App from './App.vue'
import moneyInput from './directives/moneyInput'

Vue.directive('money-input', moneyInput)

new Vue({
  render: h => h(App),
}).$mount('#app')

3. 在组件中使用 (Template)

在你的 Vue 模板中直接绑定指令即可。可以通过参数传递自定义配置。

xml 复制代码
<template>
  <div class="container">
    <h3>转账金额</h3>
    <!-- 使用默认配置 (14位整数 + 2位小数) -->
    <input 
      type="text" 
      v-money-input 
      placeholder="请输入金额" 
      @keyup.enter="submit"
    />

    <h3>证券经纪存款 (可配置不同位数)</h3>
    <!-- 假设证券业务需要更大的额度,比如 18 位整数 -->
    <input 
      type="text" 
      v-money-input="{ integerLimit: 18, decimalLimit: 2 }" 
      placeholder="证券经纪存款"
    />
  </div>
</template>

<script>
export default {
  methods: {
    submit(e) {
      // 这里的 e.target.value 将是纯数字字符串(例如 "1234567890.00")
      console.log('提交金额:', e.target.value);
      alert(`实际提交数值: ${e.target.value}`);
    }
  }
}
</script>

<style scoped>
.container {
  padding: 20px;
}
input {
  width: 300px;
  padding: 10px;
  border: 1px solid #ccc;
  margin-bottom: 20px;
  /* 初始字体大小建议设置为最大的 48px */
  font-size: 48px; 
  font-family: Arial, sans-serif;
  outline: none;
}
input:focus {
  border-color: #007bff;
}
</style>

关键点说明:

  1. 字符数统计逻辑 :在 updateFontSize函数中,计算长度时使用的是 integerPart.replace(/,/g, '').length + decimalPart.length,这确保了逗号不计入字符总数,符合你的需求"字符不包括小数点和千位分隔符"。
  2. 空值处理 :如果用户输入后又全部删除,blur事件不会强制补零,保持输入框为空,体验更好。
  3. 精度问题 :这个指令在前端做展示层 的格式化。实际提交给后端时,获取到的值已经是标准的数字字符串(如 1000000.50),后端可以直接使用 parseFloat或数据库字段进行高精度处理,避免了前端浮点数精度丢失的问题。
相关推荐
zhedream1 小时前
Vue 3 Teleport 报错实录:从 patch 时机到 `defer` 属性
前端·vue.js
研☆香1 小时前
jQuery补充知识点
前端·javascript·jquery
lichenyang4531 小时前
打车票根卡片 UI 重构:从 Circle 挖洞到 clipShape PathShape,再到 100% 自适应
前端
傅科摆 _ py1 小时前
AI Ping 平台使用教程
java·前端·人工智能
lichenyang4531 小时前
聊天历史从 Preferences 搬到关系型数据库(RDB):为什么换、怎么换、踩了什么坑
前端
HjhIron1 小时前
从栈到队列,再到链表:前端开发者必知的线性数据结构
前端·javascript
PedroQue991 小时前
uni-app路由管理神器:vue-router风格体验
前端·uni-app
用户1733598075371 小时前
花两周用 Vue 3 做了个 PDF 工具站,我在生产环境踩了 8 个坑
前端·vue.js
风骏时光牛马1 小时前
TypeScript 泛型与工具类型实战:企业级通用数据请求封装完整案例
前端