Vue 封装无限层级树形菜单组件(后台传的是扁平数组)

项目原因,需要把一个扁平/线性数组转换成树形数组(符合 el-tree 数据要求)

const resData = [
  { id: '1', label: '动物', parentId: '', icon: '' },
  { id: '2', label: '狗', parentId: '1', icon: 'icon-chongwutubiao13' },
  { id: '3', label: '哈士奇', parentId: '2', icon: 'icon-hashiqi' },
  { id: '4', label: '柯基', parentId: '2', icon: 'icon-keji-' },
  { id: '6', label: '猫', parentId: '1', icon: 'icon-chongwutubiao04' },
  { id: '7', label: '植物', parentId: '', icon: '' },
  { id: '8', label: '微生物', parentId: '', icon: '' },
]

JS 代码:扁平数组转换成树形数组
直接上代码,不需要递归

function transformData(data) {
  // 深拷贝一份(以防连续调用出错)
  const cData = JSON.parse(JSON.stringify(data))
  const map = {}
  const tData = []
  // 注意:这里item的引用地址指向cData
  cData.forEach(item => (map[item.id] = item))
  cData.forEach(child => {
    const mapItem = map[child.parentId]
    if (mapItem) {
      // 注意:这里mapItem引用地址指向也是指向cData
      if (!mapItem.children) mapItem.children = []
      mapItem.children.push(child)
    } else {
      // 顶级节点
      tData.push(child)
    }
  })
  return tData
}

项目应用:el-tree 制作一个树形多级嵌套菜单栏
在这里插入图片描述
Vue 代码

<template>
  <el-scrollbar wrap-class="scrollbar-wrapper" style="height: 100%">
    <el-tree
      ref="tree"
      v-loading="treeDataLoading"
      node-key="id"
      :data="treeData"
      :show-checkbox="showCheckBox"
      :default-expand-all="defaultExpandAll"
      style="height: 100%; padding-bottom: 20px"
    >
      <span slot-scope="{ node, data }" class="custom-tree-node">
        {{ node.label }}
        <template v-if="data.icon">
          <el-tooltip effect="dark" :content="data.label" placement="top-start">
            <i :class="['iconfont', data.icon]"></i>
          </el-tooltip>
        </template>
      </span>
    </el-tree>
  </el-scrollbar>
</template>

<script>
export default {
  props: {
    // 节点是否可被选择(显示前面的复选框)
    showCheckBox: {
      type: Boolean,
      default: false,
    },
    // 是否默认展开所有节点
    defaultExpandAll: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      treeData: [],
      treeDataLoading: false,
      resData: [
        { id: '1', label: '动物', parentId: '', icon: '' },
        { id: '2', label: '狗', parentId: '1', icon: 'icon-chongwutubiao13' },
        { id: '3', label: '哈士奇', parentId: '2', icon: 'icon-hashiqi' },
        { id: '4', label: '柯基', parentId: '2', icon: 'icon-keji-' },
        { id: '6', label: '猫', parentId: '1', icon: 'icon-chongwutubiao04' },
        { id: '7', label: '植物', parentId: '', icon: '' },
        { id: '8', label: '微生物', parentId: '', icon: '' },
      ],
    }
  },
  methods: {
    init() {
      this.treeDataLoading = true
      // 发送数据请求(这里不发送请求以resData模拟)
      new Promise(res => {
        const tree = this.resData
        this.treeData = this.transformData(tree)
        res()
      })
        .catch(() => {})
        .finally(() => {
          this.treeDataLoading = false
        })
    },
    transformData(data, params = {}) {
      const cData = JSON.parse(JSON.stringify(data))
      const map = {}
      const tData = []
      const attr = {
        id: 'id',
        parentId: 'parentId',
      }
      const arg = Object.assign({}, attr, params)
      cData.forEach(item => (map[item[arg.id]] = item))
      cData.forEach(child => {
        const mapItem = map[child[arg.parentId]]
        if (mapItem) {
          if (!mapItem.children) mapItem.children = []
          mapItem.children.push(child)
        } else {
          tData.push(child)
        }
      })
      return tData
    },
    // 全选
    checkAll() {
      this.$refs.tree.setCheckedNodes(this.treeData)
    },
    // 取消全选
    cancelAll() {
      this.$refs.tree.setCheckedKeys([])
    },
  },
}
</script>

<style lang="less" scoped>
.scrollbar-wrapper {
  overflow-x: hidden !important;
}
.custom-tree-node {
  font-size: 16px;
}
</style>

父组件调用

<template>
  <div>
    <tree-select ref="tree" defaultExpandAll />
  </div>
</template>
<script>
import TreeSelect from './components/TreeSelect'
export default {
  components: {
    TreeSelect,
  },
  mounted() {
    this.$refs['tree'].init()
  },
}
</script>

js实现无限层级树形数据结构(创新算法)

由于做项目的需要,把一个线性数组转成树形数组,在网上查了很多文章,觉得他们写的太复杂了,于是自己写了一个,在折腾了一下午终于把它写出来啦(激动.gif),用两个filter过滤器就搞定了,代码简洁明了,数据结构小白都能看懂。
js代码:把扁平数据转成树形数据

function setTreeData(source){
    let cloneData = JSON.parse(JSON.stringify(source))      // 对源数据深度克隆
    return  cloneData.filter(father=>{                      // 循环所有项,并添加children属性
        let branchArr = cloneData.filter(child=> father.id == child.parentId);   // 返回每一项的子级数组
        branchArr.length>0 ? father.children=branchArr : ''   //给父级添加一个children属性,并赋值
        return father.parentId==0;      //返回第一层
    });
}

封装函数:

function treeData(source, id, parentId, children){   
    let cloneData = JSON.parse(JSON.stringify(source))
    return cloneData.filter(father=>{
        let branchArr = cloneData.filter(child => father[id] == child[parentId]);
        branchArr.length>0 ? father[children] = branchArr : ''
        return father[parentId] == 0        // 如果第一层不是parentId=0,请自行修改
    })
}
 
// 调用时,字段名以字符串的形式传参,如treeData(source, 'id', 'parentId', 'children')

在这里插入图片描述
vue组件:

<template>
  <el-tree
    :data="treeData"
    :props="defaultProps"
    accordion
    @node-click="handleNodeClick">
  </el-tree>
</template>
 
<script>
    export default {
        name: "Test",
      data(){
        return {
          data : [
            {id:1,parentId:0,name:"一级菜单A",rank:1},
            {id:2,parentId:0,name:"一级菜单B",rank:1},
            {id:3,parentId:0,name:"一级菜单C",rank:1},
            {id:4,parentId:1,name:"二级菜单A-A",rank:2},
            {id:5,parentId:1,name:"二级菜单A-B",rank:2},
            {id:6,parentId:2,name:"二级菜单B-A",rank:2},
            {id:7,parentId:4,name:"三级菜单A-A-A",rank:3},
            {id:8,parentId:7,name:"四级菜单A-A-A-A",rank:4},
            {id:9,parentId:8,name:"五级菜单A-A-A-A-A",rank:5},
            {id:10,parentId:9,name:"六级菜单A-A-A-A-A-A",rank:6},
            {id:11,parentId:10,name:"七级菜单A-A-A-A-A-A-A",rank:7},
            {id:12,parentId:11,name:"八级菜单A-A-A-A-A-A-A-A",rank:8},
            {id:13,parentId:12,name:"九级菜单A-A-A-A-A-A-A-A-A",rank:9},
            {id:14,parentId:13,name:"十级菜单A-A-A-A-A-A-A-A-A-A",rank:10},
          ],
          defaultProps: {
            children: 'children',
            label: 'name'
          }
        }
      },
      computed:{
        treeData(){
          let cloneData = JSON.parse(JSON.stringify(this.data))    // 对源数据深度克隆
          return cloneData.filter(father=>{               
            let branchArr = cloneData.filter(child=>father.id == child.parentId)    //返回每一项的子级数组
            branchArr.length>0 ? father.children = branchArr : ''   //如果存在子级,则给父级添加一个children属性,并赋值
            return father.parentId==0;      //返回第一层
          });
        }
      },
      methods:{
        handleNodeClick(data){
          // console.log(data)
          console.log(this.treeData)
        }
      },
      mounted(){
      }
    }
</script>
 
<style scoped>
 
</style>
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值