perf(table): 优化表格数据量较大时行选中性能 (#2004)

* perf(table): 优化表格数据量较大时行选中性能

* chore: 改进选择器

* refactor: 简化代码

* feat: 多选时禁用过渡效果以提高性能

* update code

* update code
pull/2021/head
morning-star 2024-06-18 09:34:41 +08:00 committed by GitHub
parent dfe67f1813
commit 249d6db78e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 123 additions and 39 deletions

View File

@ -1028,6 +1028,9 @@ hr.layui-border-black{border-width: 0 0 1px;}
.layui-table-checked{background-color: #dbfbf0;} .layui-table-checked{background-color: #dbfbf0;}
.layui-table-checked.layui-table-hover, .layui-table-checked.layui-table-hover,
.layui-table-checked.layui-table-click{background-color: #abf8dd;} .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, .layui-table th,

View File

@ -843,20 +843,13 @@ layui.define(['lay', 'layer', 'util'], function(exports){
// 事件 // 事件
var events = function(reElem, RE_CLASS){ var events = function(reElem, RE_CLASS){
var check = $(this); var check = $(this);
var skin = check.attr('lay-skin') || 'primary';
var isSwitch = skin === 'switch';
var isPrimary = skin === 'primary';
// 勾选 // 勾选
reElem.on('click', function(){ reElem.on('click', function(){
var othis = $(this);
var filter = check.attr('lay-filter') // 获取过滤器 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; if(check[0].disabled) return;
@ -864,19 +857,10 @@ layui.define(['lay', 'layer', 'util'], function(exports){
// 半选 // 半选
if (check[0].indeterminate) { if (check[0].indeterminate) {
check[0].indeterminate = false; check[0].indeterminate = false;
reElem.find('.'+ CLASS.SUBTRA).removeClass(CLASS.SUBTRA).addClass('layui-icon-ok');
} }
// 开关 // 开关
check[0].checked ? ( check[0].checked = !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])
);
// 事件 // 事件
layui.event.call(check[0], MOD_NAME, RE_CLASS[2]+'('+ filter +')', { layui.event.call(check[0], MOD_NAME, RE_CLASS[2]+'('+ filter +')', {
@ -885,6 +869,27 @@ layui.define(['lay', 'layer', 'util'], function(exports){
othis: reElem 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){
'<i class="layui-icon '+(skin === 'primary' && !check.checked && othis.get(0).indeterminate ? CLASS.SUBTRA : 'layui-icon-ok')+'"></i>' '<i class="layui-icon '+(skin === 'primary' && !check.checked && othis.get(0).indeterminate ? CLASS.SUBTRA : 'layui-icon-ok')+'"></i>'
].join(''), ].join(''),
// 开关 // 开关
"switch": '<div>'+ ((check.checked ? title[0] : title[1]) || '') +'</div><i></i>' "switch": '<div>'+ ((check.checked ? title[0] : (title[1] || title[0])) || '') +'</div><i></i>'
}; };
return type[skin] || type['checkbox']; return type[skin] || type['checkbox'];
}(), }(),
@ -968,15 +973,10 @@ layui.define(['lay', 'layer', 'util'], function(exports){
if(radio[0].disabled) return; if(radio[0].disabled) return;
layui.each(sameRadio, function(){ layui.each(sameRadio, function(){
var next = $(this).next('.' + CLASS);
this.checked = false; this.checked = false;
next.removeClass(CLASS + 'ed');
next.children('.layui-icon').removeClass(ANIM + ' ' + ICON[0]).addClass(ICON[1]);
}); });
radio[0].checked = true; 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 +')', { layui.event.call(radio[0], MOD_NAME, 'radio('+ filter +')', {
elem: radio[0], elem: radio[0],
@ -984,6 +984,16 @@ layui.define(['lay', 'layer', 'util'], function(exports){
othis: reElem 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; 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);
}
}
})
);
}
/** /**
* 主动触发验证 * 主动触发验证

View File

@ -147,7 +147,8 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
var ELEM_GROUP = 'laytable-cell-group'; var ELEM_GROUP = 'laytable-cell-group';
var ELEM_COL_SPECIAL = 'layui-table-col-special'; var ELEM_COL_SPECIAL = 'layui-table-col-special';
var ELEM_TOOL_PANEL = 'layui-table-tool-panel'; 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'; var DATA_MOVE_NAME = 'LAY_TABLE_MOVE_DICT';
@ -1571,7 +1572,6 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
checked: checkStatus.isAll, checked: checkStatus.isAll,
indeterminate: !checkStatus.isAll && checkStatus.data.length // 半选 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 options = that.config;
var isCheckAll = opts.index === 'all'; // 是否操作全部 var isCheckAll = opts.index === 'all'; // 是否操作全部
var isCheckMult = layui.type(opts.index) === 'array'; // 是否操作多个 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) { var tr = function(tr) {
return isCheckAll ? tr : tr.filter(isCheckMult ? function() { return isCheckAll ? tr : tr.filter(isCheckMult ? function() {
var dataIndex = $(this).data('index'); var dataIndex = $(this).data('index');
return opts.index.indexOf(dataIndex) !== -1; return opts.index[dataIndex];
} : '[data-index="'+ opts.index +'"]'); } : '[data-index="'+ opts.index +'"]');
}(that.layBody.find('tr')); }(that.layBody.find(selector));
// 默认属性 // 默认属性
opts = $.extend({ opts = $.extend({
@ -1616,14 +1630,18 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
return opts.type === 'radio' ? true : (existChecked ? opts.checked : !value) return opts.type === 'radio' ? true : (existChecked ? opts.checked : !value)
}; };
var ignoreTrIndex = {};
// 设置选中状态 // 设置选中状态
layui.each(thisData, function(i, item){ 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 || ( 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 checked = item[options.checkName] = getChecked(item[options.checkName]);
// 标记当前行背景色 // 标记当前行背景色
var currTr = tr.filter('[data-index="'+ i +'"]'); // 此处只更新 radio 和 单个 checkbox
currTr[checked ? 'addClass' : 'removeClass'](ELEM_CHECKED); if(!isCheckAll && !isCheckMult){
var currTr = tr.filter('[data-index="'+ i +'"]');
// 若为 radio 类型,则取消其他行选中背景色 currTr[checked ? 'addClass' : 'removeClass'](ELEM_CHECKED);
if(opts.type === 'radio'){
currTr.siblings().removeClass(ELEM_CHECKED); // 若为 radio 类型,则取消其他行选中背景色
if(opts.type === 'radio'){
currTr.siblings().removeClass(ELEM_CHECKED);
}
} }
} else if(opts.type === 'radio') { } else if(opts.type === 'radio') {
delete item[options.checkName]; 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="'+ ({ var checkedElem = tr.find('input[lay-type="'+ ({
radio: 'layTableRadio', radio: 'layTableRadio',
@ -1657,7 +1696,12 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
: checkedElem ).prop('checked', getChecked(checkedSameElem.prop('checked'))); : checkedElem ).prop('checked', getChecked(checkedSameElem.prop('checked')));
that.syncCheckAll(); that.syncCheckAll();
that.renderForm(opts.type);
if(needDisableTransition){
setTimeout(function(){
that.layBox.removeClass(DISABLED_TRANSITION);
},100)
}
}; };
// 数据排序 // 数据排序