|
@@ -0,0 +1,367 @@
|
|
|
+/**
|
|
|
+ * Layui 数字输入组件
|
|
|
+ *
|
|
|
+ * @author iTanken
|
|
|
+ * @since 2019-03-29
|
|
|
+ * @version 2020-01-19:数字键盘纵向定位自适应
|
|
|
+ * @version 2020-04-02:添加功能按钮悬浮提示开关参数;添加悬浮提示内部键盘按钮样式;修复 number 类型输入框小数输入问题
|
|
|
+ */
|
|
|
+layui.define(['jquery'], function(exports) {
|
|
|
+ var $ = layui.$, baseClassName = 'layui-input-number', keyClassName = 'layui-keyboard-number',
|
|
|
+ style = ['<style type="text/css">',
|
|
|
+ '.', baseClassName, ' + .', keyClassName, ' { position: absolute; display: block; ',
|
|
|
+ ' background-color: #f2f2f2; border-radius: 2px; border: 1px solid #e6e6e6; outline: none; }',
|
|
|
+ '.', keyClassName, ' .layui-key-btn { font-family: Consolas; font-size: 17px; font-weight: 600; ',
|
|
|
+ ' text-align: center; background-color: #ffffff; cursor: pointer; overflow: hidden; padding: 10px; }',
|
|
|
+ '.', keyClassName, ' .layui-key-btn:active { background-color: #f2f2f2; }',
|
|
|
+ '.layui-layer-tips kbd { display: inline-block; padding: 3px 5px; font-size: 11px;',
|
|
|
+ ' line-height: 10px; color: #24292e; vertical-align: middle; background-color: #fafbfc;',
|
|
|
+ ' border: 1px solid #d1d5da; border-radius: 3px; box-shadow: inset 0 -1px 0 #d1d5da; }',
|
|
|
+ '</style>'].join('');
|
|
|
+ $('head link:last')[0] && $('head link:last').after(style) || $('head').append(style);
|
|
|
+
|
|
|
+ var numberInput = {
|
|
|
+ /** 默认配置选项 */
|
|
|
+ options: {
|
|
|
+ // 123:123键置顶, 789:789键置顶
|
|
|
+ topBtns: 123,
|
|
|
+ // 右侧功能按钮
|
|
|
+ rightBtns: true,
|
|
|
+ // 功能按钮提示
|
|
|
+ showTips: true,
|
|
|
+ // 监听键盘事件
|
|
|
+ listening: true,
|
|
|
+ // 批量配置默认小数精确度,-1 不处理精确度
|
|
|
+ defaultPrec: -1,
|
|
|
+ // 初始化回调,无参
|
|
|
+ initEnd: $.noop,
|
|
|
+ // 触发显示回调,参数为当前输入框和数字键盘的 jQuery 对象
|
|
|
+ showEnd: $.noop,
|
|
|
+ // 隐藏键盘回调,参数为当前输入框的 jQuery 对象
|
|
|
+ hideEnd: $.noop,
|
|
|
+ // z-index
|
|
|
+ zIndex: 19999999
|
|
|
+ },
|
|
|
+ /** 初始化 */
|
|
|
+ init: function(custom) {
|
|
|
+ var _this = this;
|
|
|
+ _this.options = $.extend(_this.options, custom);
|
|
|
+ $('.' + baseClassName).attr({
|
|
|
+ "readonly": "readonly"
|
|
|
+ }).on('focus', function(e) {
|
|
|
+ _this.showKeyboard(_this, $(this));
|
|
|
+ });
|
|
|
+ typeof _this.options.initEnd === 'function' && _this.options.initEnd();
|
|
|
+ },
|
|
|
+ /** 获取按键悬浮提示 */
|
|
|
+ getTips: function(tip) {
|
|
|
+ return this.options.showTips ? (' lay-tips="' + tip + '"') : '';
|
|
|
+ },
|
|
|
+ /** 显示数字键盘 */
|
|
|
+ showKeyboard: function(_this, $input) {
|
|
|
+ // 2020-04-02:修复小数输入问题,将 number 类型输入框一律设置为 text
|
|
|
+ $input.prop('type') === 'number' && $input.attr('type', 'text');
|
|
|
+ var $keyBoard = $input.next('.' + keyClassName);
|
|
|
+ if (!$keyBoard[0]) {
|
|
|
+ // 不存在,添加元素
|
|
|
+ var sizeXS = _this.options.rightBtns ? 'xs3' : 'xs4',
|
|
|
+ sizeZero = _this.options.rightBtns ? 'xs6' : 'xs4',
|
|
|
+ // 按钮 123
|
|
|
+ btn123 = [
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="49 97">1</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="50 98">2</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="51 99">3</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>'
|
|
|
+ ].join(''),
|
|
|
+ // 按钮 789
|
|
|
+ btn789 = [
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="55 103">7</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="56 104">8</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="57 105">9</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>'
|
|
|
+ ].join(''),
|
|
|
+ /* 退格键 */
|
|
|
+ backspace = [
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="8">',
|
|
|
+ '<i class="layui-icon layui-icon-return"', _this.getTips('退格 <kbd>Backspace</kbd>'), '></i>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>'
|
|
|
+ ].join(''),
|
|
|
+ /* 增加键 */
|
|
|
+ add = [
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="38 39">',
|
|
|
+ '<i class="layui-icon layui-icon-up"', _this.getTips('增加 <kbd>↑</kbd>'), '></i>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>'
|
|
|
+ ].join(''),
|
|
|
+ /* 减小键 */
|
|
|
+ reduce = [
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="37 40">',
|
|
|
+ '<i class="layui-icon layui-icon-down"', _this.getTips('减小 <kbd>↓</kbd>'), '></i>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>'
|
|
|
+ ].join(''),
|
|
|
+ /* 清空键 */
|
|
|
+ reset = [
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="46">',
|
|
|
+ '<i class="layui-icon layui-icon-refresh-1"', _this.getTips('清空 <kbd>Delete</kbd>'), '></i>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>'
|
|
|
+ ].join('');
|
|
|
+
|
|
|
+ $input.after(['<div tabindex="0" hidefocus="true" class="', keyClassName,
|
|
|
+ ' layui-unselect layui-anim layui-anim-upbit" ',
|
|
|
+ 'style="width:', $input.width() + 10, 'px;">',
|
|
|
+ '<div class="layui-row layui-col-space1">',
|
|
|
+ _this.options.topBtns == 789 ? btn789 : btn123,
|
|
|
+ _this.options.rightBtns ? backspace : '',
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="52 100">4</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="53 101">5</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="54 102">6</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ _this.options.rightBtns ? add : '',
|
|
|
+ _this.options.topBtns == 789 ? btn123 : btn789,
|
|
|
+ _this.options.rightBtns ? reduce : '',
|
|
|
+ _this.options.rightBtns ? '' : backspace,
|
|
|
+ '<div class="layui-col-', sizeZero, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="48 96">0</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ '<div class="layui-col-', sizeXS, '">',
|
|
|
+ '<div class="layui-card">',
|
|
|
+ '<div class="layui-key-btn" data-keycode="110 190">.</div>',
|
|
|
+ '</div>',
|
|
|
+ '</div>',
|
|
|
+ _this.options.rightBtns ? reset : '',
|
|
|
+ '</div>',
|
|
|
+ '</div>'].join(''));
|
|
|
+
|
|
|
+ $keyBoard = $input.next('.' + keyClassName);
|
|
|
+
|
|
|
+ $keyBoard.on('touchstart click', '.layui-key-btn', function(e) {
|
|
|
+ _this.setValue(_this, $input, $(this));
|
|
|
+ layui.stope(e);
|
|
|
+ return false;
|
|
|
+ });
|
|
|
+ $keyBoard.on('blur', function(e) {
|
|
|
+ _this.setValueRange(_this, $input, _this.toFixedPrec(_this, $input));
|
|
|
+ $keyBoard.remove(); // $keyBoard.hide();
|
|
|
+ typeof _this.options.hideEnd === 'function' && _this.options.hideEnd($input);
|
|
|
+ });
|
|
|
+ _this.options.listening && _this.initKeyListening(_this, $input, $keyBoard);
|
|
|
+ }
|
|
|
+
|
|
|
+ _this.display(_this, $input, $keyBoard);
|
|
|
+ },
|
|
|
+ /** 设置数字键盘样式并显示 */
|
|
|
+ display: function(_this, $input, $keyBoard) {
|
|
|
+ var showTop = $input[0].offsetTop + $input[0].offsetHeight + 4, boardHeight = $keyBoard.height(),
|
|
|
+ $win = $(window), topOffset = $keyBoard.offset().top + $keyBoard.outerHeight() + 4 - $win.scrollTop();
|
|
|
+
|
|
|
+ // 数字键盘纵向定位自适应
|
|
|
+ if (topOffset + boardHeight > $win.height() && topOffset >= boardHeight) {
|
|
|
+ showTop = $input[0].offsetTop - boardHeight - 5;
|
|
|
+ }
|
|
|
+
|
|
|
+ $keyBoard.css({
|
|
|
+ 'top': showTop + 'px',
|
|
|
+ 'left': $input[0].offsetLeft + 'px',
|
|
|
+ 'z-index': _this.options.zIndex
|
|
|
+ });
|
|
|
+
|
|
|
+ $keyBoard.show(200, function() {
|
|
|
+ typeof _this.options.showEnd === 'function' && _this.options.showEnd($input, $keyBoard);
|
|
|
+ }).focus();
|
|
|
+ },
|
|
|
+ /** 初始化键盘监听事件 */
|
|
|
+ initKeyListening: function(_this, $input, $keyBoard) {
|
|
|
+ var $key, code;
|
|
|
+ $keyBoard.on('keydown', function(e) {
|
|
|
+ code = e.keyCode;
|
|
|
+ var inputNumber = parseInt($input.val(), 10) || 0;
|
|
|
+ if (code === 107 || e.shiftKey && code === 187) {
|
|
|
+ // 加号切换正数
|
|
|
+ inputNumber < 0 && _this.setValueRange(_this, $input, Math.abs(inputNumber));
|
|
|
+ } else if (code === 109 || e.shiftKey && code === 189) {
|
|
|
+ // 减号切换负数
|
|
|
+ inputNumber > 0 && _this.setValueRange(_this, $input, '-' + inputNumber);
|
|
|
+ } else {
|
|
|
+ // 监听数字键盘,退格键(Backspace)/重置键(Delete)
|
|
|
+ $key = $keyBoard.find('.layui-key-btn[data-keycode~=' + code + ']');
|
|
|
+ if ($key[0]) {
|
|
|
+ $key.trigger('click').css("background-color", "#f2f2f2");
|
|
|
+ $keyBoard.off('keyup').on('keyup', function(e) {
|
|
|
+ $('.layui-key-btn[data-keycode]').css("background-color", "#ffffff");
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (code > 36 && code < 41) {
|
|
|
+ // 上下左右键,防止触发混动条滑动事件
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ /** 处理精确度 */
|
|
|
+ toFixedPrec: function(_this, $input, val1, val2) {
|
|
|
+ var m, s, rs, prec = $.trim($input.data('prec'));
|
|
|
+ // 2020-04-02:修复获取小数精确度配置值问题
|
|
|
+ prec = parseInt(prec === '' || isNaN(prec) ? _this.options.defaultPrec : prec, 10);
|
|
|
+
|
|
|
+ val1 = val1 === undefined ? $input.val() : val1;
|
|
|
+ val1 = val1 == '' ? ($input.attr('min') || 0) : val1;
|
|
|
+ rs = val1.toString().split('.')[1];
|
|
|
+ if (prec < 0) {
|
|
|
+ prec = rs && rs.length || prec;
|
|
|
+ }
|
|
|
+
|
|
|
+ val2 = val2 || 0;
|
|
|
+ rs = val2.toString().split('.')[1];
|
|
|
+ prec = Math.max(prec, rs ? rs.length : 0);
|
|
|
+
|
|
|
+ m = Math.pow(10, prec);
|
|
|
+ s = ((val1 * m + val2 * m).toFixed(0) / m).toString();
|
|
|
+ rs = s.indexOf('.');
|
|
|
+ if (rs < 0 && prec > 0) {
|
|
|
+ rs = s.length;
|
|
|
+ s += '.';
|
|
|
+ }
|
|
|
+ while (s.length <= rs + prec) {
|
|
|
+ s += '0';
|
|
|
+ }
|
|
|
+ return s;
|
|
|
+ },
|
|
|
+ /** 设置值范围 */
|
|
|
+ setValueRange: function(_this, $input, value) {
|
|
|
+ var minVal = $input.attr('min') || Math.pow(-2, 63),
|
|
|
+ maxVal = $input.attr('max') || Math.pow(2, 63) - 1;
|
|
|
+
|
|
|
+ minVal = typeof minVal === 'string' && minVal.indexOf('.') > -1 ? parseFloat(minVal) : parseInt(minVal, 10);
|
|
|
+ maxVal = typeof maxVal === 'string' && maxVal.indexOf('.') > -1 ? parseFloat(maxVal) : parseInt(maxVal, 10);
|
|
|
+
|
|
|
+ if (value < minVal) {
|
|
|
+ value = _this.toFixedPrec(_this, $input, minVal);
|
|
|
+ _this.tips($input, '最小值为 <kbd>' + minVal + '</kbd>!');
|
|
|
+ }
|
|
|
+ if (value > maxVal) {
|
|
|
+ value = _this.toFixedPrec(_this, $input, maxVal);
|
|
|
+ _this.tips($input, '最大值为 <kbd>' + maxVal + '</kbd>!');
|
|
|
+ }
|
|
|
+ value = value < minVal ? minVal : (value > maxVal ? maxVal : value);
|
|
|
+
|
|
|
+ $input.val(value);
|
|
|
+ },
|
|
|
+ /** 设置输入框值 */
|
|
|
+ setValue: function(_this, $input, $key) {
|
|
|
+ var inputVal = $.trim($input.val()), keyVal = $.trim($key.text()), changeVal,
|
|
|
+ prec = $.trim($input.data('prec')), isDecimal = inputVal.indexOf('.') > -1;
|
|
|
+ // 2020-04-02:修复获取小数精确度配置值问题
|
|
|
+ prec = parseInt(prec === '' || isNaN(prec) ? _this.options.defaultPrec : prec, 10);
|
|
|
+
|
|
|
+ if ($.inArray(keyVal, ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.']) > -1) {
|
|
|
+ if (keyVal === '.') {
|
|
|
+ if (inputVal === '' || isDecimal) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (prec === 0) {
|
|
|
+ _this.tips($input, '当前字段不允许输入小数!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (keyVal === '0' && inputVal.indexOf('0') === 0 && !isDecimal) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (inputVal.indexOf('.') > -1 && inputVal.split('.')[1].length >= prec && prec > 0) {
|
|
|
+ _this.tips($input, '精确度为保留小数点后 <kbd>' + prec + '</kbd> 位!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ changeVal = inputVal = (keyVal !== '.' && inputVal === '0' ? '' : inputVal) + keyVal;
|
|
|
+ $input.val(inputVal);
|
|
|
+ } else {
|
|
|
+ changeVal = inputVal === '' ? 0 : inputVal,
|
|
|
+ step = $input.attr('step');
|
|
|
+ if (isDecimal) {
|
|
|
+ step = parseFloat(step) || 0.1;
|
|
|
+ changeVal = parseFloat(changeVal);
|
|
|
+ } else {
|
|
|
+ step = parseInt(step, 10) || 1;
|
|
|
+ changeVal = parseInt(changeVal, 10);
|
|
|
+ }
|
|
|
+ // right function buttons
|
|
|
+ switch($key.data('keycode')) {
|
|
|
+ case '38 39':
|
|
|
+ // ↑、→ 键增加
|
|
|
+ changeVal = _this.toFixedPrec(_this, $input, changeVal, step);
|
|
|
+ break;
|
|
|
+ case '37 40':
|
|
|
+ // ↓、← 键减小
|
|
|
+ changeVal = _this.toFixedPrec(_this, $input, changeVal, -step);
|
|
|
+ break;
|
|
|
+ case 8:
|
|
|
+ // Backspace 键退格
|
|
|
+ var valLength = inputVal.length;
|
|
|
+ valLength && $input.val(inputVal.substring(0, valLength - 1));
|
|
|
+ return;
|
|
|
+ case 46:
|
|
|
+ // Delete 键清空
|
|
|
+ $input.val('');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $input.val(changeVal);
|
|
|
+ },
|
|
|
+ /** 提示 */
|
|
|
+ tips: function($input, msg) {
|
|
|
+ return layer.tips(msg, $input, { tips: [1, '#01AAED'], time: 2e3, anim: 6, zIndex: this.options.zIndex });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ exports('numinput', numberInput);
|
|
|
+});
|