我们来自己搞一个树组件

1. 创建 Vue 项目

如果你还没有创建 Vue 项目,可以使用 Vue CLI 快速创建:

vue create my-tree-component
cd my-tree-component

2. 安装依赖

确保安装了 Vue Router 和 Vuex(如果需要状态管理):

npm install vue-router vuex --save

3. 创建 Tree 组件

src/components 目录下创建 Tree.vue 文件:

<template>
  <div class="tree">
    <ul>
      <tree-node v-for="node in data" :key="node.id" :node="node"></tree-node>
    </ul>
  </div>
</template>

<script>
import TreeNode from './TreeNode.vue';

export default {
  name: 'Tree',
  components: {
    TreeNode
  },
  props: {
    data: {
      type: Array,
      required: true
    }
  }
};
</script>

<style scoped>
.tree ul {
  list-style-type: none;
  padding-left: 20px;
}
</style>

4. 创建 TreeNode 组件

src/components 目录下创建 TreeNode.vue 文件:

<template>
  <li>
    <span @click="toggle">{{ node.name }}</span>
    <ul v-if="isOpen">
      <tree-node v-for="child in node.children" :key="child.id" :node="child"></tree-node>
    </ul>
  </li>
</template>

<script>
export default {
  name: 'TreeNode',
  props: {
    node: {
      type: Object,
      required: true
    }
  },
  data() {
    return {
      isOpen: false
    };
  },
  methods: {
    toggle() {
      this.isOpen = !this.isOpen;
    }
  }
};
</script>

<style scoped>
li {
  cursor: pointer;
}
</style>

5. 使用 Tree 组件

src/App.vue 中使用 Tree 组件:

<template>
  <div id="app">
    <tree :data="treeData"></tree>
  </div>
</template>

<script>
import Tree from './components/Tree.vue';

export default {
  name: 'App',
  components: {
    Tree
  },
  data() {
    return {
      treeData: [
        {
          id: 1,
          name: 'Node 1',
          children: [
            { id: 2, name: 'Child Node 1' },
            { id: 3, name: 'Child Node 2' }
          ]
        },
        {
          id: 4,
          name: 'Node 2',
          children: [
            { id: 5, name: 'Child Node 3' }
          ]
        }
      ]
    };
  }
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

6. 运行项目

运行项目以查看效果:

npm run serve

7. 功能扩展

  • 搜索功能:添加一个搜索框,根据输入过滤树节点。
  • 拖拽排序:使用第三方库如 vuedraggable 实现节点的拖拽排序。
  • 懒加载:对于大型树结构,可以实现懒加载,仅在展开节点时加载子节点数据。

功能扩展的实现

1. 搜索功能

修改 Tree.vue 组件

Tree.vue 中添加一个搜索框,并根据输入过滤树节点。

<template>
  <div class="tree">
    <input type="text" v-model="searchQuery" placeholder="Search..." />
    <ul>
      <tree-node v-for="node in filteredData" :key="node.id" :node="node"></tree-node>
    </ul>
  </div>
</template>

<script>
import TreeNode from './TreeNode.vue';
import { filterTree } from '@/utils/treeUtils';

export default {
  name: 'Tree',
  components: {
    TreeNode
  },
  props: {
    data: {
      type: Array,
      required: true
    }
  },
  data() {
    return {
      searchQuery: ''
    };
  },
  computed: {
    filteredData() {
      if (!this.searchQuery) return this.data;
      return filterTree(this.data, this.searchQuery);
    }
  }
};
</script>

<style scoped>
.tree ul {
  list-style-type: none;
  padding-left: 20px;
}
</style>
创建 treeUtils.js 工具文件

src/utils 目录下创建 treeUtils.js 文件,用于实现树节点的过滤功能。

export function filterTree(data, query) {
  const lowerCaseQuery = query.toLowerCase();
  return data.filter(node => {
    if (node.name.toLowerCase().includes(lowerCaseQuery)) {
      return true;
    }
    if (node.children && node.children.length > 0) {
      node.children = filterTree(node.children, query);
      return node.children.length > 0;
    }
    return false;
  });
}

2. 拖拽排序

安装 vuedraggable

首先,安装 vuedraggable 库:

npm install vuedraggable
修改 Tree.vue 组件

Tree.vue 中引入 vuedraggable 并使用它来实现拖拽排序。

<template>
  <div class="tree">
    <input type="text" v-model="searchQuery" placeholder="Search..." />
    <draggable v-model="filteredData" @change="onDragChange">
      <tree-node v-for="node in filteredData" :key="node.id" :node="node"></tree-node>
    </draggable>
  </div>
</template>

<script>
import TreeNode from './TreeNode.vue';
import draggable from 'vuedraggable';
import { filterTree } from '@/utils/treeUtils';

export default {
  name: 'Tree',
  components: {
    TreeNode,
    draggable
  },
  props: {
    data: {
      type: Array,
      required: true
    }
  },
  data() {
    return {
      searchQuery: '',
      localData: [...this.data]
    };
  },
  computed: {
    filteredData: {
      get() {
        if (!this.searchQuery) return this.localData;
        return filterTree(this.localData, this.searchQuery);
      },
      set(value) {
        this.localData = value;
      }
    }
  },
  methods: {
    onDragChange(event) {
      console.log('Drag change:', event);
      // 处理拖拽后的变化,例如更新父组件的数据
      this.$emit('update:data', this.localData);
    }
  }
};
</script>

<style scoped>
.tree ul {
  list-style-type: none;
  padding-left: 20px;
}
</style>

3. 懒加载

修改 TreeNode.vue 组件

TreeNode.vue 中实现懒加载功能。

<template>
  <li>
    <span @click="toggle">{{ node.name }}</span>
    <ul v-if="isOpen">
      <draggable v-model="node.children" @change="onDragChange">
        <tree-node v-for="child in node.children" :key="child.id" :node="child"></tree-node>
      </draggable>
    </ul>
  </li>
</template>

<script>
import draggable from 'vuedraggable';

export default {
  name: 'TreeNode',
  components: {
    draggable
  },
  props: {
    node: {
      type: Object,
      required: true
    }
  },
  data() {
    return {
      isOpen: false,
      hasLoadedChildren: false
    };
  },
  methods: {
    toggle() {
      if (!this.hasLoadedChildren && this.node.children && this.node.children.length === 0) {
        this.loadChildren();
      }
      this.isOpen = !this.isOpen;
    },
    loadChildren() {
      // 模拟异步加载子节点
      setTimeout(() => {
        this.node.children = [
          { id: 101, name: 'Lazy Child 1' },
          { id: 102, name: 'Lazy Child 2' }
        ];
        this.hasLoadedChildren = true;
      }, 1000);
    },
    onDragChange(event) {
      console.log('Drag change:', event);
      // 处理拖拽后的变化,例如更新父组件的数据
      this.$emit('update:node', { ...this.node, children: this.node.children });
    }
  }
};
</script>

<style scoped>
li {
  cursor: pointer;
}
</style>

4. 更新 App.vue

确保 App.vue 中的 Tree 组件能够接收和处理数据更新。

<template>
  <div id="app">
    <tree :data="treeData" @update:data="updateTreeData"></tree>
  </div>
</template>

<script>
import Tree from './components/Tree.vue';

export default {
  name: 'App',
  components: {
    Tree
  },
  data() {
    return {
      treeData: [
        {
          id: 1,
          name: 'Node 1',
          children: [
            { id: 2, name: 'Child Node 1' },
            { id: 3, name: 'Child Node 2' }
          ]
        },
        {
          id: 4,
          name: 'Node 2',
          children: [
            { id: 5, name: 'Child Node 3' }
          ]
        }
      ]
    };
  },
  methods: {
    updateTreeData(newData) {
      this.treeData = newData;
    }
  }
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

5. 运行项目

运行项目以查看效果:

npm run serve

基本的功能就都具备了,当然实际生产中可能会碰到各种新的需求,慢慢来扩展即可。

11-12 20:16