公司/企业落地,即拿即用,移动端无限级树组件封装,有手就行

70 阅读3分钟

1.为什么封装?

业务需要,公司PC端用到了树组件,pc端用EXt组件做的,相对来说比较容易,PDA需要同步,但是PDA用的框架比较古老,cube UI,相信很多人都没听过,下面这个实践我是引入了vantUI,因为现在毕竟没人用cubeUI,引入组件库的目的是用它自带的UI风格和ICON图标。下面是实践结果:

image.png

image.png

2.废话不多说,先介绍一下他的功能

2.1 单选,只能选中最后一级子节点(公司业务是这样,代码贴上可以自己改)
2.2 展开收缩功能
2.3 第一级手风琴功能(比较复杂,代码里边有注释)
2.4 回显功能

3.直接贴代码

image.png

app.vue

<template>
  <div id="app">
    <van-nav-bar title="Tree Component Demo" />
    <N-tree
      :treeData="treeData"
      :selected-item="selectedItem"
      @item-click="onSelectItem"
      @toggle-expand="onExpandItem"
    />
  </div>
</template>

<script>
import NTree from './components/N-tree.vue'

export default {
  name: 'App',
  components: {
    NTree,
  },
  data() {
    return {
      selectedItem: null,
      treeData: [
        {
          id: 1,
          name: 'Node 1',
          expanded: false,
          leaf: false,
          children: [
            {
              id: 2,
              name: 'Node 1.1',
              expanded: false,
              leaf: false,
              children: [
                { id: 3, name: 'Node 1.1.1', expanded: false, leaf: true, children: [] },
                {
                  id: 4,
                  name: 'Node 1.1.2',
                  expanded: false,
                  leaf: false,
                  children: [
                    { id: 5, name: 'Node 1.1.2.1', expanded: false, leaf: true, children: [] },
                    { id: 6, name: 'Node 1.1.2.2', expanded: false, leaf: true, children: [] },
                  ],
                },
              ],
            },
            { id: 7, name: 'Node 1.2', expanded: false, leaf: true, children: [] },
            {
              id: 8,
              name: 'Node 1.3',
              expanded: false,
              leaf: false,
              children: [
                {
                  id: 9,
                  name: 'Node 1.3.1',
                  expanded: false,
                  leaf: false,
                  children: [
                    {
                      id: 10,
                      name: 'Node 1.3.1.1',
                      expanded: false,
                      leaf: false,
                      children: [
                        { id: 11, name: 'Node 1.3.1.1.1', expanded: false, leaf: true, children: [] },
                        { id: 12, name: 'Node 1.3.1.1.2', expanded: false, leaf: true, children: [] },
                      ],
                    },
                  ],
                },
              ],
            },
          ],
        },
        {
          id: 13,
          name: 'Node 2',
          expanded: false,
          leaf: false,
          children: [
            {
              id: 14,
              name: 'Node 2.1',
              expanded: false,
              leaf: false,
              children: [
                { id: 15, name: 'Node 2.1.1', expanded: false, leaf: true, children: [] },
                {
                  id: 16,
                  name: 'Node 2.1.2',
                  expanded: false,
                  leaf: false,
                  children: [
                    {
                      id: 17,
                      name: 'Node 2.1.2.1',
                      expanded: false,
                      leaf: false,
                      children: [
                        {
                          id: 18,
                          name: 'Node 2.1.2.1.1',
                          expanded: false,
                          leaf: false,
                          children: [
                            { id: 19, name: 'Node 2.1.2.1.1.1', expanded: false, leaf: true, children: [] },
                          ],
                        },
                      ],
                    },
                  ],
                },
              ],
            },
          ],
        },
        {
          id: 20,
          name: 'Node 3',
          expanded: false,
          leaf: false,
          children: [
            {
              id: 21,
              name: 'Node 3.1',
              expanded: false,
              leaf: false,
              children: [
                {
                  id: 22,
                  name: 'Node 3.1.1',
                  expanded: false,
                  leaf: false,
                  children: [
                    {
                      id: 23,
                      name: 'Node 3.1.1.1',
                      expanded: false,
                      leaf: false,
                      children: [
                        { id: 24, name: 'Node 3.1.1.1.1', expanded: false, leaf: true, children: [] },
                        { id: 25, name: 'Node 3.1.1.1.2', expanded: false, leaf: true, children: [] },
                      ],
                    },
                    {
                      id: 26,
                      name: 'Node 3.1.1.2',
                      expanded: false,
                      leaf: false,
                      children: [
                        {
                          id: 27,
                          name: 'Node 3.1.1.2.1',
                          expanded: false,
                          leaf: true,
                          children: [],
                        },
                      ],
                    },
                  ],
                },
              ],
            },
          ],
        },
      ],
    }
  },
  methods: {
    onSelectItem(item) {
      this.selectedItem = item
      console.log('Selected item:', item)
    },
    onExpandItem(item) {
      console.log('Expand/Collapse item:', item)
    },
  },
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: left;
  color: #2c3e50;
}
</style>

N-tree-item.vue

<template>
  <div class="tree-item" :style="{ paddingLeft: `${level * 8}px` }">
    <div class="tree-item-content" @click="onClickItem">
      <span class="expand-toggle" v-if="item.children && item.children.length" @click.stop="onToggleExpand">
        <van-icon :name="item.expanded ? 'arrow-down' : 'arrow'"></van-icon>
      </span>
      <span :class="{ selected: selectedItem && selectedItem.id === item.id }">{{ item.name }}</span>
      <span v-if="selectedItem && selectedItem.id === item.id" class="selected-icon">
        <van-icon name="certificate" />
      </span>
    </div>
    <div v-if="item.children && item.children.length && item.expanded">
      <N-tree-item
        v-for="(child, index) in item.children"
        :key="index"
        :item="child"
        :level="level + 1"
        @item-click="$emit('item-click', $event)"
        @toggle-expand="$emit('toggle-expand', $event)"
        :selected-item="selectedItem"
      />
    </div>
  </div>
</template>

<script>
export default {
  name: 'N-tree-item',
  props: {
    item: {
      type: Object,
      required: true,
    },
    level: {
      type: Number,
      default: 0,
    },
    selectedItem: {
      type: Object,
      default: () => null,
    },
  },
  methods: {
    onClickItem() {
      if (this.item && this.item.leaf) {
        this.$emit('item-click', this.item);
      }
      if (!this.item.leaf) {
        this.onToggleExpand()
      }
    },
    onToggleExpand() {
      this.$emit('toggle-expand', this.item, this.level); // 传递 level 参数
    },
  },
}
</script>


<style scoped>
.tree-item {
  margin-bottom: 5px;
}

.tree-item-content {
  cursor: pointer;
  line-height: 3rem;
  font-size: 1.2rem;
  border-bottom: 0.026667rem solid #D9D9D9;
  white-space: normal;
  word-break: break-word;
  position: relative;
  margin-left: 0.15rem;
}

.expand-toggle {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
  font-size: 1rem;
}

.cubeic-arrow, .cubeic-select {
  transition: transform 0.3s;
}

.selected {
  color: green;
  font-weight: bold;
}

.selected-icon {
  position: absolute;
  right: 5px;
  top: 50%;
  transform: translateY(-50%);
  color: green;
}

.cubeic-ok::before {
  content: "\E601";
  color: green;
  font-size: 25px;
}

.cubeic-arrow {
  font-size: 26px;
  color: #c8c8cd;
  margin-right: 8px;
}

.cubeic-select::before {
  content: "\E609";
  color: #c8c8cd;
  font-size: 25px;
  margin-right: 8px;
}
.van-icon-arrow-down:before {
  content: '\e65e';
  font-size: 10px;
  margin-right: 5px;
}
.van-icon-arrow:before {
  content: '\e660';
  font-size: 10px;
  margin-right: 5px;
}
</style>

N-tree.vue

<template>
  <div class="tree-container" style="border-top: 0.026667rem solid #D9D9D9;">
    <group label-width="auto" label-margin-right="2em" label-align="left">
      <N-tree-item
        v-for="(item, index) in treeData"
        :key="index"
        :item="item"
        :level="0"
        @item-click="onItemClick"
        @toggle-expand="onToggleExpand"
        :selected-item="selectedItem"
      />
    </group>
  </div>
</template>

<script>
import NTreeItem from './N-tree-item.vue'
export default {
  name: 'N-tree',
  components: {
    NTreeItem,
  },
  props: {
    treeData: {
      type: Array,
      default: () => [],
    },
    selectedItem: {
      type: Object,
      default: () => null,
    },
  },
  methods: {
    onItemClick(item) {
      this.$emit('item-click', item);
    },
    // 处理展开/折叠逻辑,确保第一层级只有一个节点展开
    onToggleExpand(item, level) {
      if (level === 0) {
        // 只在第一层级应用手风琴效果
        if (item.expanded) {
          // 如果节点已经展开,则将其折叠
          item.expanded = false;
        } else {
          // 如果要展开节点,则关闭同层级的其他节点及其所有子节点
          this.closeSiblingsAndChildren(this.treeData, item);
          // 然后展开当前节点
          item.expanded = true;
        }
      } else {
        // 对非第一层级的节点,正常展开/折叠
        item.expanded = !item.expanded;
      }
      this.$emit('toggle-expand', item);
    },
    // 递归关闭第一层级的兄弟节点及其所有子节点
    closeSiblingsAndChildren(nodes, currentItem) {
      nodes.forEach(node => {
        // 如果是第一层级的节点且不是当前点击的节点,则关闭
        if (node !== currentItem && node.expanded) {
          this.closeAllChildren(node); // 关闭该节点及其所有子节点
          node.expanded = false;
        }
      });
    },
    // 递归关闭所有子节点
    closeAllChildren(node) {
      if (node.children && node.children.length) {
        node.children.forEach(child => {
          child.expanded = false;
          // 递归关闭子节点的子节点
          this.closeAllChildren(child);
        });
      }
    }
  },
}
</script>

<style scoped>
</style>

index.html

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <title>Mobile Vue 2 App</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>

4.怎么用就不用我说了吧!