Bootstrap

vue项目 使用SortableJs拖拽树形排序,树形添加、删除、编辑

使用sortable.js拖拽树形结构数据
在网上研究许久没有找到合适的,自己写了一个组件,可同级、跨级拖动,如有bug可自行修改。

在这里插入图片描述

使用

安装SortableJs

$ npm install sortablejs --save

定义个事件总线 使用store也可以

// eventBus.js
import Vue from 'vue'
const Bus = new Vue()
export default Bus

main.js全局引入事件总线

import Bus from '@/plugins/eventBus'
Vue.prototype.$bus = Bus

定义VUE树形组件

<template>
   <div class="tree-list nested-sortable" ref="nestedSortable">
      <div v-for="(item, index) in value" :key="item.id" class="tree-list-item" :class="`nested-${deep}`" :data-tid="item.id">
        <div class="title">
          <span class="drag-node">
            <i v-if="!root && parent.paramType !== 'JSONArray'" class="el-icon-rank drag-icon"></i>
          </span>
          <span class="open-arrow">
            <template v-if="item.paramType === 'JSONObject' || item.paramType === 'JSONArray'">
              <el-button :icon="hiddenList.includes(item.id) ? 'el-icon-arrow-right' : 'el-icon-arrow-down'" type="text" size="" @click="hiddenNode(item.id)"></el-button>
            </template>
          </span>

          <span>{{ item.columnName }}</span>

          <span class="tree-list-btns">
            <el-button icon="el-icon-plus" type="text" size="" @click="addNode(index)"></el-button>
            <el-button v-if="!root && parent.paramType !== 'JSONArray'" icon="el-icon-minus" type="text" size="" @click="removeNode(index)"></el-button>
          </span>
        </div>
        <template v-if="item.children">
          <sort-list v-model="item.children" :deep="deep + 1" :root="false" class="nestedSortable-children" :class="{ 'hidden': hiddenList.includes(item.id) }" :data-pid="item.id" :parent="item"></sort-list>
        </template>
      </div>
   </div>
</template>
<script>
import SortList from './SortList.vue'
import Sortable from 'sortablejs'
export default {
   name: 'SortList',
   components: {
     SortList
   },
   props: {
    value: {
      type: Array,
      default: () => {
        return []
      }
    },
    // 节点深度
    deep: Number,
    // 是否根节点
    root: {
      type: Boolean,
      default: false
    },
    // 父节点数据
    parent: {
      type: Object,
      default: null
    }
   },
   data() {
     return {
        hidden: false,
        // 当前隐藏的节点ID数组
        hiddenList: [],
        // 排序实例
        sortInstance: null
     }
   },
   mounted() {
   	 // 在递归组件中初始化实例,根节点不必拖动
     !this.root && this.initSortable()
     // 根节点监听排序做数据变更
     if (this.root) {
      this.$bus.$on('treeList:sort', (data) => {
        console.log('treeList:sort', data)
        // 新节点的数据
        let newList = this.findArr(data.newPid)
        // 旧节点的数据
        let oldList = this.findArr(data.oldPid)
        // 删除旧位置的数据
        let delRow = oldList.splice(data.oldIndex, 1)[0]
        // 新位置插入数据
        newList.splice(data.newIndex, 0, delRow)
      })
     }
   },
   beforeDestroy () {
    // 销毁监听
    if (this.root) {
      this.$bus.$off('treeList:sort')
      this.sortInstance = null
    }
   },
   methods: {
    // 隐藏子级
    hiddenNode(id){
      let hasHidden = this.hiddenList.findIndex(item => item===id)
      if (this.hiddenList.includes(id)) {
        this.hiddenList.splice(hasHidden, 1)
      } else {
        this.hiddenList.push(id)
      }      
    },
    // 根据ID查询数组
    findArr(id) {
      let list
      const deepFind = (arr) => {
        for (let i = 0; i < arr.length; i++) {
          if (arr[i].id === id) {
            list = arr[i].children
            break
          }
          if (arr[i].children?.length) {
            deepFind(arr[i].children)
          }
        }
      }
      deepFind(this.value || [])
      return list
    },
    // 根据ID查询父节点数据
    findParent (id) {
      let obj = null
      const deepFind = (arr) => {
        for (let i = 0; i < arr.length; i++) {
          if (arr[i].id === id) {
            obj = arr[i]
            break
          }
          if (arr[i].children?.length) {
            deepFind(arr[i].children)
          }
        }
      }
      deepFind(this.value || [])
      return obj      
    },
    // 增加节点
    addNode (index) {
      let list = this.value
      list.splice(index+1, 0, {
        'id': 'item-1-1', // 自己生成
        'columnName': 'item-1-1',
        'paramType': 'String',
        'paramField': 'in',
        'paramName': '名称',
        'isNull': 1,
        'paramExample': '张三',
        'description': '三三',
        'isRequired': '1'
      })
    },
    // 删除节点
    removeNode (index) {
      let list = this.value
      list.splice(index, 1)
    },
    // 初始化拖拽实例
    initSortable () {
      let _this = this
      let el = this.$refs['nestedSortable']
      this.sortInstance = new Sortable(el, {
        group: {
          name: 'nested-'+ _this.deep,
          put (evt) {
            // 控制是否可以拖入
            if (_this.parent.paramType === 'JSONObject') {
              return true
            } else {
              return false
            }
          },
          pull(){
            // 控制是否可以进行拖拽
            if(_this.parent.paramType === 'JSONArray') {
              return false
            }
            return true
          }
        },
        handle: '.drag-node',
        sort: true,
        animation: 150,
        fallbackOnBody: true,
        swapThreshold: 0.65,
        onEnd(evt) {
          let { newIndex, oldIndex, to, item } = evt
          console.log('-----onEnd-----', newIndex, oldIndex, evt)
          // 触发顶层数据变更,使用store也行
          _this.$bus.$emit('treeList:sort', {
            newIndex,
            oldIndex,
            id: item.tid,
            newPid: to.dataset.pid,
            oldPid: _this.parent.id
          })
        }
      });
    }
   }
};
</script>
<style lang='scss' scoped>
  .tree-list {
    .tree-list-item {
      margin-left: 30px;
    }
    .tree-list-btns {
      display: inline-block;
      margin-left: 20px;
    }
    .drag-node {
      display: inline-block;
      width: 20px;
      height: 20px;
      &:hover {
        cursor: move;
      }
    }
    .hidden {
      display: none;
    }
    .open-arrow {
      display: inline-block;
      width: 25px;
    }
  }

</style>

使用

<template>
   <div>
    <sort-list v-model="json" :root="true" :deep="1"></sort-list>
  </div>
</template>
<script>
import SortList from './SortList.vue'
export default {
   name: 'SortTableList',
   
  components: {
    SortList
  },
   mixins: [],
   data() {
    return {
      json: [
        {
          'columnName': 'root',
          'paramType': 'JSONObject',
          'paramField': 'in',
          'paramName': '名称',
          'id': 'item-0',
          'children': [
            {
              'id': 'item-1-1',
              'columnName': 'item-1-1',
              'paramType': 'String',
              'paramField': 'in',
              'paramName': '名称',
              'isNull': 1,
              'paramExample': '张三',
              'description': '三三',
              'isRequired': '1'
            }, {
              'id': 'item-1-2',
              'columnName': 'item-1-2 JSONObject',
              'paramType': 'JSONObject',
              'paramField': 'in',
              'paramName': '名称',
              'isNull': 1,
              'paramExample': '张三',
              'description': '三三',
              'isRequired': '',
              'children': [
                {
                  'id': 'item-1-2-1',
                  'columnName': 'item-1-2-1',
                  'paramType': 'String',
                  'paramField': 'in',
                  'paramName': 'item-1',
                  'isNull': 1,
                  'paramExample': 'item-1-2-1',
                  'description': '1111',
                  'isRequired': '1'
                }, {
                  'id': 'item-1-2-2',
                  'columnName': 'item-2-2',
                  'paramType': 'String',
                  'paramField': 'in',
                  'paramName': 'item-1',
                  'isNull': 1,
                  'paramExample': 'item222',
                  'description': '222',
                  'isRequired': '1'
                }, {
                  'id': 'item-1-2-3',
                  'columnName': 'item-1-2 JSONObject',
                  'paramType': 'JSONObject',
                  'paramField': 'in',
                  'paramName': '名称',
                  'isNull': 1,
                  'paramExample': '张三',
                  'description': '三三',
                  'isRequired': '',
                  'children': [
                    {
                      'id': 'item-1-2-3-1',
                      'columnName': 'item-1-2-3-1',
                      'paramType': 'String',
                      'paramField': 'in',
                      'paramName': 'item-1',
                      'isNull': 1,
                      'paramExample': 'item-1-2-3-1',
                      'description': '1111',
                      'isRequired': '1'
                    }, {
                      'id': 'item-1-2-3-2',
                      'columnName': 'item-1-2-3-2',
                      'paramType': 'String',
                      'paramField': 'in',
                      'paramName': 'item-1',
                      'isNull': 1,
                      'paramExample': 'item-1-2-3-2',
                      'description': 'item-1-2-3-2',
                      'isRequired': '1'
                    }
                  ]
                }
              ]
            }, {
              'id': 'item-1-3',
              'columnName': 'item-1-3 JSONArray',
              'paramType': 'JSONArray',
              'paramField': 'in',
              'paramName': '名称',
              'isNull': 1,
              'paramExample': '张三',
              'description': '三三',
              'isRequired': '1',
              'children': [{
                'id': 'item-1-3-1',
                'columnName': 'item-1-3-1',
                'paramType': 'JSONObject',
                'paramField': 'in',
                'paramName': '名称',
                'children': [
                  {
                    'id': 'item-1-3-1-1',
                    'columnName': 'item-1-3-1-1',
                    'paramType': 'String',
                    'paramField': 'in',
                    'paramName': 'item-1',
                    'isNull': 1,
                    'paramExample': 'item111',
                    'description': '1111',
                    'isRequired': '1'
                  }, {
                    'id': 'item-1-3-1-2',
                    'columnName': 'item-1-3-1-2',
                    'paramType': 'String',
                    'paramField': 'in',
                    'paramName': 'item-1',
                    'isNull': 1,
                    'paramExample': 'item222',
                    'description': '222',
                    'isRequired': '1'
                  }, {
                    'id': 'item-1-3-1-3',
                    'columnName': 'item-1-3-1-3',
                    'paramType': 'String',
                    'paramField': 'in',
                    'paramName': 'item-1',
                    'isNull': 1,
                    'paramExample': 'item-1-3-1-3',
                    'description': '222',
                    'isRequired': '1'
                  }
                ]
              }]

            }
          ]
        }
      ]
    }
  },
   watch: {
     json : {
      deep: true,
      handler(val) {
        console.log(JSON.stringify(val, null, 2))
      }
     }
   }
};
</script>
;