ElementUI-tree drag-and-drop function and node customization

Foreword

When multiple categories are encountered on the management side, they are required to be displayed hierarchically, and each category has additional operations. For example: add category, edit category, delete, drag to category, etc.

The following will record such a demand internship process.

Understand needs

  1. Classified display displayed by level
  2. Categories display operable buttons based on specific parameters. Category operations include add, delete, and change.
  3. Categories also support drag and drop, but not all categories support drag and drop.
  4. Click Category to perform other operations. For example: refresh data (not implemented)
  5. After adding a category, refresh the category data. The currently selected category is the added category.
  6. After deleting a category, return to the previous category
  7. Right-click the category and click the operation button to pop up the operation pop-up window
  8. Click the arrows in front of categories to expand and collapse categories

Renderings

  • Classification display

  • Category operation pop-up window

Component library

Using the Tree tree control and Dropdown menu in ElementUI

  • Tree tree control: Element – The world’s most popular Vue UI framework
  • Dropdown dropdown menu dropdown: Element – The world’s most popular Vue UI framework

Start coding

Build tree component

  • html part:
<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,'Add subcategory')">
                    <img src="~@/../add.png" class="myicon-opt"/>
                    Add subcategory
                  </div>
                </el-dropdown-item>
                <el-dropdown-item v-if="data.is_edit_sort">
                  <div @click="editClassify(data)">
                    <img src="~@/../edit.png" class="myicon-opt" />
                    Revise
                  </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" />
                    delete
                  </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){
          // When v value is 0, 0 == '' value is 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: {
      // Click on the category name
      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)
      },
      // Get the multi-level parent class id and add it to the array subscript 0
      selectNode(id,node){
        id.unshift(node.data.id)
        if(node.parent.parent != null){
          this.selectNode(id,node.parent)
        }
      },
      // Right click on category
      rightClick(event,data, Node, element){
        setTimeout(()=>{
          this.checkedKeys = data.id
          this.$refs['messageDrop' + data.id].show()
        })
      },
      // Click the action button
      setSeletKey(k){
        setTimeout(()=>{
          this.checkedKeys = k
        })
      },
      // Asynchronous monitoring of drop-down menu, open (true) or hidden (flase)
      controlCheckedKeys(flag){
        if(!flag){
          this.checkedKeys = ''
        }
      },
      // Event triggered when the node starts dragging
      handleDragStart(node) {
        if(!node.data.is_edit_sort){
          return false
        }
      },
      // Event triggered when dragging is successfully completed
      handleDrop(draggingNode, dropNode, dropType) {
        if(dropType == 'none') return
        //Preparation sorting parameters can be changed by yourself
        let params = {
          pk1: draggingNode.data.id,
          pk2: dropNode.data.id,
          direction:dropType == 'before' ? -1 : 1
        }
        this.orderClassify(params)
      },
      /**
       * Determine whether the target node can be placed when dragging.
       * @param {*} draggingNode
       * @param {*} dropNode
       * @param {*} The type parameter has three situations: 'prev', 'inner' and 'next', which respectively means placing before the target node, inserting into the target node and placing after the target node.
       */
      allowDrop(draggingNode, dropNode, type) {
        if (draggingNode.level === dropNode.level) {
          if (draggingNode.data.parent_id === dropNode.data.parent_id & amp; & amp; dropNode.data.is_edit_sort) {
            // Drag up || Drag down
            return type === "prev" || type === "next"
          }
        } else {
          // Process at different levels
          return false
        }
      },
      //Determine whether the node can be dragged
      allowDrag(draggingNode) {
        if(!draggingNode.data.is_edit_sort){
          return false
        }
        return true
      },
      async orderClassify(params){
        //Send sorting request
      },
      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 : 'Add new category','')
      },
      editClassify(row){
        this.setClassCofig(true,row.id, 'Modify classification', row.name)
      },
      closeAdd(){
        this.setClassCofig(false,'', '', '')
      },
      //Add/modify categories
      async sureClassify(params){
        let {value,Id} = this.classifyCofig
        // Determine whether the current addition or modification is based on the value of value
        // Refresh the category, cid is the id of the new category
        let refresh = { }
        if(value){
          refresh.flag = false
        }else{
          refresh.flag = true
        }
        // Prepare parameters and send request
        // Executed after the request is successful
        this.setClassCofig(false,'', '', '')
        refresh.cid = value? this.checkedId : res.data.data.id
        this.$emit('refreshClass',refresh)
      },
      //Determine whether the category can be deleted
      async delBefore(id,pid){
       //1. Customize to determine whether it can be deleted,
       
       //2. You can delete and perform the delete operation.
       this.sureDelete(id,pid)
        
      },
      //Delete the category and return to the previous level after deletion
      async sureDelete(id,pid){
        //1. Interface usage data to be deleted
        //2. Initiate a request and execute the following code after the request is successful.
        this.setClassCofig(false,'', '', '')
          let refresh = {
            flag: true,
            cid: pid
          }
          this.$emit('refreshClass',refresh)
      },
    }
  };
</script>

Use tree component

  • html
 <PersonalTree :activeId="currentClassfiyId" :classifyData="classifyData"
        @selectId="changeSelectId" @selectaddId="setAddId" @refreshClass="refreshClass"/>
  • js
<script>
//Introduce the tree component here and name it customTree
  export default{
    components:{customTree},
      data(){
        return{
          currentClassfiyId:'',
          addClassifyId:[],
          classifyData:[],
        }
      },
    mounted(){
      this.getClassList(true)
    },
    methods:{
        async getClassList(flagScene,cid){
          // console.log(flagScene,cid)
          //Send a request to get all categories
          this.classifyData = res.data.data.classify
          this.currentClassfiyId = cid || this.classifyData?.[0].id
          if(flagScene){
              // You can get the content
            }
          }
        },
        refreshClass({flag,cid}){
         //To refresh the category list
           this.getClassList(flag,cid)
        },
        setAddId(val){
          this.addClassifyId = val
        },
        changeSelectId(id){
          this.currentClassfiyId = id
          // You can get the content
        },
    }
   }
</script>
 

classifyData data:

[{
  "id": 1033,
  "name": "First level classification",
  "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": "Test category b",
  "parent_id": 0,
  "level": 1,
  "child": [],
  "is_edit_sort": true,
  "is_add_classify": true,
  "is_add_scene": true
},{
  "id": 1015,
  "name": "No operation area",
  "parent_id": 0,
  "level": 1,
  "child": [],
  "is_edit_sort": false,
  "is_add_classify": false,
  "is_add_scene": false
}]

If this helps you, please bookmark + follow! ! !