效果(默认全部展开):
<!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="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
/>
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<style>
/* 树状菜单 */
#ulShow {
height: 100%;
}
#ulShow > .list-group {
color: #fff;
margin: 0;
height: 40px;
line-height: 40px;
margin: 0;
list-style: none;
}
/* 一级菜单 */
#ulShow > .list-group > .list-group {
margin: 0;
padding-left: 20px;
background-color: #2d363f;
}
#ulShow > .list-group > .collapse > .list-group {
margin: 0;
}
/* 动画进行时的样式设置,要和动画结束时的样式保持一致 */
#ulShow > .list-group > .collapse > .list-group > .list-group-item,
#ulShow > .list-group .collapsing .list-group-item {
display: flex;
align-items: center;
padding: 0;
padding-left: 40px;
background-color: #1e2d3b;
border: 0;
}
#ulShow > .list-group > .collapse > .list-group > .list-group-item a,
#ulShow > .list-group .collapsing .list-group-item a {
text-decoration: none;
color: #fff;
}
#ulShow > .list-group > .collapse > .list-group > .list-group-item:hover,
#ulShow > .list-group .collapsing .list-group-item:hover {
background: #2d363f;
cursor: pointer;
}
#ulShow > .list-group > .collapse > .list-group > .list-group-item:hover a,
#ulShow > .list-group .collapsing .list-group-item:hover a {
color: lightblue;
}
#ulShow > .list-group .collapsing {
transition: height 0.5s ease;
/* 修改为你想要的持续时间和缓动函数 */
/* 动画加载时会有一个向下的高度占位 */
/* background-color: red; */
}
</style>
<body>
<div id="ulShow"></div>
<script>
const menuArray = [
{ id: 1, pageName: "用户管理", pid: 0 },
{ id: 2, pageName: "图书管理", pid: 0 },
{ id: 3, pageName: "销售管理", pid: 0 },
{ id: 4, pageName: "借阅管理", pid: 0 },
{ id: 5, pageName: "系统设置", pid: 0 },
{ id: 6, pageName: "用户类型管理", pid: 1 },
{ id: 7, pageName: "用户信息管理", pid: 1 },
{ id: 8, pageName: "新增用户", pid: 1 },
{ id: 9, pageName: "图书类型管理", pid: 2 },
{ id: 10, pageName: "图书管理", pid: 2 },
{ id: 11, pageName: "入库管理", pid: 3 },
{ id: 12, pageName: "出库管理", pid: 3 },
{ id: 13, pageName: "借书管理", pid: 4 },
{ id: 14, pageName: "还书管理", pid: 4 },
{ id: 15, pageName: "退出", pid: 5 },
];
function showMenu(menuArray) {
// 创建一个映射对象,用于存储菜单项及其子菜单项,map是计算结束后返回的值,是下一次调用回调时的第一个参数;item是当前值
const itemMap = menuArray.reduce((map, item) => {
// 将每个菜单项添加到映射中,并初始化其子菜单项数组
/*
在这段代码中,...item 用于对象展开(Object Spreading)是有效的,因为它正确地复制了 item 对象的所有可枚举属性到新创建的对象中。
对象展开(Object Spreading)与浅拷贝(Shallow Copy)是相关的概念。在这段代码中,...item 进行的操作实际上是一个浅拷贝。
浅拷贝是指创建一个新对象,并将原始对象的所有非静态属性的值复制到新对象中。如果这些属性值是基本类型(如数字、字符串、布尔值),则直接复制值;如果属性值是引用类型(如对象、数组),则复制的是内存中的地址,而不是实际的对象或数组。这意味着新对象和原始对象仍然共享对这些引用类型属性的引用。
在对象展开中,...item 创建了一个新对象,并将 item 的所有可枚举属性复制到新对象中。由于这些属性是按值复制的,如果属性是基本类型,它们将被直接复制;如果属性是引用类型(例如,另一个对象或数组),则复制的是对这个引用类型值的引用,而不是实际的对象或数组本身。因此,对象展开在这种情况下执行的是一个浅拷贝。
这段代码中的 subItems: [] 创建了一个新的空数组,并将其赋值给新对象的 subItems 属性。这是对新数组的一个独立引用,它不会影响原始 item 对象中的任何属性或数组。
因此,当你在这段代码中使用 ...item 时,你正在创建一个新对象,该对象包含原始 item 对象的所有属性的浅拷贝,并且添加了一个新的 subItems 属性,该属性指向一个全新的数组。这样,每个 item 在 itemMap 中都有一个独立的、不与其他 item 共享的 subItems 数组。
*/
map[item.id] = { ...item, subItems: [] };
// 如果菜单项没有父级(顶级菜单项),则标记为顶级
if (item.pid === 0) {
map[item.id].topLevel = true;
} else {
// 如果菜单项有父级,则将其添加到父级菜单项的子菜单项数组中
const parentId = item.pid;
if (map[parentId]) {
map[parentId].subItems.push(item);
}
}
return map;
}, {});
console.log(itemMap, "itemMap");
// 从映射中提取所有顶级菜单项,返回一个新数组
const topLevelItems = Object.values(itemMap).filter(
(item) => item.topLevel
);
// 构建顶级菜单项的 HTML 内容
const listGroupContent = topLevelItems
.map((topItem) => {
let subItemsHtml = "";
// 如果顶级菜单项有子菜单项,则构建子菜单项的 HTML 内容
if (topItem.subItems.length > 0) {
// 为子菜单项创建一个唯一的 collapse ID
const collapseId = `collapse-${topItem.id}`;
// 构建子菜单项的列表组,并将其包装在一个 collapse 元素中
subItemsHtml =
`<div class="collapse" id="${collapseId}">` +
`<ul class="list-group list-group-flush">` +
topItem.subItems
.map(
(subItem) =>
// 将子菜单项转换为 a 标签,并设置 href 属性
`<li class="list-group-item subItem" οnclick="handleSubItem('${subItem.pageName}')"><a href="#">${subItem.pageName}</a></li>`
)
.join("") +
`</ul>` +
`</div>`;
// 创建折叠触发器的按钮,并设置 data-target 属性以指向相应的 collapse 元素
const triggerButton = `<div class="list-group" data-toggle="collapse" data-target="#${collapseId}" aria-expanded="false" aria-controls="${collapseId}">${topItem.pageName}</div>`;
// 返回顶级菜单项的 HTML 内容,包括折叠触发器和子菜单项
return triggerButton + subItemsHtml;
} else {
// 如果顶级菜单项没有子菜单项,则只创建一个 a 标签
return `<li class="list-group-item"><a href="#">${topItem.pageName}</a></li>`;
}
})
.join("");
// 清空 ulShow 元素的内容,并添加新构建的列表组内容
$("#ulShow")
.empty()
.append(`<ul class="list-group">${listGroupContent}</ul>`);
// 初始化所有 collapse 元素以启用折叠功能
$(".collapse").collapse();
}
function handleSubItem(pageName) {
console.log(pageName, "pageName");
}
$(".subItem").on("click", function (pageName) {
handleSubItem(pageName);
});
window.onload = function () {
showMenu(menuArray);
};
</script>
</body>
</html>