fix(form-input-number): 限制允许输入的字符 (#2465)

* fix(form-input-number): 阻止非法字符输入

* chore: 简化代码

* fix: ie8

* update

* fix

* feat(form-input-number): 增强 input-number

* refactor: 移除 lay-keyboard 属性,由 readonly 替代

* update

* update

* docs: 更新文档

* chore: readonly 时禁用控制按钮

* docs: update
This commit is contained in:
morning-star 2025-03-10 14:08:59 +08:00 committed by GitHub
parent 7c503ad6c7
commit 6fa811f8a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 116 additions and 8 deletions

View File

@ -114,7 +114,8 @@ toc: true
数字输入框 <sup>2.8.9+</sup>
</h3>
一般搭配 `<input type="number">` 使用,用于替代原生数字输入框,支持的属性如下:
一般搭配 `<input type="text">` 使用,用于替代原生数字输入框,支持的属性如下:
注:<sup>2.10+</sup> 之前的版本,使用 `type="number"` 类型的输入框。
| 属性 | 描述 |
| --- | --- |
@ -122,6 +123,8 @@ toc: true
| min | 设置数字的最小值 |
| max | 设置数字的最大值 |
| lay-precision <sup>2.8.18+</sup> | 设置数字的小数位精度。注<sup>2.9.8+</sup>:若值为 `0`,则表示取整。 |
| lay-step-strictly <sup>2.10+</sup> | 步长严格模式,只能输入步长的倍数 |
| lay-wheel <sup>2.10+</sup> | 是否启用滚轮或触摸板事件处理 |
### 示例
@ -134,19 +137,22 @@ toc: true
<hr class="ws-space-16">
<div class="layui-row layui-col-space16">
<div class="layui-col-xs6">
<input type="number" lay-affix="number" placeholder="设置 step 为 0.01" step="0.01" class="layui-input">
<input type="text" lay-affix="number" placeholder="设置 step 为 0.01" step="0.01" class="layui-input">
</div>
<div class="layui-col-xs6">
<input type="number" lay-affix="number" placeholder="设置 step,min,max" step="10" min="0" max="100" class="layui-input">
<input type="text" lay-affix="number" placeholder="设置 step,min,max" step="10" min="0" max="100" class="layui-input">
</div>
<div class="layui-col-xs6">
<input type="text" lay-affix="number" placeholder="步长严格模式" lay-step-strictly step="10" min="0" max="100" class="layui-input">
</div>
<div class="layui-col-xs4">
<input type="number" lay-affix="number" placeholder="设置小数位精度为 2" step="0.1" lay-precision="2" class="layui-input">
<input type="text" lay-affix="number" placeholder="设置小数位精度为 2" step="0.1" lay-precision="2" class="layui-input">
</div>
<div class="layui-col-xs4">
<input type="number" lay-affix="number" readonly placeholder="不允许输入状态" class="layui-input">
<input type="text" lay-affix="number" readonly placeholder="不允许输入状态" class="layui-input">
</div>
<div class="layui-col-xs4">
<input type="number" lay-affix="number" disabled placeholder="禁用状态" class="layui-input">
<input type="text" lay-affix="number" disabled placeholder="禁用状态" class="layui-input">
</div>
</div>
</div>

View File

@ -838,7 +838,8 @@ hr.layui-border-black{border-width: 0 0 1px;}
.layui-input-wrap .layui-input[type="number"]::-webkit-outer-spin-button,
.layui-input-wrap .layui-input[type="number"]::-webkit-inner-spin-button{-webkit-appearance: none !important;}
.layui-input-wrap .layui-input[type="number"]{-moz-appearance: textfield;}
.layui-input-wrap .layui-input[type="number"].layui-input-number-out-of-range{color:#ff5722;}
.layui-input-wrap .layui-input.layui-input-number-out-of-range,
.layui-input-wrap .layui-input.layui-input-number-invalid{color:#ff5722;}

View File

@ -19,6 +19,7 @@ layui.define(['lay', 'layer', 'util'], function(exports){
var HIDE = 'layui-hide';
var DISABLED = 'layui-disabled';
var OUT_OF_RANGE = 'layui-input-number-out-of-range';
var BAD_INPUT = 'layui-input-number-invalid';
var Form = function(){
this.config = {
@ -194,10 +195,15 @@ layui.define(['lay', 'layer', 'util'], function(exports){
var precision = Number(elem.attr('lay-precision'));
var noAction = eventType !== 'click' && rawValue === ''; // 初始渲染和失焦时空值不作处理
var isInit = eventType === 'init';
var isBadInput = isNaN(value);
var isStepStrictly = typeof elem.attr('lay-step-strictly') === 'string';
if(isNaN(value)) return; // 若非数字,则不作处理
elem.toggleClass(BAD_INPUT, isBadInput);
if(isBadInput) return; // 若非数字,则不作处理
if(eventType === 'click'){
// 兼容旧版行为2.10 以前 readonly 不禁用控制按钮
if(elem[0].type === 'text' && typeof elem.attr('readonly') === 'string') return;
var isDecrement = !!$(that).index() // 0: icon-up, 1: icon-down
value = isDecrement ? value - step : value + step;
}
@ -214,6 +220,9 @@ layui.define(['lay', 'layer', 'util'], function(exports){
if (!noAction) {
// 初始渲染时只处理数字精度
if (!isInit) {
if(isStepStrictly){
value = Math.round(value / step) * step;
}
if(value <= min) value = min;
if(value >= max) value = max;
}
@ -223,7 +232,9 @@ layui.define(['lay', 'layer', 'util'], function(exports){
} else if(precision > 0) { // 小数位精度
value = value.toFixed(precision);
}
elem.val(value);
elem.attr('lay-input-mirror', elem.val())
}
// 超出范围的样式
@ -369,6 +380,71 @@ layui.define(['lay', 'layer', 'util'], function(exports){
className: 'layui-input-number',
disabled: othis.is('[disabled]'), // 跟随输入框禁用状态
init: function(elem){
// 旧版浏览器不支持更改 input 元素的 type 属性,需要主动设置 text
if(elem.attr('type') === 'text' || elem[0].type === 'text'){
var ns = '.lay_input_number';
var skipCheck = false;
var isComposition = false;
var isReadonly = typeof elem.attr('readonly') === 'string';
var isMouseWheel = typeof elem.attr('lay-wheel') === 'string';
var btnElem = elem.next('.layui-input-number').children('i');
// 旧版浏览器不支持 beforeInput 事件,需要设置一个 attr 存储输入前的值
elem.attr('lay-input-mirror', elem.val());
elem.off(ns);
// 旧版浏览器不支持 event.inputType 属性,需要用 keydown 事件来判断是否跳过输入检查
elem.on('keydown' + ns, function (e) {
skipCheck = false;
if (e.keyCode === 8 || e.keyCode === 46) { // Backspace || Delete
skipCheck = true;
}
// Up & Down 键盘事件处理
if(!isReadonly && btnElem.length === 2 && (e.keyCode === 38 || e.keyCode === 40)){
e.preventDefault();
btnElem.eq(e.keyCode === 38 ? 0 : 1).click();
}
})
elem.on('input' + ns + ' propertychange' + ns, function (e) {
if (isComposition || (e.type === 'propertychange' && e.originalEvent.propertyName !== 'value')) return;
if (skipCheck || canInputNumber(this.value)) {
elem.attr('lay-input-mirror', this.value);
} else {
// 恢复输入前的值
this.value = elem.attr('lay-input-mirror');
}
elem.toggleClass(BAD_INPUT, isNaN(Number(this.value)));
});
elem.on('compositionstart' + ns, function () {
isComposition = true;
});
elem.on('compositionend' + ns, function () {
isComposition = false;
elem.trigger('input');
})
// 响应鼠标滚轮或触摸板
if(isMouseWheel){
elem.on(['wheel','mousewheel','DOMMouseScroll'].join(ns + ' ') + ns, function (e) {
if(!btnElem.length) return;
if(!$(this).is(':focus')) return;
var direction = 0;
e.preventDefault();
// IE9+chrome 和 firefox 同时添加 'wheel' 和 'mousewheel' 事件时,只执行 'wheel' 事件
if(e.type === 'wheel'){
e.deltaX = e.originalEvent.deltaX;
e.deltaY = e.originalEvent.deltaY;
direction = Math.abs(e.deltaX) >= Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
}else if(e.type === 'mousewheel' ){
direction = -e.originalEvent.wheelDelta;
}else if(e.type === 'DOMMouseScroll'){
direction = e.originalEvent.detail;
}
btnElem.eq(direction > 0 ? 1 : 0).click();
})
}
if(isReadonly){
btnElem.addClass(DISABLED);
}
}
handleInputNumber.call(this, elem, 'init')
},
click: function(elem){
@ -1344,6 +1420,31 @@ layui.define(['lay', 'layer', 'util'], function(exports){
return true;
}
}
// 修改自 https://github.com/Tencent/tdesign-common/blob/53786c58752401e648cc45918f2a4dbb9e8cecfa/js/input-number/number.ts#L209
var specialCode = ['-', '.', 'e', 'E', '+'];
function canInputNumber(number) {
if (number === '') return true;
// 数字最前方不允许出现连续的两个 0
if (number.slice(0, 2) === '00') return false;
// 不能出现空格
if (number.match(/\s/g)) return false;
// 只能出现一个点(.
var tempMatched = number.match(/\./g);
if (tempMatched && tempMatched.length > 1) return false;
// 只能出现一个ee
tempMatched = number.match(/e/g);
if (tempMatched && tempMatched.length > 1) return false;
// 只能出现一个负号(-)或 一个正号(+),并且在第一个位置;但允许 3e+10 这种形式
var tempNumber = number.slice(1);
tempMatched = tempNumber.match(/(\+|-)/g);
if (tempMatched && (!/e(\+|-)/i.test(tempNumber) || tempMatched.length > 1)) return false;
// 允许输入数字字符
var isNumber = !isNaN(Number(number));
if (!isNumber && !(specialCode.indexOf(number.slice(-1)) !== -1)) return false;
if (/e/i.test(number) && (!/\de/i.test(number) || /e\./.test(number))) return false;
return true;
}
var form = new Form();
var $dom = $(document);