From 6fa811f8a56afe6fbea02f8fc934f119b2444a61 Mon Sep 17 00:00:00 2001 From: morning-star <26325820+Sight-wcg@users.noreply.github.com> Date: Mon, 10 Mar 2025 14:08:59 +0800 Subject: [PATCH] =?UTF-8?q?fix(form-input-number):=20=E9=99=90=E5=88=B6?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E8=BE=93=E5=85=A5=E7=9A=84=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=20(#2465)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- docs/form/input.md | 18 +++++--- src/css/layui.css | 3 +- src/modules/form.js | 103 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 116 insertions(+), 8 deletions(-) diff --git a/docs/form/input.md b/docs/form/input.md index e7cf5a3b..e596c129 100644 --- a/docs/form/input.md +++ b/docs/form/input.md @@ -114,7 +114,8 @@ toc: true 数字输入框 2.8.9+ -一般搭配 `` 使用,用于替代原生数字输入框,支持的属性如下: +一般搭配 `` 使用,用于替代原生数字输入框,支持的属性如下: +注:2.10+ 之前的版本,使用 `type="number"` 类型的输入框。 | 属性 | 描述 | | --- | --- | @@ -122,6 +123,8 @@ toc: true | min | 设置数字的最小值 | | max | 设置数字的最大值 | | lay-precision 2.8.18+ | 设置数字的小数位精度。注2.9.8+:若值为 `0`,则表示取整。 | +| lay-step-strictly 2.10+ | 步长严格模式,只能输入步长的倍数 | +| lay-wheel 2.10+ | 是否启用滚轮或触摸板事件处理 | ### 示例 @@ -134,19 +137,22 @@ toc: true
- +
- + +
+
+
- +
- +
- +
diff --git a/src/css/layui.css b/src/css/layui.css index 63ac6feb..a30e0299 100644 --- a/src/css/layui.css +++ b/src/css/layui.css @@ -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;} diff --git a/src/modules/form.js b/src/modules/form.js index 533c803b..d736ece8 100644 --- a/src/modules/form.js +++ b/src/modules/form.js @@ -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; + // 只能出现一个e(e) + 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);