【vue】el-tree的新增/编辑/删除节点

1、概述

关于树形结构的新增同级节点新增子级节点修改节点名称删除节点等四种操作,各种参数配置完全继承el-tree,本篇使用vue2 + element-ui

2、效果图展示

3、调用方式

<template>
    <Tree
        :data="treeData"
        :props="defaultProps"
        :default-expanded-keys="expandedKeys"
        node-key="id"
        highlight-current
        :highligh-color="highlighColor"
        :show-btn-group="showBtnGroup"
        :draggable="true"
        @node-click="nodeClick"
        @editNodeSubmit="addNode"
        @deleteNodeSubmit="deleteNode"
        @node-drag-end="handleDragEnd"
      />
</template>      

<script>
export default {
    name: 'Tree',
    data() {
        return {
          defaultProps: {
            children: 'children',
            label: 'label'
          },
          expandedKeys: [], // 展开节点
          highlighColor: {
            color: '#FFAE0D',
            bgColor: 'rgba(255, 174, 13, .1)'
          },
          showBtnGroup: true,
          treeData: []
        }
    }
}
</script>

4、源码解析

<template>
  <el-tree
    ref="tree"
    :class="[
      'menu-el-tree',
      iconStyle.src && 'menu-expand-icon',
      highlighColor && 'menu-node-highligh'
    ]"
    :style="{
      '--bgUrl': 'url(' + iconStyle.src + ')',
      '--iconWidth': iconStyle.width,
      '--iconHeight': iconStyle.height,
      '--iconTransform': iconStyle.transform,
      '--hoverColor': hoverStyle.color,
      '--hoverBgColor': hoverStyle.bgColor,
      '--highlighColor': highlighColor.color,
      '--highlighBgColor': highlighColor.bgColor
    }"
    node-key="id"
    :draggable="draggable"
    v-bind="$attrs"
    v-on="$listeners"
  >
    <template v-slot="{ node, data }">
      <div v-if="!data.isEdit" class="custom-tree-node-root" @mouseenter="nodeMouseEnter(node)" @mouseleave="nodeMouseLeve(node)">
        <span class="custom-tree-node">{{ node.label }}</span>
        <section v-show="node.showBtn" class="tree-btn-group">
          <el-tooltip
            v-for="item in btnGroup"
            :key="item"
            effect="light"
            placement="top"
            popper-class="common-tooltip-primary"
            :content="{
              'addChild': '新增子级',
              'delete': '删除',
              'edit': '修改',
              'addSibling': '新增同级'
            }[item]"
          >
            <i
              :class="{
                'addChild': 'el-icon-circle-plus-outline',
                'delete': 'el-icon-circle-close',
                'edit': 'el-icon-edit',
                'addSibling': 'el-icon-plus'
              }[item]"
              @click.stop="() => handleOperaion(item, node, data)"
            />
          </el-tooltip>
        </section>
      </div>
      <div v-else class="custom-tree-node-root-edit">
        <el-input v-model="inputValue" :size="editInputStyle.size" :style="{ width: editInputStyle.width }" />
        <section :id="data.id">
          <el-tooltip
            content="确定"
            popper-class="common-tooltip-primary"
            effect="light"
            placement="top"
          >
            <i class="el-icon-success" @click.stop="() => editMenuSubmit(node, data)" />
          </el-tooltip>
          <el-tooltip
            content="取消"
            popper-class="common-tooltip-primary"
            effect="light"
            placement="top"
          >
            <i class="el-icon-error" @click.stop="() => addSiblingCancel(node, data)" />
          </el-tooltip>
        </section>
      </div>
    </template>
  </el-tree>
</template>
<script>
export default {
  name: 'MenuTree',
  inheritAttrs: true,
  props: {
    hoverStyle: {
      type: Object,
      default: () => ({
        color: '#606266',
        bgColor: '#F5F7FA'
      })
    },
    /**
     * @description 当前行选中高亮颜色配置
     */
    highlighColor: {
      type: Object,
      default: () => ({
        color: '#606266',
        bgColor: '#fff9ec'
      })
    },
    /**
     * @description 动态左侧图标配置(图片格式)
     */
    iconStyle: {
      type: Object,
      default: () => ({
        src: '',
        width: '20px',
        height: '17px',
        transform: 'rotate(270deg)'
      })
    },
    /**
     * @description 操作按钮组
     * addChild => 新增子级,delete => 删除, edit => 修改, addSibling => 新增同级
     */
    btnGroup: {
      type: Array,
      default: () => ['edit', 'addSibling', 'addChild', 'delete']
    },
    /**
     * @description 是否展示操作按钮
     */
    showBtnGroup: {
      type: Boolean,
      default: false
    },
    /**
     * @description 编辑模式输入框样式
     */
    editInputStyle: {
      type: Object,
      default: () => ({
        size: 'mini',
        width: '200px'
      })
    },
    /**
     * 是否可拖拽
     */
    draggable: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      inputValue: ''
    }
  },
  methods: {
    /**
     * @description 鼠标移入目录层级
     */
    nodeMouseEnter(data) {
      if (!this.showBtnGroup) return;
      this.$set(data, 'showBtn', true)
    },
    /**
     * @description 鼠标移除目录层级
     */
    nodeMouseLeve(data) {
      if (!this.showBtnGroup) return;
      this.$set(data, 'showBtn', false)
    },
    /**
     * 当前目录操作分发
     * @param {string} eventKey 事件名称
     * @param {object} currentNode 当前Node节点内容
     * @param {object} data 当前节点数据
     */
    handleOperaion(eventKey, currentNode, data) {
      console.log(currentNode, data, 'menuRoot');
      switch (eventKey) {
        case 'addSibling':
          this.addSibling(currentNode, data);
          break;
        case 'edit':
          this.editTreeItem(data);
          break;
        case 'addChild':
          this.addChild(currentNode, data);
          break;
        case 'delete':
          this.deleteTreeItem(currentNode, data);
          break;
      }
    },
    /**
     * @description 编辑当前节点
     */
    editTreeItem(data) {
      this.$set(data, 'isEdit', true);
      this.inputValue = data.label; // 当前正在编辑内容赋值
      this.$nextTick(() => {
        document.getElementById(data.id).previousElementSibling.firstElementChild.focus();
      })
    },
    /**
     * @description 添加同级节点
     */
    addSibling(currentNode, data) {
      const treeDOM = this.$refs.tree;
      const id = Math.ceil(Math.random() * 100)
      const newData = { id: id, pId: data.pId, label: '', isEdit: true, isNew: true, children: [] };
      treeDOM.insertAfter(newData, currentNode);
      // 聚焦当前新增目录
      this.$nextTick(() => {
        document.getElementById(newData.id).previousElementSibling.firstElementChild.focus();
      })
    },
    /**
     * @description 添加子级节点
     */
    addChild(currentNode, data) {
      const treeDOM = this.$refs.tree;
      const id = Math.ceil(Math.random() * 100)
      const newData = { id: id, pId: data.id, label: '', isEdit: true, isNew: true, children: [] };
      treeDOM.append(newData, currentNode);
      // 展开子节点后才能获取DOM聚焦
      treeDOM.store.nodesMap[data.id].expanded = true;
      setTimeout(() => {
        document.getElementById(newData.id).previousElementSibling.firstElementChild.focus();
      }, 500)
    },
    /**
     * @description 编辑模式修改确认
     */
    editMenuSubmit(node, data) {
      this.$emit('editNodeSubmit', {
        node,
        data,
        currentLabel: this.inputValue,
        // 新增同级/子级节点接口调用成功的话,即新增同级/子级节点
        callback: (status) => {
          if (status) {
            this.resetNode()
          }
        }
      });
    },
    /**
     * @description 删除当前节点
     */
    deleteTreeItem(node, data) {
      this.$emit('deleteNodeSubmit', {
        node,
        data,
        callback: (status) => {
          // 删除接口调用成功的话,即删除节点
          if (status) {
            const treeDOM = this.$refs.tree;
            treeDOM.remove(node);
          }
        }
      });
    },
    /**
     * @description 取消同级节点添加
     */
    addSiblingCancel(node, data) {
      // 如果是新增的节点,取消即是删除
      if (data.isNew) {
        const treeDOM = this.$refs.tree;
        treeDOM.remove(node);
      } else {
        // 重置修改内容
        this.inputValue = '';
        data.isEdit = false;
      }
    },
    /**
     * 寻找第一个叶子节点及叶子节点的父节点
     * @param {*} tree 平铺数组
     */
    findFirstChildAndParent(tree) {
      let firstChild = null;
      let parentOfFirstChild = null;
      const dfs = (node, parent) => {
        if (firstChild !== null) {
          return; // 如果已经找到了第一个子节点,则不再继续搜索
        }
        if (node.children && node.children.length > 0) {
          // eslint-disable-next-line
          for (const child of node.children) {
            dfs(child, node);
          }
        } else {
          firstChild = node;
          parentOfFirstChild = parent;
        }
      }
      // eslint-disable-next-line
      for (const node of tree) {
        dfs(node, null);
      }
      return {
        firstChild,
        parentOfFirstChild
      };
    },
    /**
     * 获取自身树结构实例
     */
    getTree() {
      return this.$refs.tree;
    },
    /**
     * 重置节点数据
     */
    resetNode() {
      this.inputValue = '';
    },
    /**
     * 寻找节点对应的父级节点
     * @param {*} tree
     * @param {*} nodeId
     */
    findParentByChildId(tree, nodeId) {
      let parentOfFirstChild = null;
      const dfs = (node, parent) => {
        if (parentOfFirstChild !== null) {
          return;
        }
        if (node.children && node.children.length > 0) {
          // eslint-disable-next-line
          for (const child of node.children) {
            dfs(child, node);
          }
        } else {
          // 找到对应节点后,返回其父节点
          if (node.id === nodeId) {
            parentOfFirstChild = parent;
          }
        }
      }
      // eslint-disable-next-line
      for (const node of tree) {
        dfs(node, null);
      }
      return parentOfFirstChild
    }
  }
}
</script>
<style scoped lang="scss">
// 动态配置右侧图标
.menu-expand-icon {
  ::v-deep .el-tree-node__expand-icon:not(.is-leaf) {
    &::before {
      background: var(--bgUrl);
      background-size: contain;
      background-repeat: no-repeat;
      background-position: center;
      content: '';
      width: var(--iconWidth);
      height: var(--iconHeight);
      display: inline-block;
      transform: var(--iconTransform);
    }
  }
}
// 动态配置hover样式
.menu-el-tree {
  ::v-deep .el-tree-node__content {
    &:hover {
      background: var(--hoverBgColor);
      color: var(--hoverColor);
    }
  }
  .custom-tree-node-root {
    display: flex;
    align-items: center;
    flex: 1;
    .tree-btn-group {
      margin-left: 15px;
      display: flex;
      align-items: center;
      column-gap: 8px;
      i {
        font-size: 15px;
        &:hover {
          color: $primary
        }
      }
    }
  }
}
.menu-node-highligh.el-tree--highlight-current {
  ::v-deep .el-tree-node.is-current>.el-tree-node__content {
    background: var(--highlighBgColor);
    color: var(--highlighColor);
  }
}
.custom-tree-node-root-edit {
  display: flex;
  align-items: center;
  .el-input, ::v-deep .el-input .el-input__inner {
    height: 26px;
  }
  section {
    i {
      margin-left: 10px;
      font-size: 15px;
      color: $primary;
    }
  }
}
</style>

5、疑难解答

1、拖拽节点时,如何监听和区分拖拽事件?

使用的是el-tree内置的node-drag-end事件,当拖拽完成时触发,抛出四个参数

* @param {*} draggingNode 被拖拽的节点

 * @param {*} dropNode 最后进入的节点

 * @param {*} dropType 相对比的位置

 * @param {*} ev 事件

2、新增和删除节点,如何再触发后端接口后再执行节点的新增和删除?

节点新增和删除执行下列两个事件

@editNodeSubmit="addNode"

@deleteNodeSubmit="deleteNode"

他们回调的最后一个参数都是callback,当callback(true)传入为true时,则代表接口成功,则进行后续的树节点操作。

------有不懂的或建议欢迎直接评论噢~------

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/560407.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

上位机图像处理和嵌入式模块部署(树莓派4b和视觉slam十四讲)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 实际使用中&#xff0c;树莓派4b是非常好的一个基础平台。本身板子价格也不是很贵&#xff0c;建议大家多多使用。之前关于vslam&#xff0c;也就是…

CSS display属性

目录 概述&#xff1a; 设置display示例&#xff1a; none&#xff1a; block&#xff1a; inline&#xff1a; inline-block &#xff1a; 概述&#xff1a; 在CSS中我们可以使用display属性来控制元素的布局&#xff0c;我们可以通过display来设置元素的类型。 在不设置…

webpack源码分析——enhanced-resolve库之getType、normalize、join和cachedJoin函数

一、PathType 路径类型 const PathType Object.freeze({Empty: 0, // 空Normal: 1, // 默认值Relative: 2, // 相对路径AbsoluteWin: 3, // win 下的绝对路径AbsolutePosix: 4, // posix 下的绝对路径Internal: 5 // enhanced-resolve 内部自定义的一种类型&#xff0c;具体是…

Redis:报错Creating Server TCP listening socket *:6379: bind: No error

错误&#xff1a; window下启动redis服务报错&#xff1a; Creating Server TCP listening socket *:6379: bind: No error 原因&#xff1a; 端口6379已被绑定&#xff0c;应该是因为上次未关闭服务 解决&#xff1a; ①依次输入命令&#xff1a; redis-cli.exe &#xff08…

IntelliJ IDEA运行发布传统Java Web Application项目

接 重温8年前项目部署 要求&#xff0c;如何改用IntelliJ IDEA运行发布传统 Java Web Application项目呢&#xff0c;简述步骤如下&#xff1a; 一、下载源码 源码&#xff1a;https://github.com/wysheng/kindergarten 下载后的本地项目路径&#xff1a;/Users/songjianyon…

前后端跨域请求代码实战(vue3.4+springboot2.7.18)

前端代码 v3.4.21&#xff08;前端不是主业&#xff0c;所以就贴一贴代码&#xff0c;有疑问评论区见&#xff09;后端代码&#xff0c;springboot 2.7.18&#xff08;后端&#xff09; 文章内容&#xff1a; 一&#xff0c;后端代码 二&#xff0c;前端代码 三&#xff0c;后…

安全开发实战(1)--Cdn

目录 安全开发专栏 CDN介绍 1.信息收集阶段 1.1判断CDN是否存在 1.1.1, One 1.1.2,Two(改进) 1.1.3,进行整合 增加输入功能 1.1.4 批量读取监测存储(进行测试) 问题1: 问题2: 解决方案: 1.1.4 基本编写完成 命令框中: cdn存在.txt 总结 这里我是根据整个渗透测…

个人网页地址发布页面源码

源码介绍 个人网页地址发布页面源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面 效果预览 源码下载 个人网页地址发布页面源码

利用搞笑电影,引爆中年圈,日入2000+,短视频最新变现玩法,适合0基础小白

大家好&#xff0c;今天要分享的项目是“通过搞笑电影吸引中年群体&#xff0c;实现日收入2000的短视频变现新策略&#xff0c;适合零基础新手”。该项目着眼于利用搞笑电影内容来吸引中年观众&#xff0c;这是一个相对未被充分开发的市场领域&#xff0c;竞争较少。与其他热门…

香港服务器_免备案服务器有哪些正规的?企业、建站方向

香港服务器&#xff0c;是最受欢迎的外贸、企业建站服务器&#xff0c;在个人建站领域&#xff0c;香港服务器、香港虚拟主机都是首选的网站服务器托管方案&#xff0c;不仅其具备免备案的特点&#xff0c;而且国内外地区访问速度都很快。那么&#xff0c;现今2024年个人和企业…

企业监管工具:为何如此重要?

随着通信技术的发展&#xff0c;员工使用微信等即时通讯工具来进行工作沟通已经成为了常态。为了帮助企业有效地监管员工的工作微信使用情况&#xff0c;微信管理系统应运而生。 下面就一起来看看&#xff0c;它都有哪些功能吧&#xff01; 1、历史消息&#xff1a;洞察员工聊…

力扣---从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,null,null,15,7]示…

IO——进程间通讯 IPC

1. 进程间通信方式 (1) 早期进程间通信&#xff1a; 无名管道(pipe)、有名管道(fifo)、信号(signal) (2) system V IPC&#xff1a; 共享内存(shared memory)、消息队列(message queue)、信号灯集(semaphore set) (3) BSD&#xff1a; 套接字(socket) 2. 无名管道 2.1特点 …

泛微E9开发 快速隐藏明细表列

快速隐藏明细表列 1、隐藏列方法&#xff08;不作用&#xff0c;一直隐藏&#xff09; 在实际运用中&#xff0c;用户不需要但是需要间接使用的列&#xff0c;我们可以通过右击该列-【列自定义属性】-在“列自定义属性”菜单中启用“隐藏列”功能。 根据该方法设置的前端页…

基于Springboot的社区疫情返乡管控系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的社区疫情返乡管控系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

OpenHarmony 上传和下载(API 10)教程~

介绍 本示例使用ohos.request接口创建上传和下载任务&#xff0c;实现上传、下载功能&#xff0c;hfs作为服务器&#xff0c;实现了文件的上传和下载和任务的查询功能。 效果预览 使用说明 1.本示例功能需要先配置服务器环境后使用&#xff0c;具体配置见上传下载服务配置。…

中颖51芯片学习7. printf重定向到串口与自定义日志输出函数

中颖51芯片学习7. printf重定向到串口与自定义日志输出函数 一、 printf 重定向1. 概念2. 实现方式3. C51 中printf数值格式化 二、日志函数1. 实现方案分析2. 代码&#xff08;1&#xff09;log_utils.h&#xff08;2&#xff09;main.c 3. 通过预定义宏实现日志分级输出&…

汇编语言88888

汇编语言安装指南 第一步&#xff1a;在github上下载汇编语言的安装包 网址&#xff1a;GitHub - HaiPenglai/bilibili_assembly: B站-汇编语言-pdf、代码、环境等资料B站-汇编语言-pdf、代码、环境等资料. Contribute to HaiPenglai/bilibili_assembly development by creat…

Flyweight 享元

意图 运用共享技术有效地支持大量细粒度的对象。 结构 其中 Flyweight描述一个接口&#xff0c;通过这个接口Flyweight可以接受并作用于外部状态。ConcreteFlyweight实现Flyweight接口&#xff0c;并作为内部状态&#xff08;如果有&#xff09;增加存储空间。ConcreteFlywe…

数字阅览室解决方案

一、方案概述 “数字阅览室”概念一经提出&#xff0c;就得到了广泛的关注&#xff0c;纷纷组织力量进行探讨、研究和开发&#xff0c;进行各种模型的试验。随着数字地球概念、技术、应用领域的发展&#xff0c;数字阅览室已成为数字地球家庭的成员&#xff0c;为信息高速公路…