如何实现文件夹优先排序的层级嵌套UL列表?
问题描述
从层级分类数组生成嵌套UL列表,子分类关联父分类ID,要求展示规则为:有子项的“文件夹”类列表项排在前面,无子项的“文件”类列表项在后。当前实现会按数组顺序生成列表(实际表现为字母排序),不符合需求,例如data-id=7的catA_2被夹在两个文件夹UL之间,而非排在所有文件夹之后。
期望的HTML结构
<ul id='myList' class="folder"> <li data-id=0> <ul class="folder"> <li data-id=1>catA <ul class="folder"> <li data-id=3>catA_1 <ul> <li data-id=4>catA_1_1</li> </ul> <ul> <li data-id=5>catA_1_2</li> </ul> <ul> <li data-id=6>catA_1_3</li> </ul> </li> </ul> <ul class="folder"> <li data-id=8>catA_3 <ul> <li data-id=9>catA_3_1</li> </ul> <ul> <li data-id=10>catA_3_2</li> </ul> <ul class="folder"> <li data-id=11>catA_3_3 <ul> <li data-id=12>catA_3_3_1</li> </ul> <ul><li data-id=13>catA_3_3_2</li></ul> </li> </ul> </li> </ul> <ul> <li data-id=7>catA_2</li> <!-- 这两项(data-id=7 & 14)排在文件夹之后,因为它们没有子项 --> </ul> <ul> <li data-id=14>catA_4</li> </ul> </li> </ul> <ul> <li data-id=2>catB</li> <!-- catB同理 --> </ul> </li> </ul>
当前实现代码
JavaScript
const src = [[1,"catA",0],[2,"catB",0],[3,"catA_1",1],[4,"catA_1_1",3],[5,"catA_1_2",3],[6,"catA_1_3",3],[7,"catA_2",1],[8,"catA_3",1],[9,"catA_3_1",8],[10,"catA_3_2",8],[11,"catA_3_3",8],[12,"catA_3_3_1",11],[13,"catA_3_3_2",11],[14,"catA_4",1],[15,"catB_1",2],[16,"catB_1_1",15],[17,"catB_1_2",16],[18,"catB_1_3",16],[19,"catB_2",15],[20,"catB_3",15],[21,"catB_3_1",20],[22,"catB_3_2",20],[23,"catB_3_3",20],[24,"catB_3_3_1",23],[25,"catB_3_3_2",23],[26,"catB_4",15]]; function tree(src, parent = 0) { const el = document.getElementById("myList").querySelector("li[data-id='" + parent + "']"); if (!el) return; for (var i = 0; i < src.length; i++) { if (src[i][2] === parent) { const new_parent = src[i][0]; el.insertAdjacentHTML("beforeend", "<ul><li data-id='" + new_parent + "'>" + src[i][1] + "</li></ul>"); el.parentElement.classList.add("folder"); tree(src, new_parent); } } } tree(src)
HTML
<ul id='myList'> <li data-id=0></li> </ul>
解决方案
核心思路是先筛选并处理有子项的分类(文件夹),再处理无子项的分类(文件),具体实现如下:
- 提前建立父ID到子分类的映射,快速判断某个分类是否有子项;
- 在递归函数中拆分当前父分类的子项为两组,优先处理文件夹类项,再处理文件类项。
修改后的JavaScript代码:
const src = [[1,"catA",0],[2,"catB",0],[3,"catA_1",1],[4,"catA_1_1",3],[5,"catA_1_2",3],[6,"catA_1_3",3],[7,"catA_2",1],[8,"catA_3",1],[9,"catA_3_1",8],[10,"catA_3_2",8],[11,"catA_3_3",8],[12,"catA_3_3_1",11],[13,"catA_3_3_2",11],[14,"catA_4",1],[15,"catB_1",2],[16,"catB_1_1",15],[17,"catB_1_2",16],[18,"catB_1_3",16],[19,"catB_2",15],[20,"catB_3",15],[21,"catB_3_1",20],[22,"catB_3_2",20],[23,"catB_3_3",20],[24,"catB_3_3_1",23],[25,"catB_3_3_2",23],[26,"catB_4",15]]; // 建立父ID到子分类的映射,用于快速判断是否有子项 const parentMap = src.reduce((map, item) => { const parentId = item[2]; if (!map[parentId]) map[parentId] = []; map[parentId].push(item); return map; }, {}); // 判断某个分类是否有子项 function hasChildren(id) { return parentMap[id] && parentMap[id].length > 0; } function tree(src, parent = 0) { const el = document.getElementById("myList").querySelector("li[data-id='" + parent + "']"); if (!el) return; // 获取当前父分类下的所有子项 const children = parentMap[parent] || []; // 分成两组:有子项的(文件夹)和无子项的(文件) const folderItems = children.filter(item => hasChildren(item[0])); const fileItems = children.filter(item => !hasChildren(item[0])); // 先处理文件夹 folderItems.forEach(item => { const new_parent = item[0]; const ul = document.createElement('ul'); ul.classList.add('folder'); ul.innerHTML = `<li data-id='${new_parent}'>${item[1]}</li>`; el.appendChild(ul); el.parentElement.classList.add("folder"); tree(src, new_parent); }); // 再处理文件 fileItems.forEach(item => { const new_parent = item[0]; const ul = document.createElement('ul'); ul.innerHTML = `<li data-id='${new_parent}'>${item[1]}</li>`; el.appendChild(ul); }); } tree(src)
内容的提问来源于stack exchange,提问作者midget
相关产品推荐
相关产品推荐

