mirror of https://github.com/layui/layui
perf(table): 优化表格数据量较大时行选中性能 (#2004)
* perf(table): 优化表格数据量较大时行选中性能 * chore: 改进选择器 * refactor: 简化代码 * feat: 多选时禁用过渡效果以提高性能 * update code * update codepull/2021/head
parent
dfe67f1813
commit
249d6db78e
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主动触发验证
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
};
|
||||
|
||||
// 数据排序
|
||||
|
|
Loading…
Reference in New Issue