設計思路:
要生成菜單的源數據往往是一個樹形數據結構(若不是也可以轉換成樹形結構),(那我們一起寫博客吧)因為源數據結構和目標菜單結構都為樹形結構,所以其實我們要做的就是數據結構的轉譯,即將js樹形數據轉換為 ul, li 拼接成的樹形菜單。在這里我們通過樹的深度優先遍歷方式來完成這次轉義操作。
結構(轉義)映射關系說明:
迭代樹形數據時,樹形結構數據的每個同層級別的每條數據轉換成一個 LI標簽包裹的菜單項(添加class為menu-item標識),當遇到childrens項時,當前項數據用 LI 標簽包裹(添加class為menu標識),同時在該LI標簽后面添加UL標簽作為下一級菜單包裹項,迭代childrens作為上述UL標簽的子菜單項,對childrens子元素的操作同上述描述的操作。
簡單總結就是樹的每條數據都轉換為LI標簽內容,每個childrens都轉換為UL標簽。
以下我們來完成對上述描述代碼實現
1,js樹形數據

1 var menuList = [ 2 { 3 name: '音程比較', 4 childrens: [ 5 { 6 name: '比較純音程', 7 }, 8 { 9 name: '比較不完全和協音程', 10 }, 11 { 12 name: '比較不協和音程', 13 childrens: [ 14 { 15 name: '大二度&小七度-上行', 16 intervalId: 112, 17 questionType: "bj", 18 }, 19 { 20 name: '大二度&小七度-下行', 21 intervalId: 113, 22 questionType: "bj", 23 }, 24 ], 25 }, 26 { 27 name: '比較跨兩個八度的復合音程', 28 } 29 ] 30 }, 31 { 32 name: '音程辨認', 33 childrens: [ 34 { 35 name: '辨認小二度和大二度音程', 36 childrens: [ 37 { 38 name: '分辨小二度和大二度音程-上行', 39 intervalId: 1, 40 questionType: "br", 41 }, 42 { 43 name: '分辨小二度和大二度音程-下行', 44 intervalId: 2, 45 questionType: "br", 46 }, 47 { 48 name: '分辨小二度和大二度音程-和聲', 49 intervalId: 3, 50 questionType: "br", 51 }, 52 { 53 name: '分辨小二度和大二度音程-上行下行', 54 intervalId: 4, 55 questionType: "br", 56 }, 57 { 58 name: '分辨小二度和大二度音程-上行和聲', 59 intervalId: 5, 60 questionType: "br", 61 }, 62 { 63 name: '分辨小二度和大二度音程-下行和聲', 64 intervalId: 6, 65 questionType: "br", 66 } 67 ] 68 }, 69 { 70 name: '辨認小三度和大三度的音程', 71 childrens: [ 72 { 73 name: "分辨小三度和大三度音程-上行", 74 intervalId: 7, 75 questionType: "br", 76 }, 77 { 78 name: "分辨小三度和大三度音程-下行", 79 intervalId: 8, 80 questionType: "br", 81 }, 82 { 83 name: "分辨小三度和大三度音程-和聲", 84 intervalId: 9, 85 questionType: "br", 86 }, 87 { 88 name: "分辨小三度和大三度音程-上行下行", 89 intervalId: 10, 90 questionType: "br", 91 }, 92 { 93 name: "分辨小三度和大三度音程-上行和聲", 94 intervalId: 11, 95 questionType: "br", 96 }, 97 { 98 name: "分辨小三度和大三度音程-下行和聲", 99 intervalId: 12, 100 questionType: "br", 101 } 102 ] 103 }, 104 ] 105 }, 106 ]
2,迭代生成樹形菜單

1 // 生成dom tree 2 function generateDomTree(treeData, config = { 3 label: 'name', 4 childrens: 'childrens' 5 }) { 6 var label = config.label // 要顯示的字段名 7 var childrensKey = config.childrens // 子節點字段名 8 var container = generateDomEle('ul') 9 /* 10 fragment 菜單容器 11 menuList 待遍歷菜單 12 show 展開菜單 13 */ 14 function deep(fragment, menuList, show) { 15 for (const menu of menuList) { 16 var liDom = generateDomEle('li', { 17 innerText: menu[label] 18 }) 19 var childrens = menu[childrensKey] 20 // 子節點存在 21 if (childrens && childrens.length) { 22 fragment.appendChild(liDom) // 插入節點 23 var urlDom = generateDomEle('ul') // 生成下一級菜單 24 liDom.classList.add('menu') // 添加菜單標識 25 if (!show) { 26 // 關閉菜單 27 liDom.classList.add('close') 28 } 29 fragment.appendChild(urlDom) 30 31 deep(urlDom, childrens, show) // 迭代 32 } else { 33 liDom.classList.add('menu-item') // 添加菜單項標識 34 fragment.appendChild(liDom) 35 } 36 } 37 } 38 deep(container, treeData, false) 39 return container 40 }
3,給菜單添加點擊展開關閉操作

1 var container = generateDomTree(treeData) 2 container.addEventListener('click', function (e) { 3 var target = e.target 4 // 點中 li 5 if (target.tagName == 'LI') { 6 // 點中菜單 7 if (target.classList.contains('menu')) { 8 target.classList.toggle('close') // 如果關閉則展開,展開則關閉 9 } else { 10 //做你想做的 11 } 12 } 13 }, false)
4,相關代碼解釋說明
4-1,代碼中用到的自定義方法代碼片段

1 // 生成dom節點 2 function generateDomEle(tagName, property) { 3 var property = property || {} 4 var ele = document.createElement(tagName) 5 for (var key in property) { 6 ele[key] = property[key] 7 } 8 return ele 9 }
4-2,效果圖
4-3,生成的dom結構
@萍2櫻釋 ღ( ´・ᴗ・` )