在前端开发中,表单输入的控制是一个常见且重要的需求。尤其是对于数字输入框,我们往往需要限制用户只能输入数字、小数点,同时还希望能够控制小数点后的位数,以确保输入数据的准确性和一致性。在 Vue 项目里,使用自定义指令可以优雅地解决这类问题。本文将详细介绍一个自定义的数字输入指令 numberInput,帮助大家在项目中高效实现精确的数字输入控制。
一、指令功能与优势
numberInput 自定义指令具备以下强大功能:
-
输入内容限制:严格限制用户只能输入数字和小数点,避免无效字符干扰数据准确性。
-
小数位数控制:通过指令参数可灵活设置小数点后的位数,默认值为 2 。当参数设为 0 时,仅允许输入整数,完全禁止小数点的输入。
-
正负性控制:只支持正数和零的输入,进一步规范数据格式。
使用该指令的优势显著:
-
提高开发效率:一次编写,多处复用,减少重复代码。在多个页面有数字输入限制需求时,只需引入该指令,配置参数即可使用,无需为每个输入框单独编写繁琐的验证逻辑。
-
增强用户体验:实时对用户输入进行校验和修正,输入不符合规则的数据时,输入框会自动调整为合规内容,避免用户因输入错误而导致后续操作失败,提升用户使用的流畅感。
二、指令代码以及解析
import type { Directive } from "vue";
/**
* 数字输入指令
* 限制只能输入数字、小数点,且限制小数点后的位数
* 使用方式:v-number-input="2" (参数为小数点后的位数,默认为2)
* 当参数为0时,只允许输入整数,禁止输入小数点
* 只支持正数和零的输入
*/
export const numberInput: Directive = {
mounted(el, binding) {
// 获取小数位数,并进行参数验证
let precision = binding.value === undefined ? 2 : binding.value;
// 参数验证:确保precision是非负整数
if (
typeof precision !== "number" ||
precision < 0 ||
!Number.isInteger(precision)
) {
console.warn(
"v-number-input: precision 参数必须是非负整数,已重置为默认值 2"
);
precision = 2;
}
// 获取真实的输入框元素
const inputEl = el.tagName === "INPUT" ? el : el.querySelector("input");
if (!inputEl) return;
// 标记是否正在使用输入法
let isComposing = false;
// 标记是否正在程序化修改值(避免死循环)
let isUpdating = false;
// 输入法开始事件
const compositionStartHandler = () => {
isComposing = true;
};
// 输入法结束事件
const compositionEndHandler = () => {
isComposing = false;
// 输入法结束后立即处理一次
handler();
};
const handler = function () {
// 如果正在使用输入法或正在程序化更新,跳过处理
if (isComposing || isUpdating) return;
const originalValue = inputEl.value;
let value = originalValue;
// 如果precision为0,完全禁止小数点输入
if (precision === 0) {
value = value.replace(/[^\d]/g, ""); // 只保留数字
} else {
// 移除非数字字符(只保留数字和小数点)
value = value.replace(/[^\d.]/g, "");
// 确保只有一个小数点
const dotIndex = value.indexOf(".");
if (dotIndex !== -1) {
const dotCount = value.split(".").length - 1;
if (dotCount > 1) {
value =
value.substring(0, dotIndex + 1) +
value.substring(dotIndex + 1).replace(/\./g, "");
}
// 限制小数点后位数
if (value.length > dotIndex + precision + 1) {
value = value.substring(0, dotIndex + precision + 1);
}
}
// 确保不以小数点开头
if (value.startsWith(".")) {
value = "0" + value;
}
}
// 只有当值真正改变时才更新
if (value !== originalValue) {
isUpdating = true;
inputEl.value = value;
// 触发input事件,确保v-model更新
inputEl.dispatchEvent(new Event("input"));
// 触发change事件,确保表单验证更新
inputEl.dispatchEvent(new Event("change"));
isUpdating = false;
}
};
// 添加事件监听
inputEl.addEventListener("input", handler);
inputEl.addEventListener("compositionstart", compositionStartHandler);
inputEl.addEventListener("compositionend", compositionEndHandler);
// 保存remove函数以便在unmounted时清理
el._numberInputRemove = () => {
inputEl.removeEventListener("input", handler);
inputEl.removeEventListener("compositionstart", compositionStartHandler);
inputEl.removeEventListener("compositionend", compositionEndHandler);
};
},
unmounted(el) {
// 清理事件监听
el._numberInputRemove && el._numberInputRemove();
}
};
-
参数获取与验证:在 mounted 钩子函数中,从 binding.value 获取小数点后的位数 precision ,如果未传入参数,则默认值为 2 。同时,对 precision 进行严格验证,确保其为非负整数,若不满足条件,将重置为默认值 2 并在控制台给出警告提示。
-
输入框元素获取:根据指令绑定的元素 el ,判断其是否为 INPUT 标签。如果是,则直接使用;如果不是,则查找其内部的 input 元素作为实际操作对象,若未找到则直接返回,不进行后续操作。
-
输入法状态管理:定义 isComposing 标记是否正在使用输入法,以及 isUpdating 标记是否正在程序化修改值,用于避免在输入法输入过程中或指令自身修改值时触发不必要的处理逻辑,防止出现死循环。
-
输入处理逻辑:在 handler 函数中,首先判断当前是否处于输入法输入状态或正在程序化更新,若为真则直接返回。然后根据 precision 的值进行不同的处理:
-
当 precision === 0 时,使用正则表达式 value.replace(/[^\d]/g, "") 移除所有非数字字符,只保留数字,实现只允许输入整数的功能。
-
当 precision > 0 时,先使用 value.replace(/[^\d.]/g, "") 移除非数字和小数点的字符;接着通过判断小数点数量和位置,确保输入框中只有一个小数点,并限制小数点后的位数;最后,若输入值以小数点开头,自动在前面补 0 。
-
三、使用示例
在main.ts中导入注册后即可通过v-number-input全局使用
<el-input
v-model.number=""
v-number-input="0"
placeholder="请输入区域排序"
/>