思想:一级数据直接展示,其余的数据暂时不生成。带到点击某一个一级在去生成其对应的二级数据,并且后续只需要判断是否存在,存在则隐藏。需要准备一个记录隐藏状态的数组,及存子集的对象,便于快速访问。在tr上可以通过处理好的数据添加自定义属性,便于后续操作。实列中的图标可以自己去引入,使用UI组件库中的图标,对于none的图标也可以依据自己的需求进行改变。
<!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>原生js实现树形表格</title>
</head>
<style>
.icon-angle-left:before {
content: '▶';
}
.icon-angle-down:before {
content: '▼';
}
table {
border-spacing: 0px;
border-collapse: collapse;
width: 50%;
margin: 100px auto;
border: rgb(221, 221, 221);
}
table tbody tr.level_odd{
background-color: #FFF;
}
table tbody tr.level_even{
background-color: #f5f5f5;
}
.c_pointer{
cursor: pointer;
}
</style>
<body>
<table border>
<thead>
<tr>
<th>科目</th>
<th>分数</th>
</tr>
</thead>
<tbody id="myTbody"></tbody>
</table>
<script>
// 树形结构层级转换
treeList = [
{
name:'总分',
grade:935
},
{
name: '主课',
grade: 375,
childs: [
{
name: '语文',
grade: 125,
childs: [
{
name: '作文',
grade: 55
}, {
name: '其他',
grade: 70
}
]
},
{
name: '数学',
grade: 125
},
{
name: '英语',
grade: 125
}
]
},
{
name: '文综',
grade: 290,
childs: [
{
name: '历史',
grade: 100,
},
{
name: '政治',
grade: 95
},
{
name: '地理',
grade: 95
}
]
},
{
name: '理综',
grade: 270,
childs: [
{
name: '物理',
grade: 80
},
{
name: '生物',
grade: 100
},
{
name: '化学',
grade: 90
}
]
}
]
const fIdChildMapList = {}
const trStatus = {}
function flatAndaddSpace(tree, pre = '', idPre = '', pid = '', plevel = 0) {
let data = []
const len = tree.length
for (let i = 0; i < len; i++) {
if (fIdChildMapList[pid]) {
fIdChildMapList[pid].push(idPre + i)
} else {
fIdChildMapList[pid] = [idPre + i]
}
tree[i].preSpace = pre
tree[i].f_id = idPre + i
tree[i].level = plevel + 1
trStatus[idPre + i] = true
const obj = {}
Object.keys(tree[i]).forEach(key => {
if (key != 'childs') {
obj[key] = tree[i][key] === null ? '-' : tree[i][key]
}
})
data.push(obj)
if (tree[i].childs) {
let currentPre = pre + ' '
data = data.concat(flatAndaddSpace(tree[i].childs, currentPre, tree[i].f_id + '-', tree[i].f_id, tree[i].level))
}
}
return data
}
const handleData = flatAndaddSpace(treeList, '', '', '-1')
/**
*
* @param {string} curId
* @param {boolean} showChild
* @returns
*/
function getWillHideFId(curId) {
if (!fIdChildMapList[curId]) return []
var rst = fIdChildMapList[curId]
fIdChildMapList[curId].forEach(key => {
rst = rst.concat(getWillHideFId(key))
})
return rst
}
const createTr = data => {
const tr = document.createElement('tr')
let level = data.level % 2 !== 0 ? 'odd' : 'even'
let icon = fIdChildMapList[data.f_id] ? '<i class="icon icon-angle-left"></i>' : '<i class="icon none"></i>'
tr.className = `f_id_${data.f_id} level_${level} tree_table_tr ${fIdChildMapList[data.f_id] ?'c_pointer':''}`
tr.setAttribute('f_id', data.f_id)
const str = `
<td>
${data.preSpace}
${icon}
${data.name}
</td>
<td>${data.grade}</td>`
tr.innerHTML = str
tr.addEventListener('click', function () {
handleClick(this)
})
return tr
}
function init() {
const myTbody = document.querySelector('#myTbody')
handleData.forEach(item => {
if (fIdChildMapList['-1'].includes(item.f_id)) {
myTbody.appendChild(createTr(item))
}
})
}
init()
/**
* @param {HTMLElement} ele
*/
function handleClick(ele) {
const fid = ele.getAttribute('f_id')
const showChild = trStatus[fid]
handShowAndHide(fid, showChild, ele)
trStatus[fid] = !showChild
let iconClass = showChild ? "icon icon-angle-down" : "icon icon-angle-left"
if (ele.firstElementChild.firstElementChild.className.indexOf('none') === -1) {
ele.firstElementChild.firstElementChild.className = iconClass
ele.firstElementChild.firstElementChild.className = iconClass
}
}
function handShowAndHide(fid, showChild, ele) {
if (!showChild) {
// 关闭
getWillHideFId(fid).forEach(id => {
if (document.querySelector(`.f_id_${id}`))
document.querySelector(`.f_id_${id}`).style.display = "none"
let className = fIdChildMapList[id] ? "icon icon-angle-left" : "icon none"
document.querySelector(`.f_id_${id}`).firstElementChild.firstElementChild.className = className
trStatus[id] = false
})
} else {
(fIdChildMapList[fid] || []).forEach(id => {
if (document.querySelector(`.f_id_${id}`)) {
document.querySelector(`.f_id_${id}`).style.display = "table-row"
} else {
const tr = createTr(handleData.filter(item => item.f_id === id)[0])
document.querySelector('#myTbody').insertBefore(tr, ele.nextSibling)
}
trStatus[id] = true
})
}
}
</script>
</body>
</html>