fix(table): 修复 2.9.9 table.setRowChecked 的参数中, index 选项为数组时,无法选中的问题 (#1914)

* revert: #1812

REVERT
由于 #1911 以及为了避免潜在的 BUG,恢复 #1812 全部改动和 #1760 中的部分改动。在 treeTable 中修复 #1815

* fix(treeTable): 节点移动后,行索引获取错误

* test(treeTable): 添加 crud 测试

* test(treeTable): 添加异步加载子节点测试

* chore: 添加测试文件来源

* update code
This commit is contained in:
morning-star 2024-05-20 00:26:51 +08:00 committed by GitHub
parent b3f86d206a
commit 037569489f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 538 additions and 21 deletions

View File

@ -0,0 +1,226 @@
<!-- 引用自 https://gitee.com/layui/layui/issues/I6V5VY -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="../src/css/layui.css" href2="//cdn.staticfile.org/layui/2.9.6/css/layui.css"
href3="https://cdn.jsdelivr.net/gh/layui/layui@879a9c629cc832f5b235d138adf164d74c34991b/src/css/layui.css" />
</head>
<body>
<div class="layui-fluid" style="padding: 15px;">
<button class="layui-btn" lay-on="asyncLoad">asyncLoad</button>
<button class="layui-btn" lay-on="flatData">flatData</button>
<table id="demo"></table>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Mock.js/1.0.0/mock-min.js"></script>
<script src="../src/layui.js" src1="//cdn.staticfile.org/layui/2.9.7/layui.js"
src1="https://cdn.jsdelivr.net/gh/layui/layui@879a9c629cc832f5b235d138adf164d74c34991b/src/layui.js"></script>
<script>
layui.use(["treeTable", "util"], function () {
var treeTable = layui.treeTable;
var util = layui.util;
treeTable.set({
});
util.on({
asyncLoad: function () {
treeTable.reload('demo', {
url: "/getDatas",
tree: {
data: {
isSimpleData: false
},
async: {
enable: true,
autoParam: ["parentId=id"]
}
},
}, true)
},
flatData: function () {
treeTable.reload('demo', {
data: testDatas,
url: false,
tree: {
data: {
isSimpleData: true
},
async: {
enable: false,
autoParam: ["parentId=id"]
}
},
}, true)
}
})
// 生成随机ID函数
const createId = (() => {
let nextId = 1;
return () => nextId++;
})();
// 生成节点函数
const createNode = (parentId = null, level = 0) => {
const id = createId();
const children =
level < 3
? Mock.mock({
"array|0-5": [createNode.bind(null, id, level + 1)]
}).array
: [];
return {
id,
name: `User${id}`,
type: Mock.mock("@d6"),
status: Mock.Random.d4(),
score: Mock.Random.d100(),
experience: Mock.Random.integer(1000, 99999),
sex: Mock.Random.cword("男女", 1),
city: Mock.Random.city(),
description: Mock.mock("@cparagraph"),
createTime: Mock.mock("@datetime"),
parentId,
children,
isParent: !!children.length
};
};
// 生成树
const rootNodes = Mock.mock({
"array|10-20": [createNode]
}).array;
// 将树展开
const flattenTree = (nodes, parentId = null) => {
let result = [];
nodes.forEach((node) => {
result.push({
...node,
parentId,
children: undefined
});
if (node.children.length > 0) {
result = result.concat(flattenTree(node.children, node.id));
}
});
return result;
};
const getTreeData = function (nodes) {
let result = [];
nodes.forEach((node) => {
result.push({
...node,
parentId: null,
children: getTreeData(node.children)
});
});
return result;
};
var testDatas = flattenTree(rootNodes);
//var testDatas = getTreeData(rootNodes);
Mock.mock(/getDatas/, "get", (config) => {
console.log(config);
var params = layui.url(config.url);
var search = params.search;
var parentId = search.parentId;
var dataRet = testDatas.filter(function (value, index, array) {
return value.parentId == parentId;
});
var sortColumn = search.sortColumn;
var sortType = search.sortType;
dataRet = layui.sort(dataRet, sortColumn, sortType === "desc");
var curr = search.page;
if (!curr) {
curr = 1;
} else {
curr = parseInt(curr);
curr = curr || 1;
}
var limit = search.limit;
if (!limit) {
limit = dataRet.length;
} else {
limit = parseInt(limit);
limit = limit || dataRet.length;
}
var start = (curr - 1) * limit;
return {
code: 0,
data: dataRet.slice(start, start + limit),
count: dataRet.length
};
});
treeTable.render({
elem: "#demo",
url: "/getDatas",
totalRow: true,
tree: {
async: {
enable: true,
autoParam: ["parentId=id"]
}
},
maxHeight: "full-95",
cols: [
[
{ type: "numbers", fixed: "left" },
{
field: "id",
title: "ID",
width: 145,
sort: true,
fixed: "left",
totalRow: "合计:"
},
{ field: "name", title: "用户名", width: 180, fixed: "left" },
{
field: "experience",
title: "积分",
width: 90,
sort: true,
totalRow: "{{= d.TOTAL_NUMS }}"
},
{ field: "sex", title: "性别", width: 80, sort: true },
{
field: "score",
title: "评分",
width: 80,
sort: true,
totalRow: true
},
{ field: "city", title: "城市", width: 150, templet: "#tpl1" },
{
field: "description",
title: "描述信息",
minWidth: 200,
templet: "#tpl2"
},
{ field: "createTime", title: "创建时间", width: 170 }
]
],
done: function (res, curr, count) {
console.log('渲染完成', res, curr, count, this);
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,285 @@
<!-- 引用自 https://github.com/layui/layui/issues/1815 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="../src/css/layui.css" href2="//cdn.staticfile.org/layui/2.9.6/css/layui.css" />
</head>
<body>
<table class="layui-table" id="MianTable" lay-filter="MianTable" lay-data="{id: 'MianTable'}"></table>
<script type="text/html" id="toolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="AddRowButton">
新增主件
</button>
<button class="layui-btn layui-btn-sm" lay-event="InitTableButton">
初始化数据
</button>
<button class="layui-btn layui-btn-sm" lay-event="SubmitButton">
保存
</button>
<button class="layui-btn layui-btn-sm" lay-event="expandAll-true">
expandAll-true
</button>
<button class="layui-btn layui-btn-sm" lay-event="expandAll-false">
expandAll-false
</button>
</div>
</script>
<script type="text/html" id="TableItemTools">
<div class="layui-btn-container">
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="addChild"
>新增子件</a
>
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="delChild"
>删除</a
>
</div>
</script>
<script type="text/html" id="TableItemTools">
<div class="layui-btn-container">
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="addChild"
>新增子件</a
>
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="delChild"
>删除</a
>
</div>
</script>
<script type="text/html" id="sexTpl">
<select name="select-sex" class="layui-border select-sex" lay-ignore lay-filter="sex_{{d.rowid}}" id="sex_{{d.rowid}}">
<option value="">请选择</option>
<option value="男" {{d.sex=="男"?"selected":""}}></option>
<option value="女" {{d.sex=="女"?"selected":""}}></option>
</select>
</script>
<script type="text/html" id="provinceTpl">
<select name="select-province" class="layui-border select-province" lay-ignore lay-filter="province_{{d.rowid}}" id="province_{{d.rowid}}">
<option value="">请选择</option>
{{# layui.each(d.provinceList, function(i, v){ }}
<option value="{{v.id}}" {{d.province==v.id?"selected":""}}>{{v.name}}</option>
{{# }); }}
</select>
</script>
<script type="text/html" id="cityTpl">
<select name="select-city" class="layui-border select-city" lay-ignore lay-filter="city_{{d.rowid}}" id="city_{{d.rowid}}">
<option value="">请选择</option>
{{# layui.each(d.cityList, function(i, v){ }}
<option value="{{v.id}}" {{d.city==v.id?"selected":""}}>{{v.name}}</option>
{{# }); }}
</select>
</script>
<script type="text/html" id="districtTpl">
<select name="select-district" class="layui-border select-district" lay-ignore lay-filter="district_{{d.rowid}}" id="district_{{d.rowid}}">
<option value="">请选择</option>
{{# layui.each(d.districtList, function(i, v){ }}
<option value="{{v.id}}" {{d.district==v.id?"selected":""}}>{{v.name}}</option>
{{# }); }}
</select>
</script>
<script src="../src/layui.js" src1="//cdn.staticfile.org/layui/2.9.9/layui.js"></script>
<script>
layui.use(["jquery", "treeTable", "form"], function () {
var $ = layui.jquery;
var table = layui.table;
var treeTable = layui.treeTable;
var form = layui.form;
var provinceList = [
{ id: "01", name: "湖北省" },
{ id: "02", name: "湖南省" },
{ id: "03", name: "广东省" },
];
var cityList = [
{ id: "01.01", name: "武汉市" },
{ id: "01.02", name: "黄石市" },
{ id: "02.01", name: "长沙市" },
{ id: "02.02", name: "株洲市" },
{ id: "03.01", name: "广州市" },
];
var districtList = [
{ id: "01.01.01", name: "江岸区" },
{ id: "01.01.02", name: "江汉区" },
{ id: "01.01.03", name: "武昌区" },
{ id: "01.02.01", name: "下陆区" },
{ id: "01.02.02", name: "铁山区" },
{ id: "02.01.01", name: "天心区" },
{ id: "02.01.02", name: "芙蓉区" },
{ id: "02.01.03", name: "浏阳市" },
{ id: "02.02.01", name: "天元区" },
{ id: "02.02.02", name: "石峰区" },
{ id: "03.01.01", name: "越秀区" },
{ id: "03.01.02", name: "海珠区" },
];
var isHtmlIcon = false;
var genIcon = function (str, isHtml) {
return isHtml ? normalizedIcon(str) : str
}
var icons = {
flexIconClose: genIcon('layui-icon layui-icon-addition', isHtmlIcon),
flexIconOpen: genIcon('layui-icon layui-icon-subtraction', isHtmlIcon),
iconClose: genIcon('layui-icon layui-icon-add-circle', isHtmlIcon),
iconOpen: genIcon('layui-icon layui-icon-reduce-circle', isHtmlIcon),
iconLeaf: genIcon('layui-icon layui-icon-snowflake', isHtmlIcon),
icon: genIcon('layui-icon layui-icon-addition', isHtmlIcon),
}
treeTable.render({
elem: "#MianTable",
toolbar: "#toolbar",
limit: 100,
tree: {
// customName: {
// id: "id",
// name: "id",
// },
view: {
// showFlexIconIfNotParent: true,
// flexIconClose: icons.flexIconClose,
// flexIconOpen: icons.flexIconOpen,
// iconClose: icons.iconClose,
// iconOpen: icons.iconOpen,
// iconLeaf: icons.iconLeaf,
// icon: icons.icon
},
},
cols: [
[
//标题栏
{ checkbox: true, fixed: "left" },
{ field: "id", title: "ID", width: 150 },
{
title: "操作",
width: 150,
align: "center",
toolbar: "#TableItemTools",
},
{ field: "name", title: "用户名", width: 180, edit: "text" },
{ field: "sex", title: "性别", templet: "#sexTpl" },
{ field: "province", title: "省", templet: "#provinceTpl" },
{ field: "city", title: "市", templet: "#cityTpl" },
{ field: "district", title: "区", templet: "#districtTpl" },
],
],
data: [],
done: function () {
var options = this;
// 获取当前行数据
table.getRowData = function (tableId, elem) {
var index = $(elem).closest("tr").data("index");
return table.cache[tableId][index] || {};
};
var tableViewElem = this.elem.next();
tableViewElem.off("change.tbSelect");
tableViewElem.on("change.tbSelect", ".select-sex", function () {
var value = this.value; // 获取选中项 value
var data = table.getRowData(options.id, this);
data.sex = value;
var DATA_INDEX = data.LAY_DATA_INDEX; //此处需要内部字段更新行数据
treeTable.updateNode("MianTable", DATA_INDEX, {
province: "01",
});
});
},
});
treeTable.on("toolbar(MianTable)", function (obj) {
var id = obj.config.id;
var checkStatus = treeTable.checkStatus(id);
switch (obj.event) {
case "AddRowButton":
treeTable.addNodes(id, {
parentIndex: null,
index: -1,
//index: 0,
data: {
id: Date.now(),
provinceList: $.extend(true, [], provinceList),
// icon: 'layui-icon layui-icon-github'
},
});
console.log("add", table.cache["MianTable"]);
break;
case "InitTableButton":
treeTable.reloadData("MianTable", {
data: [
{
name: 1,
provinceList: provinceList,
children: [{ name: 2, provinceList: provinceList }],
},
{ name: 3, provinceList: provinceList },
],
});
break;
case "SubmitButton":
console.log(treeTable.getData("MianTable"));
case 'expandAll-true':
treeTable.expandAll('MianTable', true);
break;
case 'expandAll-false':
treeTable.expandAll('MianTable', false);
break;
break;
default:
}
});
treeTable.on("tool(MianTable)", function (obj) {
var data = obj.data;
var id = obj.config.id;
var trElem = obj.tr;
switch (obj.event) {
case "addChild":
let json = {
id: Date.now(),
provinceList: $.extend(true, [], provinceList),
};
treeTable.addNodes(id, {
parentIndex: data["LAY_DATA_INDEX"],
index: -1,
data: json,
});
console.log("addChild", table.cache["MianTable"]);
break;
case "delChild":
// obj.del();
// treeTable.removeNode(id, data["LAY_DATA_INDEX"]);
treeTable.removeNode(id, trElem.attr("data-index"));
console.log("del", table.cache["MianTable"]);
break;
default:
}
});
function normalizedIcon(iconStr) {
return iconStr
? /<[^>]+?>/.test(iconStr) ? iconStr : '<i class="' + iconStr + '"></i>'
: ''
}
});
</script>
</body>
</html>

View File

@ -1590,7 +1590,7 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
// 匹配行元素
var tr = function(tr) {
return isCheckAll ? tr : tr.filter(isCheckMult ? function() {
var dataIndex = $(this).attr('data-index');
var dataIndex = $(this).data('index');
return opts.index.indexOf(dataIndex) !== -1;
} : '[data-index="'+ opts.index +'"]');
}(that.layBody.find('tr'));
@ -2292,7 +2292,7 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
//数据行中的事件返回的公共对象成员
var commonMember = that.commonMember = function(sets){
var othis = $(this);
var index = othis.parents('tr').eq(0).attr('data-index');
var index = othis.parents('tr').eq(0).data('index');
var tr = that.layBody.find('tr[data-index="'+ index +'"]');
var data = table.cache[that.key] || [];
@ -2338,7 +2338,7 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
var td = othis.closest('td');
var checkbox = othis.prev();
var children = that.layBody.find('input[name="layTableCheckbox"]');
var index = checkbox.parents('tr').eq(0).attr('data-index');
var index = checkbox.parents('tr').eq(0).data('index');
var checked = checkbox[0].checked;
var isAll = checkbox.attr('lay-filter') === 'layTableAllChoose';
@ -2378,7 +2378,7 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
var td = othis.closest('td');
var radio = othis.prev();
var checked = radio[0].checked;
var index = radio.parents('tr').eq(0).attr('data-index');
var index = radio.parents('tr').eq(0).data('index');
layui.stope(e);
if(radio[0].disabled) return false;
@ -2451,7 +2451,7 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
var field = othis.data('field');
var key = othis.data('key');
var col = that.col(key);
var index = othis.closest('tr').attr('data-index');
var index = othis.closest('tr').data('index');
var data = table.cache[that.key][index];
var elemCell = othis.children(ELEM_CELL);
@ -2485,7 +2485,7 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
var td = othis.parent();
var value = this.value;
var field = othis.parent().data('field');
var index = othis.closest('tr').attr('data-index');
var index = othis.closest('tr').data('index');
var data = table.cache[that.key][index];
//事件回调的参数对象
@ -2564,7 +2564,7 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
var td = othis.parent();
var key = td.data('key');
var col = that.col(key);
var index = td.parent().attr('data-index');
var index = td.parent().data('index');
var elemCell = td.children(ELEM_CELL);
var ELEM_CELL_C = 'layui-table-cell-c';
var elemCellClose = $('<i class="layui-icon layui-icon-up '+ ELEM_CELL_C +'">');
@ -2661,7 +2661,7 @@ layui.define(['lay', 'laytpl', 'laypage', 'form', 'util'], function(exports){
var toolFn = function(type){
var othis = $(this);
var td = othis.closest('td');
var index = othis.parents('tr').eq(0).attr('data-index');
var index = othis.parents('tr').eq(0).data('index');
// 标记当前活动行
that.setRowActive(index);

View File

@ -759,17 +759,19 @@ layui.define(['table'], function (exports) {
'data-index': childItem[LAY_DATA_INDEX],
'lay-data-index': childItem[LAY_DATA_INDEX],
'data-level': dataLevelNew
})
}).data('index', childItem[LAY_DATA_INDEX]);
str2Obj.trs_fixed.eq(childIndex).attr({
'data-index': childItem[LAY_DATA_INDEX],
'lay-data-index': childItem[LAY_DATA_INDEX],
'data-level': dataLevelNew
})
}).data('index', childItem[LAY_DATA_INDEX]);
str2Obj.trs_fixed_r.eq(childIndex).attr({
'data-index': childItem[LAY_DATA_INDEX],
'lay-data-index': childItem[LAY_DATA_INDEX],
'data-level': dataLevelNew
})
}).data('index', childItem[LAY_DATA_INDEX]);
})
tableViewElem.find(ELEM_MAIN).find('tbody tr[lay-data-index="' + dataIndex + '"]').after(str2Obj.trs);
@ -972,9 +974,9 @@ layui.define(['table'], function (exports) {
'lay-data-index': dataItem[LAY_DATA_INDEX],
'data-level': dataLevel
};
trAllObj.trs.eq(dataIndex).attr(props)
trAllObj.trs_fixed.eq(dataIndex).attr(props)
trAllObj.trs_fixed_r.eq(dataIndex).attr(props)
trAllObj.trs.eq(dataIndex).attr(props).data('index', dataItem[LAY_DATA_INDEX]);
trAllObj.trs_fixed.eq(dataIndex).attr(props).data('index', dataItem[LAY_DATA_INDEX]);
trAllObj.trs_fixed_r.eq(dataIndex).attr(props).data('index', dataItem[LAY_DATA_INDEX]);
})
layui.each(['main', 'fixed-l', 'fixed-r'], function (i, item) {
tableView.find('.layui-table-' + item + ' tbody').html(trAllObj[['trs', 'trs_fixed', 'trs_fixed_r'][i]]);
@ -1323,7 +1325,7 @@ layui.define(['table'], function (exports) {
'data-index': trIndex,
'lay-data-index': index,
'data-level': trLevel
}));
}).data('index', trIndex));
});
that.renderTreeTable(tableView.find('tr[lay-data-index="' + index + '"]'), trLevel);
}
@ -1378,13 +1380,15 @@ layui.define(['table'], function (exports) {
tableView.find('tr[lay-data-index="' + item3[LAY_DATA_INDEX_HISTORY] + '"]').attr({
'data-index': item3[LAY_DATA_INDEX],
'lay-data-index': item3[LAY_DATA_INDEX],
});
}).data('index', item3[LAY_DATA_INDEX]);
// item3[LAY_DATA_INDEX_HISTORY] = item3[LAY_DATA_INDEX]
}
});
// 重新更新顶层节点的data-index;
layui.each(tableCache, function (i4, item4) {
tableView.find('tr[data-level="0"][lay-data-index="' + item4[LAY_DATA_INDEX] + '"]').attr('data-index', i4);
tableView.find('tr[data-level="0"][lay-data-index="' + item4[LAY_DATA_INDEX] + '"]')
.attr('data-index', i4)
.data('index', i4);
})
options.hasNumberCol && formatNumber(that);
// 更新父节点状态
@ -1478,9 +1482,9 @@ layui.define(['table'], function (exports) {
'lay-data-index': newNodeItem[LAY_DATA_INDEX],
'data-level': '0'
};
newNodesHtmlObj.trs.eq(newNodeIndex).attr(attrs)
newNodesHtmlObj.trs_fixed.eq(newNodeIndex).attr(attrs)
newNodesHtmlObj.trs_fixed_r.eq(newNodeIndex).attr(attrs)
newNodesHtmlObj.trs.eq(newNodeIndex).attr(attrs).data('index', newNodeItem[LAY_DATA_INDEX]);
newNodesHtmlObj.trs_fixed.eq(newNodeIndex).attr(attrs).data('index', newNodeItem[LAY_DATA_INDEX]);
newNodesHtmlObj.trs_fixed_r.eq(newNodeIndex).attr(attrs).data('index', newNodeItem[LAY_DATA_INDEX]);
})
var trIndexPrev = parseInt(newNodes[0][LAY_DATA_INDEX]) - 1;
var tableViewElemMAIN = tableViewElem.find(ELEM_MAIN);
@ -1515,7 +1519,9 @@ layui.define(['table'], function (exports) {
// 重新更新顶层节点的data-index;
layui.each(table.cache[id], function (i4, item4) {
tableViewElem.find('tr[data-level="0"][lay-data-index="' + item4[LAY_DATA_INDEX] + '"]').attr('data-index', i4);
tableViewElem.find('tr[data-level="0"][lay-data-index="' + item4[LAY_DATA_INDEX] + '"]')
.attr('data-index', i4)
.data('index', i4);
})
that.renderTreeTable(tableViewElem.find(newNodes.map(function (value, index, array) {