element UI树形表格拖动创建子级

呜呜呜,手上项目最新的需求要求写一个树形图表格,这就算了,还要拖动,单行的表格拖动也不复杂,网上一搜就是很多,用sortablejs轻松搞定。然而树形的表格,拖动的时候就不那么容易了,并且平常拖动基本上是交换顺序的作用,我们的需求不一样,是将一条数据拖到另一条数据下面变成子级,折腾了我半天,于是又来这里哭诉了,哈哈哈。

言归正传,开始编写基于elementUI的拖拽表格吧。好在element 已经做好了树形表格,我只需要加上拖动功能,拖动还是使用的sortablejs,主要就是计算拖动后的数据比较麻烦。

直接看代码吧:

html 部分:

<el-table
      :data="tableData"
      style="width: 100%;margin-bottom: 20px;"
      :row-key="rowKey"
      border
      ref="treeTable"
      :expand-row-keys="expandRow"
      @cell-mouse-enter.once='rowDrop'
      :tree-props="treeProps">
      <slot></slot>
    </el-table>

 js部分:

js部分我写了几个函数,其中:

  getExpandRow函数是遍历层级,根据设置的默认展开到第几层,拿到符合条件的id数组,表格根据这个数组默认展开几级。

  getDealData函数也是遍历树形结构,处理数据,将树形结构平铺,一条一条的存入数组,这是因为element 的树形表格拖动时是一行一行的拖动的,用sortablejs拖动拿到的也是每一行的下标,这样存入数组了之后,就能通过下标拿到拖动的这一行的数据和即将放入的那一行的数据,并且存的时候我将children也存入了的,这样根据下标拿到的数据就是当前节点和下面的所有子节点。就可以将其整个的移动到目标节点下面。

  rowDrop是初始化拖动表格的函数,调用时没有在mouted里调用,是为了避免在表格还没有渲染好就去调用,这样会报错,放在表格的cell-mouse-enter.once事件里,并用once修饰符,执行一次,就可以了。rowDrop里对树形数据进行了处理,处理前都去执行了getDealData,是因为每次拖动了,表格都会重新排序。处理树形数据的时候需要依据平铺的数组。

  重点是changeData函数,它是rowDrop的具体实施,主要考虑到几个方面:

    1.父级不能移动到子级下面;

    2.子级已经在当前的父级下面了,则不需要再插入,否则会重复;

    3.因为只想遍历一次数据,所以在遍历的时候同时做了删除拖动的节点,并将这个节点移动到目标节点下面;

    4.最后重新渲染表格时,我最开始用了doLayout,发现没有生效,界面上树形的折叠按钮显示缩进不对,用$nextTick()也还是不对,最后决定就还是先清空数组,然后再赋值,这样表格就重新渲染了。

import Sortable from 'sortablejs'
export default {
  props: {
    data: {
      type: Array,
      default: () => []
    },
    treeProps: {
      type: Object,
      default: () => {
        return { children: 'children', hasChildren: 'hasChildren' }
      }
    },
    rowKey: {
      type: String,
      default: 'id'
    },
    expandLevel: {
      type: Number,
      default: 3
    }
  },
  watch: {
    data: {
      handler: function(val) {
        this.tableData = JSON.parse(JSON.stringify(val))
        this.getExpandRow(val)
      },
      deep: true,
      immediate: true
    }
  },
  data() {
    return {
      selectObj: {},
      tableData: [],
      flattArray: [],
      expandRow: []
    }
  },
  mounted() {
    this.getDealData()
  },
  methods: {
    getExpandRow(data) {
      const result = []
      const children = this.treeProps.children
      const rowKey = this.rowKey
      let level = 0
      // 默认展开三级
      const func = (arr, parent) => {
        arr.forEach(item => {
          if (item[children] && item[children].length !== 0) {
            if (level < this.expandLevel) {
              result.push(item[rowKey] + '')
            }
            level++
            func(item[children], item)
          }
        })
      }
      func(data)
      this.expandRow = result
    },
    getDealData() {
      const result = []
      const children = this.treeProps.children
      const rowKey = this.rowKey
      const func = function(arr, parent) {
        arr.forEach(item => {
          const obj = Object.assign(item)
          if (parent) {
            if (obj.parentIds) {
              obj.parentIds.push(parent[rowKey])
            } else {
              obj.parentIds = [parent[rowKey]]
            }
          }
          // 将每一级的数据都一一装入result,不需要删除下面的children,方便拖动的时候根据下标直接拿到整个数据,包括当前节点的子节点
          result.push(obj)
          if (item[children] && item[children].length !== 0) {
            func(item[children], item)
          }
        })
      }
      func(this.tableData)
      this.flattArray = result
    },
    rowDrop() {
      const tbody = document.querySelector('.el-table__body-wrapper tbody')
      const self = this
      Sortable.create(tbody, {
        onEnd({ newIndex, oldIndex }) {
          self.getDealData()
          const sourceObj = self.flattArray[oldIndex]
          const targetObj = self.flattArray[newIndex]
          // 改变要显示的树形数据
          self.changeData(sourceObj, targetObj)
        }
      })
    },
    changeData(sourceObj, targetObj) {
      let flag = 0
      const data = Object.assign(this.tableData)
      const children = this.treeProps.children
      const rowKey = this.rowKey
      const func = function(arr, parent) {
        for (let i = arr.length - 1; i >= 0; i--) {
          const item = arr[i]
          // 判断是否是原来拖动的节点,如果是,则删除
          if (item[rowKey] === sourceObj[rowKey] && (!parent || parent && parent[rowKey] !== targetObj[rowKey])) {
            arr.splice(i, 1)
            flag++
          }
          // 判断是否是需要插入的节点,如果是,则装入数据
          if (item[rowKey] === targetObj[rowKey]) {
            if (item[children]) {
              // 判断源数据是否已经是在目标节点下面的子节点,如果是则不移动了
              let repeat = false
              item[children].forEach(e => {
                if (e[rowKey] === sourceObj[rowKey]) {
                  repeat = true
                }
              })
              if (!repeat) {
                sourceObj.parentIds = []
                item[children].unshift(sourceObj)
              }
            } else {
              sourceObj.parentIds = []
              item[children] = [sourceObj]
            }
            flag++
          }
          // 判断是否需要循环下一级,如果需要则进入下一级
          if (flag !== 2 && item[children] && item[children].length !== 0) {
            func(item[children], item)
          } else if (flag === 2) {
            break
          }
        }
      }
      // 检测是否是将父级拖到子级下面,如果是则数据不变,界面重新回到原数据
      if (targetObj.parentIds) {
        if (targetObj.parentIds.findIndex(_ => _ === sourceObj[this.rowKey]) === -1) {
          func(data)
        } else {
          this.$message.error('不能将父级拖到子级下面')
        }
      } else {
        func(data)
      }
      this.$set(this, 'tableData', [])
      // 重新渲染表格,用doLayout不生效,所以重新装了一遍
      this.$nextTick(() => {
        this.$set(this, 'tableData', data)
      })
    }
  }
}

posted @ 2020-12-08 16:43  骄阳似我_lq  Views(2863)  Comments(2Edit  收藏  举报