vxe-table同级节点排序功能实现

问题

vue项目中使用了vxe-table加载组织机构列表,列表是树形结构的,需要实现同级组织机构通过拖拽进行排序的功能,而vxe-table官网上拖拽排序的demo没有同级节点的限制,而且存在一些bug,比如父节点向上拖拽时,子节点是和父节点一起移动的,但父节点向下拖拽时,子节点没有一起移动;Test2下的子节点拖拽到最后一行松开鼠标时位置不正确等等。下面给出一个我实现的同级节点拖拽排序功能,参考vxe-table的代码,做了很多调整,同样基于sortable.js实现。
在这里插入图片描述

排序代码


   treeDrop() {
      this.$nextTick(() => {
        const xTable = this.$refs.xTree
        if (!xTable) {
          return
        }
        this.sortable = Sortable.create(xTable.$el.querySelector('.body--wrapper>.vxe-table--body tbody'), {
          handle: '.drag-btn',
          onEnd: ({ item, oldIndex }) => {
            const options = { children: 'children' }
            const wrapperElem = item.parentNode// 移动后所处位置的父节点,由于vxe-table最终生成的dom节点都是同级的,只是节点text的缩进不同,所以移动前和移动后的父节点总是相同的
            const targetTrElem = item// 移动的节点
            const alternativeTrElem = wrapperElem.children[oldIndex]// 节点移动后,代替它原来位置的节点
            const prevTrElem = targetTrElem.previousElementSibling// 前面的兄弟节点(移动后所处位置的兄弟节点,不是移动前的)
            const nextTrElem = targetTrElem.nextElementSibling// 后面的兄弟节点

            const tableTreeData = this.tableData
            const selfNode = xe.findTree(tableTreeData, row => row.cguid === targetTrElem.getAttribute('rowid'), options)// 通过tableTreeData可以获取到移动前的数据。这里selfNode是xeUtil返回的一种数据格式,封装了tableTreeData中的数据
            const selfRow = selfNode.item// item是当前行在tableTreeData中对应的的数据

            if (prevTrElem) {
              const prevNode = xe.findTree(tableTreeData, row => row.cguid === prevTrElem.getAttribute('rowid'), options)
              const prevRow = prevNode.item

              if (xTable.isTreeExpandByRow(prevRow)) { // 如果拖拽后,preRow有子节点,且preRow是展开状态
              // if (prevRow.children && prevRow.children.length > 0) { // 通过children只能判断是否为父节点,不能判断是否是展开状态
                // 移动到当前的子节点
                if (selfRow.parentorgnid !== prevRow.cguid) { // 如果拖拽后的额父节点和新的父节点不一致,不允许
                  this.restore(wrapperElem, targetTrElem, alternativeTrElem)
                  this.$message({
                    type: 'warning',
                    message: '只能在同级节点移动!'
                  })
                  return
                }
                selfNode.items.splice(selfNode.index, 1)
                selfNode.items.unshift(selfRow)// 在数组头部添加节点
              } else { // 拖拽后,和preRow是兄弟节点或和preRow的祖先节点是兄弟节点
                // 移动到相邻节点
                if (selfRow.parentorgnid !== prevRow.parentorgnid) { // 拖动的节点和它前面的节点在拖拽前不是同级节点
                /**
                 * case1:
                 * 将1. xxxx拖到2. xxxx前面时是不允许的
                 *
                 * 1. xxxx
                 *   1.1 xxxx
                 * 2. xxxx
                 *
                 */
                  if (xe.findTree(selfRow[options.children], row => prevRow === row, options)) { // 这里selfRow[options.children]是移动前的数据,不是移动后的
                    // 错误的移动
                    this.restore(wrapperElem, targetTrElem, alternativeTrElem)
                    this.$message({
                      type: 'warning',
                      message: '只能在同级节点移动!'
                    })
                    return
                  }
                  /**
                 * case2:
                 *
                 * 2.xxx和3.xxx是同级节点,3.xxx移动到2.xxx上方,但和preRow(1.1xxx)不是兄弟节点,这种情况是允许拖拽的
                 * 注意:拖拽3. xxx到2. xxx上方时,3. xxx成为1. xxxx的子节点还是同级节点是通过修改tableTreeData控制的,权限菜
                 * 单排序页面中选择前者,这里选择后者,因为这里只能在同级节点排序,如果选择前者,需要提示只能在同级节点排序,
                 * 但用户看来可能是个bug,因为看起来拖拽结果和1. xxxx是同级的,却提示只能在同级节点排序
                 * 1. xxxx
                 *   1.1 xxx
                 * 3. xxx
                 * 2. xxx
                 */
                  let nextTrElemIsOnSameLevelWidthTargetTrElemBeforeMove = false
                  if (nextTrElem) {
                    const nextNode = xe.findTree(tableTreeData, row => row.cguid === nextTrElem.getAttribute('rowid'), options)
                    const nextRow = nextNode.item
                    if (selfRow.parentorgnid === nextRow.parentorgnid) {
                      nextTrElemIsOnSameLevelWidthTargetTrElemBeforeMove = true// 若nextTrElem拖拽前和拖拽行同级,则为case2的情况
                    }
                  }
                  if (nextTrElemIsOnSameLevelWidthTargetTrElemBeforeMove === false) {
                    this.restore(wrapperElem, targetTrElem, alternativeTrElem)
                    this.$message({
                      type: 'warning',
                      message: '只能在同级节点移动!'
                    })
                    return
                  }
                }

                selfNode.items.splice(selfNode.index, 1)[0]
                const prevRowIndex = this.findPrevRowOrItsAncestorIndex(prevRow, selfNode)// case2的情况,prevRow不在selfNode.items中,需要递归寻找祖先节点在selfNode.items中的位置(或者获取nextNode的index,插到nextNode前面?nextNode可能也不是同一级节点)
                selfNode.items.splice(prevRowIndex + 1, 0, selfRow)
                if (this.isDraggedDown(targetTrElem, alternativeTrElem) && xTable.isTreeExpandByRow(selfRow)) { // 如果是向下拖拽节点,且拖拽节点是展开状态的父节点,需要将其子节点dom元素移动位置,否则子节点不会随父节点移动(向上拖拽时没有这个问题,不知道为什么)
                  let lastInsertTrElem = targetTrElem
                  while (oldIndex < wrapperElem.children.length) {
                    const trElem = wrapperElem.children[oldIndex]
                    const isSiblingNodes = selfNode.items.some(value => value.cguid === trElem.getAttribute('rowid'))
                    if (isSiblingNodes) { // 如果当前节点是节点拖拽前的兄弟节点,说明子节点已经全部访问完了, 停止迭代
                      break
                    }
                    this.insertAfter(trElem, lastInsertTrElem)
                    lastInsertTrElem = trElem
                  }
                }
              }
            } else {
              // 移动到第一行
              if (selfRow.parentorgnid !== '000000') { // 移动后位于第一行,父节点就是000000,若原来的父节点不是000000,则改变了父节点,不允许
                this.restore(wrapperElem, targetTrElem, alternativeTrElem)
                this.$message({
                  type: 'warning',
                  message: '只能在同级节点移动!'
                })
                return
              }
              selfNode.items.splice(selfNode.index, 1)[0]
              tableTreeData.unshift(selfRow)
            }

            // 如果变动了树层级,需要刷新数据
            this.tableData = [...tableTreeData]
          }
        })
      })
    },
    findPrevRowOrItsAncestorIndex(row, selfNode) {
      let prevRowIndex = -1
      selfNode.items.some((value, index) => {
        if (value.cguid === row.cguid) {
          prevRowIndex = index
          return true
        } else {
          return false
        }
      })
      if (prevRowIndex === -1) {
        const parentRow = xe.findTree(this.tableData, r => r.cguid === row.parentorgnid, { children: 'children' }).item
        return this.findPrevRowOrItsAncestorIndex(parentRow, selfNode)
      } else {
        return prevRowIndex
      }
    },
    // 是否是向下拖拽
    isDraggedDown(targetTrElem, alternativeTrElem) {
      const targetTrElemId = targetTrElem.getAttribute('rowid')
      const alternativeTrElemId = alternativeTrElem.getAttribute('rowid')
      const treeArray = xe.toTreeArray(this.tableData)// 按先序遍历输出树节点(与表格中从上到下行的排列顺序一致)
      return treeArray.some((value, index) => {
        // 若targetTrElem拖拽前在alternativeTrElem之前(即向下拖拽),返回true
        if (value.cguid === targetTrElemId && xe.findIndexOf(treeArray, item => item.cguid === alternativeTrElemId) > index) {
          return true
        }
      })
    },
    restore(wrapperElem, targetTrElem, alternativeTrElem) {
      const insertBefore = this.isDraggedDown(targetTrElem, alternativeTrElem)
      if (insertBefore) {
        wrapperElem.insertBefore(targetTrElem, alternativeTrElem)
      } else {
        this.insertAfter(targetTrElem, alternativeTrElem)
      }
    },
    insertAfter(newElement, targetElement) {
      var parent = targetElement.parentNode
      if (parent.lastChild === targetElement) {
        // 如果最后的节点是目标元素,则直接添加。因为默认是最后
        parent.appendChild(newElement)
      } else {
        // 如果不是,则插入在目标元素的下一个兄弟节点 的前面。也就是目标元素的后面
        parent.insertBefore(newElement, targetElement.nextSibling)
      }
    }
Logo

前往低代码交流专区

更多推荐