Vue编写一个自己的树形组件

一、需求情况

有时候用Vue开发的时候,不想使用组件库,但是遇到需要目录显示的情况的时候,想要一个树形递归的组件,可以自己封装定义想要的样式效果与事件,本文讲解如何实现一个自己的树形组件。

二、开发组件

Tree.vue父组件

html 复制代码
    <!--
     * @Author: 羊驼
     * @Date: 2024-03-04 11:43:47
     * @LastEditors: 羊驼
     * @LastEditTime: 2024-03-12 17:16:58
     * @Description: 树形组件的树
    -->
    <template>
      <ul>
        <tree-node
          v-for="node in nodes"
          :key="node.id"
          :node="node"
          :select_id="select_id"
          @node-clicked="handleNodeClicked"
        ></tree-node>
      </ul>
    </template>
    <script>
    import TreeNode from "./TreeNode.vue";
    export default {
      components: {
        TreeNode,
      },
      props: {
        // 节点数据
        nodes: Array,
        // 当前选中的节点
        select_id: String | Number,
      },
      methods: {
        // 定义一个节点点击事件
        handleNodeClicked(clickedNode) {
          this.$emit("node-clicked", clickedNode);
        },
      },
    };
    </script>

    <style scoped>
    ul {
      list-style: none;
    }
    </style>

TreeNode 节点组件

html 复制代码
    <!--
     * @Author: 羊驼
     * @Date: 2024-03-04 11:42:24
     * @LastEditors: 羊驼
     * @LastEditTime: 2024-03-12 17:19:30
     * @Description: 树形节点
    -->
    <template>
      <div>
        <li
          :class="{'a-selected':select_id==node.id}"
          class="flex"
          ref="text"
          @click="childClicked(node)"
        >
          <div
            class="icon-box"
            v-if="haveChild"
          >
            <svg
              @click="setExpanded(!isExpanded)"
              t="1709605135944"
              class="icon"
              viewBox="0 0 1024 1024"
              version="1.1"
              xmlns="http://www.w3.org/2000/svg"
              p-id="2647"
              width="25"
              height="25"
              :class="{rotate:isExpanded}"
              :fill="select_id==node.id?'#ccc':'#000'"
            >
              <path
                d="M500.8 604.779L267.307 371.392l-45.227 45.27 278.741 278.613L779.307 416.66l-45.248-45.248z"
                p-id="2648"
              ></path>
            </svg>
          </div>
          <span>{{node.name}}</span>
        </li>
        <ul v-if="haveChild&&isExpanded">
          <tree-node
            v-for="child in node.children"
            :key="child.id"
            :node="child"
            :select_id="select_id"
            @node-clicked="childClicked"
          ></tree-node>
        </ul>
      </div>
    </template>

    <script>
    export default {
      // 因为是递归组件 导入的时候一定要以懒加载的形式 不然会报错的
      components: { "tree-node": () => import("./TreeNode.vue") },
      data() {
        return {
          // 展开情况
          isExpanded: false,
        };
      },
      props: {
        node: Object,
        select_id: String | Number,
      },
      computed: {
        // 判断是否可以展开
        haveChild() {
          return this.node.children && this.node.children.length;
        },
      },
      methods: {
        // 设置展开情况
        setExpanded(value) {
          this.isExpanded = value;
        },
        // 点击情况
        childClicked(clickedNode) {
          this.$emit("node-clicked", clickedNode);
        },
      },
    };
    </script>

    <style scoped>
    .icon-box {
      width: 40px !important;
      display: flex;
    }
    .icon {
      text-align: center;
      margin-right: 10px;
      transition: all 0.2s ease;
      transform: rotate(-90deg);
    }
    ul {
      list-style: none;
      padding-left: 20px !important;
    }
    .flex {
      display: flex;
      align-items: center;
      padding: 10px 20px;
      user-select: none;
    }

    .flex a {
      padding: 0 !important;
    }

    .flex:hover {
      background: #f2f9f5 !important;
      color: #008737 !important;
    }

    .flex:hover a {
      background: #f2f9f5 !important;
      color: #008737 !important;
    }

    .a-selected,
    .a-selected a {
      background: #008737 !important;
      color: #fff !important;
    }
    .rotate {
      transform: rotate(0);
    }
    </style>

三、使用组件

html 复制代码
    <!--
     * @Author: 羊驼
     * @Date: 2024-03-12 16:49:46
     * @LastEditors: 羊驼
     * @LastEditTime: 2024-03-12 17:21:17
     * @Description: 示例
    -->
    <template>
      <div>
        <tree
          :nodes="nodes"
          :select_id="select_id"
          @node-clicked="handleNodeClicked"
        >
        </tree>
      </div>
    </template>

    <script>
    import Tree from "./components/Tree.vue";
    export default {
      // 组件引入
      components: { Tree },
      data() {
        return {
          // 测试数据
          nodes: [
            {
              id: 1,
              name: "测试1",
              children: [
                {
                  id: 2,
                  name: "测试1-1",
                  children: [
                    {
                      id: 3,
                      name: "测试2-1",
                    },
                  ],
                },
                {
                  id: 4,
                  name: "测试1-2",
                },
              ],
            },
            {
              id: 6,
              name: "测试2",
            },
          ],
          select_id: "",
        };
      },
      methods: {
        // 节点选中后
        handleNodeClicked(node) {
          this.select_id = node.id;
        },
      },
    };
    </script>

    <style>
    </style>

四、效果展示

​编辑

最简版本 后续有什么需求 可以自己定义插槽和事件实现

相关推荐
卸任4 小时前
Vibe coding工具使用心得
前端·ai编程
brien95 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance5 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M5 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写5 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李5 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易5 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马6 小时前
前沿视界
前端
kyriewen6 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易6 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员