From 249d6db78ec94329acdb2666391db21bb4d1a2f5 Mon Sep 17 00:00:00 2001 From: morning-star <26325820+Sight-wcg@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:34:41 +0800 Subject: [PATCH] =?UTF-8?q?perf(table):=20=E4=BC=98=E5=8C=96=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E6=95=B0=E6=8D=AE=E9=87=8F=E8=BE=83=E5=A4=A7=E6=97=B6?= =?UTF-8?q?=E8=A1=8C=E9=80=89=E4=B8=AD=E6=80=A7=E8=83=BD=20(#2004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(table): 优化表格数据量较大时行选中性能 * chore: 改进选择器 * refactor: 简化代码 * feat: 多选时禁用过渡效果以提高性能 * update code * update code --- src/css/layui.css | 3 ++ src/modules/form.js | 89 +++++++++++++++++++++++++++++++------------- src/modules/table.js | 70 +++++++++++++++++++++++++++------- 3 files changed, 123 insertions(+), 39 deletions(-) diff --git a/src/css/layui.css b/src/css/layui.css index 45cff7d3..202c082a 100644 --- a/src/css/layui.css +++ b/src/css/layui.css @@ -1028,6 +1028,9 @@ hr.layui-border-black{border-width: 0 0 1px;} .layui-table-checked{background-color: #dbfbf0;} .layui-table-checked.layui-table-hover, .layui-table-checked.layui-table-click{background-color: #abf8dd;} +.layui-table-disabled-transition *, +.layui-table-disabled-transition *:before, +.layui-table-disabled-transition *:after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important} .layui-table th, diff --git a/src/modules/form.js b/src/modules/form.js index 4f40897e..b44a8a6a 100644 --- a/src/modules/form.js +++ b/src/modules/form.js @@ -843,20 +843,13 @@ layui.define(['lay', 'layer', 'util'], function(exports){ // 事件 var events = function(reElem, RE_CLASS){ var check = $(this); + var skin = check.attr('lay-skin') || 'primary'; + var isSwitch = skin === 'switch'; + var isPrimary = skin === 'primary'; // 勾选 reElem.on('click', function(){ - var othis = $(this); var filter = check.attr('lay-filter') // 获取过滤器 - var title = ( - othis.next('*[lay-checkbox]')[0] - ? othis.next().html() - : check.attr('title') || '' - ); - var skin = check.attr('lay-skin') || 'primary'; - - // 开关 - title = skin === 'switch' ? title.split('|') : [title]; // 禁用 if(check[0].disabled) return; @@ -864,19 +857,10 @@ layui.define(['lay', 'layer', 'util'], function(exports){ // 半选 if (check[0].indeterminate) { check[0].indeterminate = false; - reElem.find('.'+ CLASS.SUBTRA).removeClass(CLASS.SUBTRA).addClass('layui-icon-ok'); } // 开关 - check[0].checked ? ( - check[0].checked = false, - reElem.removeClass(RE_CLASS[1]), - skin === 'switch' && reElem.children('div').html(title[1]) - ) : ( - check[0].checked = true, - reElem.addClass(RE_CLASS[1]), - skin === 'switch' && reElem.children('div').html(title[0]) - ); + check[0].checked = !check[0].checked // 事件 layui.event.call(check[0], MOD_NAME, RE_CLASS[2]+'('+ filter +')', { @@ -885,6 +869,27 @@ layui.define(['lay', 'layer', 'util'], function(exports){ othis: reElem }); }); + + that.syncAppearanceOnPropChanged(this, 'checked', function(isChecked){ + if(isSwitch){ + var title = (reElem.next('*[lay-checkbox]')[0] + ? reElem.next().html() + : check.attr('title') || '' + ).split('|'); + reElem.children('div').html(isChecked ? title[0] : title[1] || title[0]); + } + reElem.toggleClass(RE_CLASS[1], isChecked); + }); + + if(isPrimary){ + that.syncAppearanceOnPropChanged(this, 'indeterminate', function(isIndeterminate){ + if(isIndeterminate){ + reElem.children('.layui-icon-ok').removeClass('layui-icon-ok').addClass(CLASS.SUBTRA); + }else{ + reElem.children('.'+ CLASS.SUBTRA).removeClass(CLASS.SUBTRA).addClass('layui-icon-ok'); + } + }) + } }; // 遍历复选框 @@ -938,7 +943,7 @@ layui.define(['lay', 'layer', 'util'], function(exports){ '' ].join(''), // 开关 - "switch": '
'+ ((check.checked ? title[0] : title[1]) || '') +'
' + "switch": '
'+ ((check.checked ? title[0] : (title[1] || title[0])) || '') +'
' }; return type[skin] || type['checkbox']; }(), @@ -968,15 +973,10 @@ layui.define(['lay', 'layer', 'util'], function(exports){ if(radio[0].disabled) return; layui.each(sameRadio, function(){ - var next = $(this).next('.' + CLASS); this.checked = false; - next.removeClass(CLASS + 'ed'); - next.children('.layui-icon').removeClass(ANIM + ' ' + ICON[0]).addClass(ICON[1]); }); radio[0].checked = true; - reElem.addClass(CLASS + 'ed'); - reElem.children('.layui-icon').addClass(ANIM + ' ' + ICON[0]); layui.event.call(radio[0], MOD_NAME, 'radio('+ filter +')', { elem: radio[0], @@ -984,6 +984,16 @@ layui.define(['lay', 'layer', 'util'], function(exports){ othis: reElem }); }); + + that.syncAppearanceOnPropChanged(this, 'checked', function(isChecked){ + if(isChecked){ + reElem.addClass(CLASS + 'ed'); + reElem.children('.layui-icon').addClass(ANIM + ' ' + ICON[0]); + }else{ + reElem.removeClass(CLASS + 'ed'); + reElem.children('.layui-icon').removeClass(ANIM + ' ' + ICON[0]).addClass(ICON[1]); + } + }) }; // 初始渲染 @@ -1064,6 +1074,33 @@ layui.define(['lay', 'layer', 'util'], function(exports){ } return that; }; + + /** + * checkbox 和 radio 指定属性变化时自动更新 UI + * @param {HTMLInputElement} elem - HTMLInput 元素 + * @param {'checked' | 'indeterminate'} propName - 属性名 + * @param {(newValue: boolean, oldValue: boolean) => void} handler - 定义如何更新 + * @see https://learn.microsoft.com/zh-cn/previous-versions//ff382725(v=vs.85)?redirectedfrom=MSDN + */ + Form.prototype.syncAppearanceOnPropChanged = function(elem, propName, handler){ + var originProps = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, propName); + + Object.defineProperty(elem, propName, + lay.extend({}, originProps, { + // 此处的 get 是为了兼容 IE<9 + get: function(){ + return originProps.get.call(this); + }, + set: function (newValue) { + var oldValue = this[propName]; + originProps.set.call(this, newValue); + if(oldValue !== newValue){ + handler(newValue, oldValue); + } + } + }) + ); + } /** * 主动触发验证 diff --git a/src/modules/table.js b/src/modules/table.js index 02071f61..b7d101f1 100644 --- a/src/modules/table.js +++ b/src/modules/table.js @@ -147,7 +147,8 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){ var ELEM_GROUP = 'laytable-cell-group'; var ELEM_COL_SPECIAL = 'layui-table-col-special'; var ELEM_TOOL_PANEL = 'layui-table-tool-panel'; - var ELEM_EXPAND = 'layui-table-expanded' + var ELEM_EXPAND = 'layui-table-expanded'; + var DISABLED_TRANSITION = 'layui-table-disabled-transition'; var DATA_MOVE_NAME = 'LAY_TABLE_MOVE_DICT'; @@ -1571,7 +1572,6 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){ checked: checkStatus.isAll, indeterminate: !checkStatus.isAll && checkStatus.data.length // 半选 }); - form.render(checkAllElem); }; // 标记当前活动行背景色 @@ -1593,14 +1593,28 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){ var options = that.config; var isCheckAll = opts.index === 'all'; // 是否操作全部 var isCheckMult = layui.type(opts.index) === 'array'; // 是否操作多个 + var needDisableTransition= isCheckAll || isCheckMult; // 减少回流 + + if(needDisableTransition){ + that.layBox.addClass(DISABLED_TRANSITION); + } + + if(isCheckMult){ + var makeMap = {} + layui.each(opts.index, function(i,v){ + makeMap[v] = true; + }) + opts.index = makeMap; + } // 匹配行元素 + var selector = (isCheckAll || isCheckMult) ? 'tr' : 'tr[data-index="'+ opts.index +'"]'; var tr = function(tr) { return isCheckAll ? tr : tr.filter(isCheckMult ? function() { var dataIndex = $(this).data('index'); - return opts.index.indexOf(dataIndex) !== -1; + return opts.index[dataIndex]; } : '[data-index="'+ opts.index +'"]'); - }(that.layBody.find('tr')); + }(that.layBody.find(selector)); // 默认属性 opts = $.extend({ @@ -1616,14 +1630,18 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){ return opts.type === 'radio' ? true : (existChecked ? opts.checked : !value) }; + var ignoreTrIndex = {}; // 设置选中状态 layui.each(thisData, function(i, item){ // 绕过空项和禁用项 - if(layui.type(item) === 'array' || item[options.disabledName]) return; + if(layui.type(item) === 'array' || item[options.disabledName]){ + ignoreTrIndex[i] = true; + return; + } // 匹配条件 var matched = isCheckAll || ( - isCheckMult ? opts.index.indexOf(i) !== -1 : Number(opts.index) === i + isCheckMult ? opts.index[i] : Number(opts.index) === i ); // 设置匹配项的选中值 @@ -1632,18 +1650,39 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){ var checked = item[options.checkName] = getChecked(item[options.checkName]); // 标记当前行背景色 - var currTr = tr.filter('[data-index="'+ i +'"]'); - currTr[checked ? 'addClass' : 'removeClass'](ELEM_CHECKED); - - // 若为 radio 类型,则取消其他行选中背景色 - if(opts.type === 'radio'){ - currTr.siblings().removeClass(ELEM_CHECKED); + // 此处只更新 radio 和 单个 checkbox + if(!isCheckAll && !isCheckMult){ + var currTr = tr.filter('[data-index="'+ i +'"]'); + currTr[checked ? 'addClass' : 'removeClass'](ELEM_CHECKED); + + // 若为 radio 类型,则取消其他行选中背景色 + if(opts.type === 'radio'){ + currTr.siblings().removeClass(ELEM_CHECKED); + } } } else if(opts.type === 'radio') { delete item[options.checkName]; } }); + if(isCheckAll){ + tr.each(function(i){ + var index = Number(this.getAttribute('data-index')); + if(!ignoreTrIndex[index]){ + var el = $(this); + el.toggleClass(ELEM_CHECKED, getChecked(thisData[index][options.checkName])) + } + }); + }else if(isCheckMult){ + tr.each(function(i){ + var index = Number(this.getAttribute('data-index')); + if(opts.index[index] && !ignoreTrIndex[index]){ + var el = $(this); + el.toggleClass(ELEM_CHECKED, getChecked(thisData[index][options.checkName])) + } + }); + } + // 若存在复选框或单选框,则标注选中状态样式 var checkedElem = tr.find('input[lay-type="'+ ({ radio: 'layTableRadio', @@ -1657,7 +1696,12 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){ : checkedElem ).prop('checked', getChecked(checkedSameElem.prop('checked'))); that.syncCheckAll(); - that.renderForm(opts.type); + + if(needDisableTransition){ + setTimeout(function(){ + that.layBox.removeClass(DISABLED_TRANSITION); + },100) + } }; // 数据排序