• 请求本地 JSON 文件获取数据 。
  1. 问题:cors ,跨域问题 。
  2. 解决:使用 jsonp 方式,请求相关数据 。
  3. 注意:在 json 文件中,json 数据使用 callback( json数据 ) 包裹,才能保持浏览器控制台打印出数据无报错。我这里回调函数是 getData( json数据 )。
  4. 代码:
<script type="text/javascript">
 /*
  getData 函数: JSONP 的函调函数,解决跨域问题。请求本地 JSON 文件
  参数: data---》拿到的 JSON 数据
*/
 function getData(data) {
     // initData---》存放当前操作完的全局树形数据
     var initData = [] 
     initData = JSON.parse(JSON.stringify(formatData(data.menuTree,'')))
     initTable(initData)
 }
</script>
<script type="text/javascript" src="./data.json?callback=getData"></script>
  • 一维数组数据转换为树形数据 (递归)。
  1. 思路:使用递归的方式将一维数组转换成树形数据,根据 menuParentid 和 menuId 的对比,对数据进行分类,添加到合适的位置上。
  2. 代码:
/* 
 formatData 函数:数据转换,将一维数组数据转换为树形数据
 参数:data---》原始一维数组数据
 返回值:resultArr---》格式化好的树形数据
*/
function formatData(data, pid) {
    var resultArr = [], temp;
    for (var i = 0; i < data.length; i++) {
        if (data[i].menuParentid == pid) {
            var obj = JSON.parse(JSON.stringify(data[i]));
            temp = formatData(data, data[i].menuId);
            if (temp.length > 0) {
                obj.children = temp;
            }
            resultArr.push(obj);
        }
    }
    return resultArr;
}
  • JS 中初始化表格 。
  1. 思路:在 initTR_DG 函数中递归的根据数据,动态新增 tr 到 table 中。根据 当前数据项的 menuType 是否为菜单,决定是否有前缀的展开和收起按钮。
  2. 代码:
/* 
  initTable 函数: 初始化表格
  参数: data---》转换的树形数据
*/
function initTable(data) {
    globalData = JSON.parse(JSON.stringify(data))
    var tableEL = document.createElement('table')
    var containerEL = document.getElementById('container')
    containerEL.appendChild(tableEL)
    tableEL.appendChild(createTH())
    initTR_DG(data,tableEL,'')
}
function initTR_DG(data,tableEL,index){
    if(data && data.length != 0){
        for(var i=0; i<data.length; i++){
            if(data[i].menuType == '菜单') {
                var trEL = createTR(data[i], 1, index+'.'+(i+1))
            } else {
                var trEL = createTR(data[i], 0, index+'.'+(i+1))
            }
            tableEL.appendChild(trEL)
            if(data[i].children && data[i].children.length != 0){
                initTR_DG(data[i].children,tableEL,index+'.'+(i+1))
            }
        }
    }
}
/*
  createTH 函数: 创建表头
  返回值: trEL---》表头元素
*/
function createTH() {
    var trEL = document.createElement('tr')
    var thEL0 = document.createElement('th')
    thEL0.innerHTML = '序号'
    trEL.appendChild(thEL0)
    var thEL1 = document.createElement('th')
    thEL1.innerHTML = '功能名称'
    trEL.appendChild(thEL1)
    var thEL2 = document.createElement('th')
    thEL2.innerHTML = '是否启用'
    trEL.appendChild(thEL2)
    var thEL3 = document.createElement('th')
    thEL3.innerHTML = '功能类型'
    trEL.appendChild(thEL3)
    var thEL4 = document.createElement('th')
    thEL4.innerHTML = '创建时间'
    trEL.appendChild(thEL4)
    var thEL5 = document.createElement('th')
    thEL5.innerHTML = '功能备注'
    trEL.appendChild(thEL5)
    var thEL6 = document.createElement('th')
    thEL6.innerHTML = '相关操作'
    trEL.appendChild(thEL6)
    return trEL
}
/* 
  createTR 函数: 创建每一列
  参数1: currentData---》当前列的数据
  参数2: type---》类型,决定有没有展开折叠按钮,1 有,其他无
  参数3 index---》计算的当前列的标号
  参数4: tier---》第几层
  返回值: trEL---》创建的当前 tr
*/
function createTR(currentData, type, index) {
    var trEL = document.createElement('tr')
    if (type == 1) {
        var tdEL0 = document.createElement('td')
        tdEL0.innerHTML = index
        tdEL0.style.fontWeight = 'bold'
        tdEL0.style.fontSize = '8px'
        trEL.appendChild(tdEL0)
        var spanEL = document.createElement('span')
        spanEL.classList.add('span')
        spanEL.title = '展开'
        spanEL.onclick = clickZK
        var spanEL1 = document.createElement('span')
        spanEL1.classList.add('span1')
        spanEL1.title = '收起'
        spanEL1.onclick = clickSQ
        var tdEL1 = document.createElement('td')
        tdEL1.innerHTML = currentData.menuText
        tdEL0.style.textAlign = 'left'
        tdEL0.appendChild(spanEL)
        tdEL0.appendChild(spanEL1)
        trEL.appendChild(tdEL1)
        var tdEL2 = document.createElement('td')
        tdEL2.innerHTML = currentData.menuIsmodify
        trEL.appendChild(tdEL2)
        var tdEL3 = document.createElement('td')
        tdEL3.innerHTML = currentData.menuType
        currentData.menuType == '菜单' ? tdEL3.style.color = '#409EFF' : tdEL3.style.color = 'green'
        trEL.appendChild(tdEL3)
        var tdEL4 = document.createElement('td')
        tdEL4.innerHTML = datefomate(currentData.menuCreatetime)
        trEL.appendChild(tdEL4)
        var tdEL5 = document.createElement('td')
        tdEL5.innerHTML = currentData.menuNote
        trEL.appendChild(tdEL5)
        var tdEL6 = document.createElement('td')
        var editBtn = document.createElement('button')
        editBtn.innerText = '编辑'
        editBtn.classList.add('button1')
        editBtn.onclick = editTableData
        tdEL6.appendChild(editBtn)
        var delBtn = document.createElement('button')
        delBtn.innerText = '删除'
        delBtn.classList.add('button2')
        delBtn.onclick = delTableData
        tdEL6.appendChild(delBtn)
        var addBtn = document.createElement('button')
        addBtn.innerText = '新增'
        addBtn.onclick = addTableData
        tdEL6.appendChild(addBtn)
        addBtn.classList.add('button0')
        trEL.appendChild(tdEL6)
    } else {
        var tdEL0 = document.createElement('td')
        var strongEL = document.createElement('strong')
        strongEL.innerHTML = index
        tdEL0.appendChild(strongEL)
        tdEL0.style.fontSize = '8px'
        trEL.appendChild(tdEL0)
        var tdEL1 = document.createElement('td')
        tdEL1.innerHTML = currentData.menuText
        trEL.appendChild(tdEL1)
        var tdEL2 = document.createElement('td')
        tdEL2.innerHTML = currentData.menuIsmodify
        trEL.appendChild(tdEL2)
        var tdEL3 = document.createElement('td')
        tdEL3.innerHTML = currentData.menuType
        currentData.menuType == '菜单' ? tdEL3.style.color = '#409EFF' : tdEL3.style.color = 'green'
        trEL.appendChild(tdEL3)
        var tdEL4 = document.createElement('td')
        tdEL4.innerHTML = datefomate(currentData.menuCreatetime)
        trEL.appendChild(tdEL4)
        var tdEL5 = document.createElement('td')
        tdEL5.innerHTML = currentData.menuNote
        trEL.appendChild(tdEL5)
        var tdEL6 = document.createElement('td')
        var editBtn = document.createElement('button')
        editBtn.innerText = '编辑'
        editBtn.classList.add('button1')
        editBtn.onclick = editTableData
        tdEL6.appendChild(editBtn)
        var delBtn = document.createElement('button')
        delBtn.innerText = '删除'
        delBtn.classList.add('button2')
        delBtn.onclick = delTableData
        tdEL6.appendChild(delBtn)
        trEL.appendChild(tdEL6)
    }
    return trEL
}
  • 列收起和列展开。
  1. 思路:根据 clickSQ 的点击事件,确定点击的数据项,在根据 clickSQ_DG 函数,递归的找出当前数据项,让它的 children = [ ],重新初始化表格。根据 clickZK 的点击事件,清空原始 table 表格结构,使用缓存的 initData 数据去重新初始化表格。
  2. 代码:
// clickSQ 函数: 列收起
function clickSQ() {
    initData = JSON.parse(JSON.stringify(globalData))
    var currentText = this.parentNode.parentNode.children[1].innerText
    clickSQ_DG(globalData,currentText)
    document.getElementById('container').innerHTML = ''
    initTable(globalData)
    SQstatus = false
}
function clickSQ_DG(globalData,currentText){
    for (var i = 0; i < globalData.length; i++) {
        if (globalData[i].menuText == currentText) {
            globalData[i].children = []
            SQstatus = true
            break
        }
        if(SQstatus == false){
            if(globalData[i].children && globalData[i].children.length != 0){
                clickSQ_DG(globalData[i].children,currentText)
            }
        }
    }
}
// clickZK 函数: 列展开
function clickZK() {
    document.getElementById('container').innerHTML = ''
    initTable(initData)
}
  • 删除操作 。
  1. 思路:根据 delTableData 确定当前操作的数据列,使用 delTableData_DG 函数递归来找到当前的数据,进行删除 ,重新初始化表格 。
  2. 表格:
// delTableData函数: 删除操作
function delTableData() {
    var currentText = this.parentNode.parentNode.children[1].innerText
    delTableData_DG(globalData,currentText)
    document.getElementById('container').innerHTML = ''
    initTable(globalData)
    SQstatus = false
}
function delTableData_DG(globalData,currentText){
    for (var i = 0; i < globalData.length; i++) {
        if (globalData[i].menuText == currentText) {
            globalData.splice(i, 1)
            SQstatus = true
            break
        }
        if(SQstatus == false){
            if(globalData[i].children && globalData[i].children.length != 0){
                delTableData_DG(globalData[i].children,currentText)
            }
        }
    }
}
  • 编辑操作 。
  1. 思路:根据 editTableData 确定当前操作的数据列,使用 editTableData_DG 函数递归来找到当前的数据,使用 setEditData 函数进行数据回显,使用 getCurrentEdit 函数进行数据缓存,当点击编辑保存事件时进行当前缓存数据的更改,最后重新初始化表格 。
  2. 代码:
// editTableData 函数: 编辑操作
function editTableData() {
    document.getElementById('edit').style.display = 'block'
    var currentText = this.parentNode.parentNode.children[1].innerText
    editTableData_DG(globalData,currentText)
    SQstatus = false
}
function editTableData_DG(globalData,currentText){
    for (var i = 0; i < globalData.length; i++) {
        if (globalData[i].menuText == currentText) {
            setEditData(globalData[i])
            getCurrentEdit(globalData[i])
            SQstatus = true
            return
        }
        if(SQstatus == false){
            if(globalData[i].children && globalData[i].children.length != 0){
                editTableData_DG(globalData[i].children,currentText,currentGlobalData)
            }
        }
    }
}
function getCurrentEdit(data){
    return currentGlobalData = data
}
/* 
  etEditData 函数: 编辑数据回显
  参数: data---》编辑回显的当前数据
*/
function setEditData(data) {
    document.getElementById('menuText').value = data.menuText
    document.getElementById('menuIsmodify').value = data.menuIsmodify == '启用' ? 1 : 0
    document.getElementById('menuType').value = data.menuType == '菜单' ? 1 : 0
    document.getElementById('menuCreatetime').value = datefomate(data.menuCreatetime)
    document.getElementById('menuNote').value = data.menuNote
}
// 编辑关闭
document.getElementById('editClose').onclick = function() {
    document.getElementById('edit').style.display = 'none'
}
// 编辑保存
document.getElementById('editSave').onclick = function() {
    currentGlobalData.menuText = document.getElementById('menuText').value
    currentGlobalData.menuIsmodify = document.getElementById('menuIsmodify').value == 1 ? '启用' : '禁用'
    currentGlobalData.menuType = document.getElementById('menuType').value == 1 ? '菜单' : '链接'
    currentGlobalData.menuCreatetime = document.getElementById('menuCreatetime').value
    currentGlobalData.menuNote = document.getElementById('menuNote').value
    document.getElementById('container').innerHTML = ''
    initTable(globalData)
    document.getElementById('edit').style.display = 'none'
}
// 编辑取消
document.getElementById('editCancle').onclick = function() {
   document.getElementById('edit').style.display = 'none'
}
  • 新增操作 。
  1. 思路:根据 addTableData 确定当前操作的数据列,使用 addTableData_DG 函数递归来找到当前的数据,使用 getCurrentAdd 函数进行数据缓存,当点击新增保存事件时进将数据添加到缓存的当前数据项的 children 中,最后重新初始化表格 。
  2. 代码:
// addTableData 函数:新增操作
function addTableData() {
    document.getElementById('add').style.display = 'block'
    document.getElementById('menuCreatetime1').value = datefomate(Date.now())
    var currentText = this.parentNode.parentNode.children[1].innerText
    addTableData_DG(globalData,currentText)
    SQstatus = false
}
function addTableData_DG(globalData,currentText){
    for (var i = 0; i < globalData.length; i++) {
        if (globalData[i].menuText == currentText) {
            getCurrentAdd(globalData[i])
            SQstatus = true
            return
        }
        if(SQstatus == false){
            if(globalData[i].children && globalData[i].children.length != 0){
                addTableData_DG(globalData[i].children,currentText)
            }
        }
    }
}
function getCurrentAdd(data){
    return currentGlobalData1 = data
}
// 新增关闭
document.getElementById('addClose').onclick = function() {
    document.getElementById('menuText1').value = ''
    document.getElementById('menuNote1').value = ''
    document.getElementById('add').style.display = 'none'
}
//新增保存
document.getElementById('addSave').onclick = function() {
    var obj = {}
    obj.children = []
    obj.menuText = document.getElementById('menuText1').value
    obj.menuIsmodify = document.getElementById('menuIsmodify1').value == 1 ? '启用' : '禁用'
    obj.menuType = document.getElementById('menuType1').value == 1 ? '菜单' : '链接'
    obj.menuCreatetime = document.getElementById('menuCreatetime1').value
    obj.menuNote = document.getElementById('menuNote1').value
    if(!currentGlobalData1.children){
        currentGlobalData1.children = []
        currentGlobalData1.children[0] = obj
    }else{
        currentGlobalData1.children.push(obj)
    }
    document.getElementById('container').innerHTML = ''
    initTable(globalData)
    document.getElementById('menuText1').value = ''
    document.getElementById('menuNote1').value = ''
    document.getElementById('add').style.display = 'none'
}
// 新增取消
document.getElementById('addCancle').onclick = function() {
    document.getElementById('menuText1').value = ''
    document.getElementById('menuNote1').value = ''
    document.getElementById('add').style.display = 'none'
}
  • 格式化日期 。
  1. 思路:使用 new Date(value) 方法返回值提供的一系列方法进行详细日期时间子项的提取,然后在根据合适的格式进行返回。
  2. 代码:
// datefomate函数: 格式化日期
function datefomate(value) {
    if (value == null || value == undefined) {
        return "";
    }
    var date = new Date(value);
    Y = date.getFullYear(),
    m = date.getMonth() + 1,
    d = date.getDate(),
    H = date.getHours(),
    i = date.getMinutes(),
    s = date.getSeconds();
    return Y + '-' + m + '-' + d + '    ' + H + ':' + i + ':' + s;
};

使用 VUE 和 Element-UI  实现树形表格 ?


【 如需查看完整示例请点击:tree_table_vue_element: 使用 vue 和 element-ui 实现,树形表格案例,支持多级数据渲染,新增,数辑,删除等操作。 


  •  请求本地 JSON 文件获取数据 。
  1. 思路:使用 axios.get( ) 的方法,请求本地 JSON 文件,获取数据 。
  2. 注意:cli2 需要把  json 放在 static 目录下,cli3 中静态资源文件目录由 static 变为 public 不能把 .json 文件直接放到 public 下,必须放到 public/js 下(自己创建 js 目录),否则会报 404 错误 。
  3. 代码:
axios.get('/js/data.json').then((res) => {
  this.defaultData = res.data.menuTree
  this.tableData = this.formatData(this.defaultData)
})
  • 一维数组数据转换为树形数据(三级)。
  1. 思路:数据格式转换,根据自己需求场景的不同来决定。我这里只涉及三级数据,选择使用 for 循环嵌套去实现(第一层判断 menuParentid 是否为空,第二三层根据比对 menuParentid 和 menuId 的值来确定存放的合适 children 位置)。更多级推荐使用递归方式实现,具体使用参考上面 。
  2. 代码:
formatData(data) {
  let resultArr = []
  for (let i = 0; i < data.length; i++) {
    if (data[i].menuParentid == '') {
      resultArr.push(data[i])
    }
  }
  for (let i = 0; i < resultArr.length; i++) {
    let newArr = []
    for (let j = 0; j < data.length; j++) {
      if (data[j].menuParentid == resultArr[i].menuId) {
        newArr.push(data[j])
      }
    }
    resultArr[i].children = newArr
  }
  for (let i = 0; i < resultArr.length; i++) {
    for (let k = 0; k < resultArr[i].children.length; k++) {
      let newArr1 = []
      for (let j = 0; j < data.length; j++) {
        if (data[j].menuParentid == resultArr[i].children[k].menuId) {
          newArr1.push(data[j])
        }
      }
      resultArr[i].children[k].children = newArr1
    }
  }
  return resultArr
}
  • 数据渲染,使用 Element-UI 中提供的表格组件 。
  1. 思路:这里使用 Element-UI 中的表格组件去实现,也可以使用 layui 或者其他 UI 库中的组件去实现 。
  2. 代码:
<el-table :data="tableData" height="500" style="width: 100%;margin-bottom: 20px;" row-key="id" border
      :expand-row-keys="['1','2','16','17','18']" :tree-props="{children: 'children', hasChildren: 'hasChildren'}">
      <el-table-column type="index" width="60" label="序号" align="center">
      </el-table-column>
      <el-table-column prop="menuText" label="功能名称" width="180" align="center">
      </el-table-column>
      <el-table-column prop="menuIsmodify" label="是否启用" width="180" align="center">
      </el-table-column>
      <el-table-column prop="menuType" label="功能类型" align="center">
        <template slot-scope="scope">
          <el-tag :type="scope.row.menuType === '菜单' ? 'primary' : 'success'" disable-transitions>{{scope.row.menuType}}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="menuCreatetime" label="创建时间" align="center">
      </el-table-column>
      <el-table-column prop="menuNote" label="备注" width="200" align="center">
      </el-table-column>
      <el-table-column label="操作" align="center">
        <template slot-scope="scope">
          <el-button style="color:#67C23A;" @click="addHandle(scope.row)" type="text" size="small"
             v-if="scope.row.menuType === '菜单'" icon="el-icon-circle-plus-outline">新增</el-button>
          <el-button type="text" size="small" @click="editHandle(scope.row)" icon="el-icon-edit">编辑</el-button>
          <el-button style="color:red;" @click="delHandle(scope.row)" type="text" size="small" icon="el-icon-delete">删除
      </el-button>
    </template>
  </el-table-column>
</el-table>
  • 删除操作 。
  1. 思路:遍历 defaultData 中的数据,找到删除的那一项进行删除,在使用 formatData 函数对数据进行格式化树形数据,重新赋值给 tableData ,进行表格的重新渲染。
  2. 代码:
delHandle(row) {
  this.$confirm('此操作将永久删除此项, 是否继续?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    for (let i = 0; i < this.defaultData.length; i++) {
      if (this.defaultData[i].id == row.id) {
        this.defaultData.splice(i, 1)
      }
    }
    this.tableData = this.formatData(this.defaultData)
    this.$message({
      type: 'success',
      message: '删除成功!'
    });
  }).catch(() => {
    this.$message({
      type: 'info',
      message: '已取消删除'
    });
  });
}
  • 编辑操作 。
  1. 思路:遍历 defaultData 中的数据,找到要编辑的那一项进行修改,在使用 formatData 函数对数据进行格式化树形数据,重新赋值给 tableData ,进行表格的重新渲染。
  2. 代码:
editHandle(row) {
  this.isExit = true
  this.timer = new Date().getTime()
  for (let i = 0; i < this.defaultData.length; i++) {
    if (this.defaultData[i].id == row.id) {
      this.currentEdit = this.defaultData[i]
    }
  }
},
eventHandle($event) {
  for (let i = 0; i < this.defaultData.length; i++) {
     if (this.defaultData[i].id == $event.id) {
      this.defaultData[i] = $event
    }
  }
  this.tableData = this.formatData(this.defaultData)
}
  • 新增操作 。
  1. 思路:遍历 defaultData 中的数据,找到要新增项的父级,使得新增项的 menuParentid 等于新增项的父级的 menuId ,同时将新增项添加到 defaultData  中 。在使用 formatData 函数对数据进行格式化树形数据,重新赋值给 tableData ,进行表格的重新渲染。
  2. 代码:
addHandle(row) {
  this.isExit1 = true
  this.timer1 = new Date().getTime()+'1'
  for (let i = 0; i < this.defaultData.length; i++) {
    if (this.defaultData[i].id == row.id) {
      this.currentAdd = this.defaultData[i]
    }
  }
},
eventHandle1($event) {
  this.defaultData.push($event)
  $event.id = this.defaultData.length
  $event.menuParentid = this.currentAdd.menuId
  this.tableData = this.formatData(this.defaultData)
 }
}