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

news2024/10/7 4:30:52

前言

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

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

了解需求

  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
}]

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cookie、session和Token的区别?JWT又是什么?单点登录又是什么?头大?快进来看看,一文帮你捋清楚~

目录 0、HTTP是无状态的 1、前端标记cookie 1.1、cookie限制空间范围 1.2、cookie限制时间范围 1.3、cookie限制使用方式 2、服务端存储session库 2.1、我们先来简单聊聊session是什么&#xff1f; 2.2、session的存储方式 2.3、session的过期和销毁 2.4、session的分…

操作系统:文件管理(二)文件系统

一战成硕 4.3 文件系统4.3.1 文件系统结构4.3.2 文件系统布局4.3.3 外存空闲空间管理4.3.4 虚拟文件系统 4.3 文件系统 4.3.1 文件系统结构 4.3.2 文件系统布局 文件系统在磁盘中的结构 文件系统在内存中的结构 内存中的信息用于管理文件系统并通过缓存提高性能&#xff0c;这…

HDFS系统权限详解

一&#xff0c;HDFS超级用户 启动namenode的用户就是HDFS中的超级用户 如图所示 HDFS中&#xff0c;也是有权限控制的&#xff0c;其控制逻辑和Linux文件系统的完全一致 但是不同的是&#xff0c;两个系统的Supergroup不同(超级用户不同) Linux的操作用户是root HDFS文件系统的…

AI 编程界的扛把子

大家好&#xff0c;我是伍六七。 全国最大 AI 社群《AI 破局俱乐部》初创合伙人。8 年 Java 经验&#xff0c;干过开发&#xff0c;也做过管理。目前在某互联网大厂从事 Java 开发&#xff0c;业余时间研究 AI 编程。 我从 2022 年底&#xff0c;ChatGPT 问世&#xff0c;就密…

K8S知识点(五)

&#xff08;1&#xff09;资源管理介绍 Pod控制器的作用&#xff0c;就是为了最终产生各种各样的Pod&#xff0c;Pod里面运行容器&#xff0c;容器里面运行程序 程序需要数据持久化&#xff0c;可以用数据存储卷来存储 Pod想要让外部访问需要通过Service代理&#xff0c;外部…

基于Pytorch框架的LSTM算法(一)——单维度单步预测(1)

1.项目说明 使用data中的close列的时间序列数据完成预测【使用close列数据中的前windowback-1天数据完成未来一天close的预测】 2.数据集 Date,Open,High,Low,Close,Adj Close,Volume 2018-05-23,182.500000,186.910004,182.179993,186.899994,186.899994,16628100 2018-05…

IDEA高效调试,你真的会了吗

大家好&#xff0c;这里是 一口八宝周 &#x1f44f; 欢迎来到我的博客 ❤️一起交流学习 文章中有需要改进的地方请大佬们多多指点 谢谢 &#x1f64f; 由于平时工作中经常需要debug调试代码&#xff0c;每次调试都会阻塞住进程&#xff0c;影响别人排查问题。 “你一个人deb…

Linux生成动态库

动态库 1.命名规则 Linux&#xff1a; libxxx.so lib &#xff1a;前缀&#xff08;固定的&#xff09;&#xff1b;xxx&#xff1a;动态库的名字&#xff08;自己取&#xff09;&#xff1b;.so&#xff1a;后缀&#xff08;固定的&#xff09;&#xff1b; Windows&#…

.NET Framework中自带的泛型委托Action

Action<>是.NET Framework中自带的泛型委托&#xff0c;可以接收一个或多个输入参数&#xff0c;但不返回任何参数&#xff0c;可传递至多16种不同类型的参数类型。在Linq的一些方法上使用的比较多。 1、Action泛型委托 .NET Framework为我们提供了多达16个参数的Action…

链表面试OJ题(1)

今天讲解两道链表OJ题目。 1.链表的中间节点 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,5] 解释&#xff1a;链表只有一个…

轻量封装WebGPU渲染系统示例<19>- 使用GPU Compute材质多pass元胞自动机(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/GameOfLifeMultiMaterialPass.ts 系统特性: 1. 用户态与系统态隔离。 细节请见&#xff1a;引擎系统设计思路 - 用户态与系统态隔离-CSDN博客 2. 高频调用与低频调…

1200*D. Same Differences(数学推公式)

Problem - 1520D - Codeforces 解析&#xff1a; 统计 a [ i ] - i #include<bits/stdc.h> using namespace std; #define int long long const int N2e55; int t,n,a[N]; signed main(){scanf("%lld",&t);while(t--){scanf("%lld",&n);…

一些对程序员有用的网站

当你遇到问题时 Stack Overflow&#xff1a;订阅他们的每周新闻和任何你感兴趣的主题Google&#xff1a;全球最大搜索引擎必应&#xff1a;在你无法使用Google的时候CSDN&#xff1a;聊胜于无AI导航一号AI导航二号 新闻篇 OSCHINA&#xff1a;中文开源技术交流社区 针对初学…

FPGA设计过程中有关数据之间的并串转化

1.原理 并串转化是指的是完成串行传输和并行传输两种传输方式之间的转换的技术&#xff0c;通过移位寄存器可以实现串并转换。 串转并&#xff0c;将数据移位保存在寄存器中&#xff0c;再将寄存器的数值同时输出&#xff1b; 并转串&#xff0c;将数据先进行移位&#xff0…

RHCSA --- Linux系统文件操作

rm -rf * 删除当前目录下的所有文件及目录&#xff0c;并且是直接删除&#xff0c;无需逐一确认命令行为 touch&#xff1a; 不存在时创建&#xff0c;存在时更新文件时间戳 touch 1 2 3 4 创建多个文件 touch {1..4}{2..4} 花括号展开创…

STM32_project:led_beep

代码&#xff1a; 主要部分&#xff1a; #include "stm32f10x.h" // Device header #include "delay.h"// 给蜂鸣器IO口输出低电平&#xff0c;响&#xff0c;高&#xff0c;不向。 //int main (void) //{ // // 开启时钟 // RC…

开关电源怎么进行老化测试?有哪些测试方法?

一、开关电源老化测试原理 开关电源老化测试是检测电源长期稳定性和可靠性的重要测试方法。通过模拟开关电源在实际工作环境(如高负荷、高温等)中的长时间使用&#xff0c;来验证其性能、稳定性和可靠性。老化测试的原理主要基于以下概念&#xff1a; 1. 加速老化原理 老化测试…

Ntrip协议是什么?(RTK)

NTRIP (Networked Transport of RTCM via Internet Protocol) 是一种将实时差分导航数据通过互联网传输的协议。它被广泛应用于全球卫星定位系统 (GNSS) 定位和导航领域&#xff0c;以提高 GNSS 定位的精度。 NTRIP 是基于 TCP/IP 的协议&#xff0c;使用 HTTP/1.1 进行数据传…

vue中异步更新$nextTick

1.需求 编辑标题, 编辑框自动聚焦 点击编辑&#xff0c;显示编辑框让编辑框&#xff0c;立刻获取焦点 2.代码实现 <template><div class"app"><div v-if"isShowEdit"><input type"text" v-model"editValue"…

leetcode链表

这几天手的骨裂稍微好一点了&#xff0c;但是还是很疼&#xff0c;最近学校的课是真多&#xff0c;我都没时间做自己的事&#xff0c;但是好在今天下午是没有课的&#xff0c;我也终于可以做自己的事情了。 今天分享几道题目 移除链表元素 这道题我们将以两种方法开解决&…