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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM