封装一个js文件,numberLimit.js,进行全局挂载,在具体的页面的el-input录入向内执行录入数字校验。可以校验是否整数、正负数、最大值、最小值、小数位数。
使用案例:允许4位小数,不允许负数,最小值为0.00
XML
<el-input v-model="scope.row.step4Price" placeholder="请输入第4阶梯子价格" type="text"
v-number-limit="{ decimalDigits: 4, allowNegative: false, min: 0.00 }" clearable:readonly="detailsDisabled"/>
numberLimit.js 如下:
javascript
export default {
inserted(el, binding, vnode) {
// 获取输入框元素(支持直接绑定input或包含input的容器)
const input = el.tagName === 'INPUT' ? el : el.querySelector('input');
if (!input) return;
// 解析参数(默认值配置)
const options = {
isInteger: false, // 是否只能输入整数
allowNegative: false, // 是否允许负数
max: undefined, // 最大值
min: undefined, // 最小值
decimalDigits: 2, // 最多允许的小数位数(整数模式下强制为0)
...binding.value // 合并用户传入的参数
};
// 处理参数关联:整数模式强制小数位为0
if (options.isInteger) {
options.decimalDigits = 0;
}
// 校验参数有效性(容错处理)
options.decimalDigits = Math.max(0, Math.floor(Number(options.decimalDigits) || 0));
options.max = isNaN(Number(options.max)) ? undefined : Number(options.max);
options.min = isNaN(Number(options.min)) ? undefined : Number(options.min);
// 格式化max/min使其与小数位匹配
const formatLimit = (value) => {
if (value === undefined) return undefined;
return options.decimalDigits === 0
? Math.round(value) // 整数模式:四舍五入取整
: Number(value.toFixed(options.decimalDigits)); // 小数模式:保留指定位数
};
const max = formatLimit(options.max);
const min = formatLimit(options.min);
// 处理输入法组合输入(避免中间状态触发校验)
let isComposing = false;
input.addEventListener('compositionstart', () => { isComposing = true; });
input.addEventListener('compositionend', () => {
isComposing = false;
input.dispatchEvent(new Event('input')); // 输入法完成后重新触发校验
});
// 核心输入处理逻辑
const handleInput = () => {
if (isComposing) return;
let value = input.value.trim();
// 空值处理(仅完全空值时清空,保留负号作为中间状态)
if (value === '') {
if (vnode.model) vnode.model.value = '';
input.value = '';
return;
}
// 处理负数(仅允许时保留负号)
let hasNegativeSign = options.allowNegative && value.startsWith('-');
let numValue = hasNegativeSign ? value.slice(1) : value;
// 过滤非法字符
if (options.isInteger) {
// 整数模式:只保留数字
numValue = numValue.replace(/[^\d]/g, '');
} else {
// 小数模式:保留数字和单个小数点
numValue = numValue.replace(/[^\d.]/g, '');
const dotIndex = numValue.indexOf('.');
if (dotIndex !== -1) {
// 移除多余的小数点
numValue = numValue.slice(0, dotIndex + 1) + numValue.slice(dotIndex + 1).replace(/\./g, '');
}
// 补前导0(如 ".123" → "0.123")
if (numValue.startsWith('.')) numValue = '0' + numValue;
}
// 限制小数位数(非整数模式)
if (!options.isInteger && numValue.includes('.')) {
const [intPart, decPart] = numValue.split('.');
numValue = `${intPart}.${decPart.slice(0, options.decimalDigits)}`;
}
// 还原负号(关键优化:允许负号作为首位,即使还未输入数字)// 有负号标识时,空数字也保留负号
let finalValue = hasNegativeSign ? (numValue ? `-${numValue}` : '-') : numValue;
// 范围校验
const parsedNum = finalValue && finalValue !== '-' ? parseFloat(finalValue) : NaN;
if (!isNaN(parsedNum)) {
// 最大值限制
if (max !== undefined && parsedNum > max) {
finalValue = options.isInteger
? max.toString()
: max.toFixed(options.decimalDigits);
}
// 最小值限制
if (min !== undefined && parsedNum < min) {
finalValue = options.isInteger
? min.toString()
: min.toFixed(options.decimalDigits);
}
} else {
// 仅在非负号中间状态时清空(保留"-"作为合法输入过程)
if (!(options.allowNegative && finalValue === '-')) {
finalValue = '';
}
}
// 同步输入框与v-model(避免不必要的触发)
if (finalValue !== input.value) {
input.value = finalValue;
if (vnode.model) {
vnode.model.value = finalValue; // 保持字符串类型,避免数字自动补位
}
input.dispatchEvent(new Event('input'));
}
};
// 绑定事件(覆盖输入、变更、失焦场景)
input.addEventListener('input', handleInput);
input.addEventListener('change', handleInput);
input.addEventListener('blur', handleInput);
// 存储事件句柄用于解绑
el._numberLimitHandlers = { handleInput };
},
// 解绑时清理事件,避免内存泄漏
unbind(el) {
const input = el.tagName === 'INPUT' ? el : el.querySelector('input');
if (input && el._numberLimitHandlers) {
const { handleInput } = el._numberLimitHandlers;
input.removeEventListener('input', handleInput);
input.removeEventListener('change', handleInput);
input.removeEventListener('blur', handleInput);
}
}
};