ElementUI-tree拖拽功能与节点自定义

前言

在管理端会遇到多分类时,要求有层次展示出来,并且每个分类有额外的操作。例如:添加分类、编辑分类、删除、拖到分类等。

下面将会记录这样的一个需求实习过程。

了解需求

  1. 分类展示按层级展示
  2. 分类根据特定的参数展示可以操作的按钮,分类的操作有增、删、改
  3. 分类还支持拖拽功能,并不是所有的分类都支持拖拽
  4. 点分类去执行别的操作。例如:刷新数据(不实现)
  5. 增加分类之后刷新分类数据,当前选择的分类为增加的分类
  6. 删除分类后回到上一级分类
  7. 右击分类和点击操作按钮均可以弹出操作弹窗
  8. 点击分类前的箭头可展开和折叠分类

效果图

  • 分类展示

  • 分类操作的弹窗

组件库

采用ElementUI 中的 Tree树形控件、Dropdown下拉菜单

  • Tree树形控件:Element - The world's most popular Vue UI framework
  • Dropdown下拉菜单dropdown:Element - The world's most popular Vue UI framework

开始编码

搭建tree 组件

  •   html 部分:
<el-tree :data="classifyData" node-key="id" draggable ref="tree" :accordion="false" 
      auto-expand-parent :default-expanded-keys="[checkedId]" :props="defaultProps"
      :allow-drop="allowDrop" :allow-drag="allowDrag"
      @node-drag-start="handleDragStart" @node-drop="handleDrop"
      @node-click="nodeClick" @node-contextmenu="rightClick"
      :show-checkbox="false" :check-strictly="true"  >
        <div class="custom-tree-node" slot-scope="{ node, data }">
          <span>{{ data.name }}</span> 
          <span>
            <el-dropdown type="primary" trigger="click" :ref="'messageDrop'+data.id" @visible-change="controlCheckedKeys">
              <span class="el-dropdown-link" @click.stop="setSeletKey(data.id)">  
                <img src="~@/../more-active.png" v-if="checkedKeys == data.id"  class="myicon-opt" /> 
                <img src="~@/../more.png" v-else class="myicon-opt" />
              </span>
              <el-dropdown-menu slot="dropdown">
                <el-dropdown-item v-if="data.is_add_classify">
                  <div @click="openClassify(data.id,'新增子分类')">
                    <img src="~@/../add.png" class="myicon-opt"/> 
                    新增子分类
                  </div>
                </el-dropdown-item>
                <el-dropdown-item v-if="data.is_edit_sort">
                  <div @click="editClassify(data)"> 
                    <img src="~@/../edit.png" class="myicon-opt" /> 
                    修改
                  </div>
                </el-dropdown-item>
                <el-dropdown-item v-if="data.is_edit_sort">
                  <div @click="delBefore(data.id,data.parent_id)">
                    <img src="~@/../del.png" class="myicon-opt" />
                    删除
                  </div>
                </el-dropdown-item>
              </el-dropdown-menu>
            </el-dropdown>
          </span>
        </div>
      </el-tree>
  • css
<style lang="stylus" scoped>
.active{
  background: #F2F6F9;
  color: #409EFF;
}
.classify{
  padding : 0 16px;
  height: 40px;
  font-family: PingFangSC-Medium;
  font-weight: 500;
  font-size: 15px; 
  line-height:40px;
}
 
.el-tree ::v-deep {
  .el-tree-node__content{ 
    @extend .classify;
    &:hover{
      @extend .active; 
    }
    .el-tree-node__expand-icon.is-leaf{
      // display:none
      margin-left:-12px
    }
  }
  .is-checked > .el-tree-node__content{
    @extend .active;
  } 
} 
.custom-tree-node{
  display: flex;
  justify-content: space-between;
  width: 100%;
}
.myicon-opt{
  vertical-align: middle;
  width: 16px;
  height: 16px;
} 
</style>
  • js
<script>
  export default {
    props:{
      activeId:{
        type:[String,Number],
        default:''
      },
      classifyData:{
        type:Array,
        default:[]
      }
    },
    watch:{
      activeId: {
        handler(v,o){
          // v 值为0时, 0 == '' 值为true
          if (typeof v == 'number') {
            this.checkedId = v 
            this.$nextTick(()=>{
              this.$refs.tree.setCheckedKeys([v])
            }) 
          }
        },
        immediate:true,
        deep:true 
      },
    },
    data() {
      return {
        checkedId:'', 
        checkedKeys:'', 
        defaultProps: {
          children: 'child',
          label: 'name'
        },
        classifyCofig:{
          flag:false,
          Id: '',
          title:'',
          value:''
        }, 
      }
    },
    methods: { 
      // 点击分类名称
      nodeClick(data,node){ 
        this.checkedId = data.id  
        this.$refs.tree.setCheckedKeys([data.id]) 
        node.expanded = true
        this.$emit('selectId',data.id)
        // console.log('node',data.id,node.parent)
        let addId  = [ data.id]
        if(node.parent.parent != null)  this.selectNode(addId,node.parent)
        // console.log('addId',addId)
        this.$emit('selectaddId', addId)
      },
      // 获取多层级的父类id加入到数组下标为0的位置
      selectNode(id,node){ 
        id.unshift(node.data.id)
        if(node.parent.parent != null){
          this.selectNode(id,node.parent)
        } 
      },
      // 右击分类
      rightClick(event,data, Node, element){ 
        setTimeout(()=>{
          this.checkedKeys = data.id 
          this.$refs['messageDrop'+data.id].show() 
        })
      },
      // 点击操作按钮
      setSeletKey(k){ 
        setTimeout(()=>{
          this.checkedKeys = k
        })
      },
      // 下拉菜单的异步监听,打开(true)还是隐藏(flase)
      controlCheckedKeys(flag){  
        if(!flag){
          this.checkedKeys = ''
        }
      },
      // 节点开始拖拽时触发的事件
      handleDragStart(node) {
        if(!node.data.is_edit_sort){
          return false
        } 
      }, 
      // 拖拽成功完成时触发的事件
      handleDrop(draggingNode, dropNode, dropType) {
        if(dropType == 'none') return 
        // 准备排序参数可自行更改
        let params = {
          pk1: draggingNode.data.id,
          pk2: dropNode.data.id,
          direction:dropType == 'before' ? -1 : 1
        }
        this.orderClassify(params)
      }, 
      /** 
       *  拖拽时判定目标节点能否被放置。
       * @param {*} draggingNode 
       * @param {*} dropNode 
       * @param {*} type 参数有三种情况:'prev'、'inner' 和 'next',分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
       */
      allowDrop(draggingNode, dropNode, type) {
        if (draggingNode.level === dropNode.level) { 
          if (draggingNode.data.parent_id === dropNode.data.parent_id && dropNode.data.is_edit_sort) {
            // 向上拖拽 || 向下拖拽
            return type === "prev" || type === "next"
          }
        } else {
          // 不同级进行处理
          return false
        }
      },
      //判断节点能否被拖拽
      allowDrag(draggingNode) {
        if(!draggingNode.data.is_edit_sort){
          return false
        }
        return true 
      }, 
      async orderClassify(params){
        // 发送排序请求
      }, 
      setClassCofig(flag,id,title,value){
        this.classifyCofig['flag'] = flag
        this.classifyCofig['Id'] = id
        this.classifyCofig['title'] = title
        this.classifyCofig['value'] = value
      },
      openClassify(pid,txt){
        this.setClassCofig(true,pid, txt ? txt : '新增分类','')   
      },
      editClassify(row){
        this.setClassCofig(true,row.id, '修改分类', row.name) 
      }, 
      closeAdd(){
        this.setClassCofig(false,'', '', '')
      },
      // 新增/修改分类
      async sureClassify(params){ 
        let {value,Id} = this.classifyCofig
        // 通过value的值判断当前是新增还是修改
        // 刷新分类,cid 新分类的id
        let refresh = { }
        if(value){ 
          refresh.flag = false
        }else{ 
          refresh.flag = true
        }  
        // 准备参数,发送请求
        // 请求成功后执行
        this.setClassCofig(false,'', '', '')
        refresh.cid = value? this.checkedId : res.data.data.id
        this.$emit('refreshClass',refresh)
      },
      //判断分类是否可以删除
      async delBefore(id,pid){
       //1.自定义判断是否可以删除,
       
       //2.可以删去执行删除操作,
       this.sureDelete(id,pid)
        
      },
      //删除分类,删除后回到上一级
      async sureDelete(id,pid){
        //1.准备删除的接口使用数据
        //2.发起请求,请求成功后执行下面代码
        this.setClassCofig(false,'', '', '')
          let refresh = {
            flag: true,
            cid: pid
          }
          this.$emit('refreshClass',refresh)
      },
    }
  };
</script>

使用tree组件

  • html
 <PersonalTree :activeId="currentClassfiyId" :classifyData="classifyData"
        @selectId="changeSelectId" @selectaddId="setAddId" @refreshClass="refreshClass"/>
  • js
<script>
// 在此处引入tree组件命名为customTree
  export default{
    components:{customTree},
      data(){
        return{
          currentClassfiyId:'',
          addClassifyId:[],
          classifyData:[], 
        }
      },
    mounted(){
      this.getClassList(true) 
    },
    methods:{
        async getClassList(flagScene,cid){
          // console.log(flagScene,cid)
          // 发送请求,获取全部分类
          this.classifyData = res.data.data.classify 
          this.currentClassfiyId = cid || this.classifyData?.[0].id
          if(flagScene){ 
              // 可以去获取内容
            } 
          }
        },
        refreshClass({flag,cid}){
         // 去刷新分类列表
           this.getClassList(flag,cid)
        },
        setAddId(val){
          this.addClassifyId = val
        },
        changeSelectId(id){
          this.currentClassfiyId = id
          // 可以去获取内容
        },
    }
   }
</script>   
 

classifyData的数据:

[{
  "id": 1033,
  "name": "一级分类",
  "parent_id": 0, 
  "level": 1,
  "child": [
    {
      "id": 1036,
      "name": "aaaaaaaaa",
      "parent_id": 1033, 
      "level": 2,
      "child": [],
      "is_edit_sort": true,
      "is_add_classify": true,
      "is_add_scene": true
    },
    {
      "id": 1035,
      "name": "aaaaa",
      "parent_id": 1033,  
      "level": 2,
      "child": [
        {
          "id": 1037,
          "name": "a-1",
          "parent_id": 1035, 
          "level": 3,
          "child": [
            {
              "id": 1040,
              "name": "a-1-3",
              "parent_id": 1037, 
              "level": 4,
              "child": [],
              "is_edit_sort": true,
              "is_add_classify": false,
              "is_add_scene": true
            },
            {
              "id": 1038,
              "name": "a-1-1",
              "parent_id": 1037, 
              "level": 4,
              "child": [],
              "is_edit_sort": true,
              "is_add_classify": false,
              "is_add_scene": true
            }
          ],
          "is_edit_sort": true,
          "is_add_classify": true,
          "is_add_scene": true
        }
      ],
      "is_edit_sort": true,
      "is_add_classify": true,
      "is_add_scene": true
    }
  ],
  "is_edit_sort": true,
  "is_add_classify": true,
  "is_add_scene": true
},{
  "id": 1032,
  "name": "测试分类b",
  "parent_id": 0, 
  "level": 1,
  "child": [],
  "is_edit_sort": true,
  "is_add_classify": true,
  "is_add_scene": true
},{
  "id": 1015,
  "name": "无操作区",
  "parent_id": 0,
  "level": 1,
  "child": [],
  "is_edit_sort": false,
  "is_add_classify": false,
  "is_add_scene": false
}]

如有帮到您,请收藏+关注哦!!!

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

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

相关文章

基于SpringBoot+Vue的婚恋相亲交友系统

基于SpringBootVue的婚恋相亲交友系统~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 管理员界面 摘要 基于SpringBootVue的婚恋相亲交友系统是一个现代化的、高效的交…

使用bitmap实现可回收自增id

需求描述 设计一个方法&#xff0c;每次调用返回一个自增id&#xff0c;同时需要满足以下要求。 可更新id的状态为已使用&#xff0c;已使用的id下次调用时不再返回可修改某个id的状态为未使用&#xff0c;下次调用时设为未使用状态的id可重新被返回 思路 思路一&#xff1…

全志T507-H技术帖 | 去掉IO扩展芯片后保留扩展引脚功能的实现方法

飞凌嵌入式推出的OKT507-C作为一款广受欢迎的开发板拥有丰富的功能接口&#xff0c;而实际上OKT507-C开发板的CPU引脚资源是比较紧缺的&#xff0c;那么它究竟是如何提供如此丰富的接口资源的呢&#xff1f;答案就是IO扩展芯片——TCA6424A。 这是一个24 位 I2C 和系统管理总线…

第三方商城对接项目(202311)

文章目录 1. 项目背景和目标2. 项目成果3. 项目经验总结4. 展望和建议 1. 项目背景和目标 竞标成功接口对接第三方商城&#xff0c;商品&#xff0c;订单&#xff0c;售后尽快完成对接 2. 项目成果 完成整个项目功能流程对接新业务功能移交项目等业务部门使用 3. 项目经验总…

MySQL中表的增删查改(进阶),超详细!

目录 一、数据库的约束 1、约束类型 2、NULL约束 3、UNIQUE&#xff1a;唯一约束 4、DEFAULT&#xff1a;默认值约束 5、PRIMARY KEY&#xff1a;主键约束&#xff08;主键只能定义一个&#xff0c;NOT NULL 和 UNIQUE 的结合&#xff09; 6、FOREIGN KEY&#xff1a;外键约…

C++之List容器

1.list容器简介 list是序列容器&#xff0c;允许在序列中的任何位置执行固定O(1)时间复杂度的插入和删除操作&#xff0c;并在两个方向进行迭代。list容器是一个双向循环链表。 list容器与vector容器区别&#xff1a; ①list中空间是随机的&#xff0c;通过指针域保存下一个成员…

【C++】类和对象的关系,对象的存储方式以及对象内存的计算

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Window10安装Docker

文章目录 Window10安装Docker前提条件Hyper -VWSL 2.0 安装包下载执行安装包更新 Window10安装Docker 前提条件 Hyper -V 如何启用 WSL 2.0 安装包下载 官网地址 下载后&#xff1a; 执行安装包 wsl --update等得有点久 重新打开 拉取一个helloworld镜像 说明已经…

【CocosCreator】利用遮罩Mask实现单边开门效果

如果对前端八股文感兴趣&#xff0c;可以留意公重号&#xff1a;码农补给站&#xff0c;总有你要的干货。 实现思路 首先新建一个新的遮罩节点&#xff08;全部采用默认属性&#xff09;&#xff0c;将其大小设为门的大小&#xff0c;然后将其摆放到门所在的位置&#xff0c;如…

MATLAB_5MW风电永磁直驱发电机-1200V直流并网MATLAB仿真模型

仿真软件&#xff1a;matlab2016b 风机传动模块、PMSG模块、蓄电池模块、超级电容模块、无穷大电源、蓄电池控制、风机控制、逆变器控制等模块。 逆变器输出电压&#xff1a; 混合储能系统SOC&#xff1a; 威♥关注“电击小子程高兴的MATLAB小屋”获取更多精彩资料&#xff0…

单调栈【2023年最新】

做题的时候看到了单调栈&#xff0c;但是不知道是个什么玩意&#xff0c;记录一下吧。 单调栈含义 单调栈是一种特殊的数据结构&#xff0c;用于解决一些与单调性相关的问题。它的基本含义是在栈的基础上&#xff0c;维护一个单调递增或单调递减的栈。 在单调递增栈中&#…

SpringCloud-Gateway无法使用Feign服务(2021.X版本)

Spring Cloud Gateway 2021.x版本&#xff0c;无法使用Feign调用其他服务接口。 问题原因&#xff1a; 在官网的 issue 里面找到了相关的问题。 How to call another micro-service on GatewayFilterFactory ? Issue #1090 spring-cloud/spring-cloud-gateway GitHubHel…

QT QSplitter

分裂器QSplitter类提供了一个分裂器部件。和QBoxLayout类似&#xff0c;可以完成布局管理器的功能,但是包含在它里面的部件,默认是可以随着分裂器的大小变化而变化的。 比如一个按钮放在布局管理器中,它的垂直方向默认是不会被拉伸的,但是放到分裂器中就可以被拉伸。还有一点不…

OV5640的参数与配置方法

分辨率和速率&#xff08;FPS&#xff09; 寄存器配置 I/O 板的驱动能力和方向控制 system clock control OV5640 PLL 允许输入时钟频率范围为 6~27 MHz&#xff0c;最大 VCO 频率为 800 MHz。 MipiClk 用于 MIPI&#xff0c;SysClk 用于图像信号处理 (ISP) 模块的内部时钟。 …

TensorFlow(1):深度学习的介绍

1 深度学习与机器学习的区别 学习目标&#xff1a;知道深度学习与机器学习的区别 区别&#xff1a;深度学习没有特征提取 1.1 特征提取方面 机器学习的特征工程步骤是要靠手动完成的&#xff0c;而且需要大量领域专业知识深度学习通常由多个层组成&#xff0c;它们通常将更简…

Redis系列之实现分布式自增主键

软件环境 JDK 1.8 SpringBoot 2.2.1 Maven 3.2 Mysql 8.0.26 redis 6.2.14 Mybatis Plus 3.4.3.4 开发工具 IntelliJ IDEA smartGit 一、实现原理 使用Redis来实现分布式的主键自增主要是依赖于Redis的INCR命令&#xff0c;调用INCR命令的对应key&#xff0c;其数值…

centos7部署Canal与Canal集成使用

1、简介 canal [kə’nl]&#xff0c;译意为水道/管道/沟渠&#xff0c;主要用途是基于 MySQL 数据库增量日志解析&#xff0c;提供增量数据订阅和消费 早期阿里巴巴因为杭州和美国双机房部署&#xff0c;存在跨机房同步的业务需求&#xff0c;实现方式主要是基于业务 trigge…

Flink架构

1、Apache Flink集群的核心架构&#xff1a; 1、client&#xff08;作业客户端&#xff09;&#xff1a;提交任务的地方叫做客户端 2、JobManager&#xff08;作业管理器&#xff09;&#xff1a;作用是用于管理集群中任务 3、TaskManager&#xff08;任务管理器&#xff09;&a…

k8s 1.28安装

容器运行时&#xff0c;containerd 按照官方的指导&#xff0c;需要安装runc和cni插件&#xff0c;提示的安装方式&#xff0c;有三种&#xff1a; 二进制安装包源码apt-get 或 dnf安装 我们这里选用第三种&#xff0c;找到docker官方提供的安装方式 ubuntu-containerd # A…

Elasticsearch:使用你的 RAG 来进行聊天

什么是人工智能中的检索增强生成&#xff08;RAG&#xff09;&#xff1f; 检索增强生成 (RAG)&#xff0c;与你的文档聊天的超级英雄&#xff0c;架起信息检索和文本生成世界的桥梁&#xff01; 这就像福尔摩斯和莎士比亚联手解决需要大量知识的复杂任务。 RAG 突然介入&…