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.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,

View File

@ -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){
'<i class="layui-icon '+(skin === 'primary' && !check.checked && othis.get(0).indeterminate ? CLASS.SUBTRA : 'layui-icon-ok')+'"></i>'
].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'];
}(),
@ -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);
}
}
})
);
}
/**
* 主动触发验证

View File

@ -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)
}
};
// 数据排序