基於Vue實現能夠拖拽排序的樹形表格(已開源)

項目須要一個能夠拖拽排序的樹形表格,github上搜了一下,併爲找到,大部分都不支持拖拽,因此本身實現了一個簡單的組件,已開源 源代碼在這裏,併發布到npm上,若是有相似需求能夠試一下,chrome上沒有任何問題javascript

效果圖以下:在線體驗DEMO

drag-tree-table
drag-tree-table

使用方式


npm i drag-tree-table --save-dev
複製代碼
import dragTreeTable from 'drag-tree-table'
複製代碼
<dragTreeTable :data="treeData" :onDrag="onTreeDataChange"></dragTreeTable>
複製代碼
// state
treeData: {
  lists: [],
  columns: []
}
// methods
onTreeDataChange(list) {
  this.treeData.lists = list
},
複製代碼

數據源(lists)配置html

參數 可選值 描述
id String 惟一標誌
parent_id String 父節點ID
order Number 排序,0開始,onDrag後order會重置
name String 默認顯示內容
open Boolean true展開,false收起
lists Array 子節點

lists 配置示例vue

[
 {
   "id":40,
   "parent_id":0,
   "order":0,
   "name":"動物類",
   "uri":"/masd/ds",
   "open":true,
   "lists":[]
 },{
   "id":5,
   "parent_id":0,
   "order":1,
   "name":"昆蟲類",
   "uri":"/masd/ds",
   "open":true,
   "lists":[
     {
       "id":12,
       "parent_id":5,
       "open":true,
       "order":0,
       "name":"螞蟻",
       "uri":"/masd/ds",
       "lists":[]
     }
   ]
 },
 {
   "id":19,
   "parent_id":0,
   "order":2,
   "name":"植物類",
   "uri":"/masd/ds",
   "open":true,
   "lists":[]
 }
]
複製代碼

列(columns)配置java

參數 可選值 描述
type 'selection', 'actions' selection會顯示摺疊圖標,actions指操做欄
title String 表格標題
field String 單元格內容取值使用
width Number 單元格寬度
align left,center,right 單元格對齊方式,默認局左對齊
formatter Function 自定義單元格顯示內容,參數爲當前行數據

columns 配置示例git

[
 {
   type: 'selection',
   title: '菜單名稱',
   field: 'name',
   width: 200,
   align: 'center',
   formatter: (item) => {
     return '<a>'+item.name+'</a>'
   }
 },
 {
   title: '連接',
   field: 'url',
   width: 200,
   align: 'center'
 },
 {
   title: '操做',
   type: 'action',
   width: 350,
   align: 'center',
   actions: [
     {
       text: '查看角色',
       onclick: this.onDetail,
       formatter: (item) => {
         return '<i>查看角色</i>'
       }
     },
     {
       text: '編輯',
       onclick: this.onEdit,
       formatter: (item) => {
         return '<i>編輯</i>'
       }
     }
   ]
 },
]
複製代碼

實現原理


組件結構github

dragTreeTable.vue是入口組件,定義總體結構
row是遞歸組件(核心組件)
clolmn單元格,內容承載
space控制縮進
複製代碼

核心點chrome

1. 核心組件row,經過組件遞歸本身實現樹形結構,並不是用JSX方式
2. 經過v-html傳入函數,返回自定義html,實現單元格內容自定義
3. 經過H5的新特性draggable,實現拖拽,拖拽的同時須要匹配對應的行,並區分上/中/下,分別對應上移/下移/中間插入,並高亮,drop時對數組進行從新排列,生成新的數組
複製代碼

遞歸調用寫法以下shell

<template>
        <div class="tree-block" draggable="true" @dragstart="dragstart($event)" @dragend="dragend($event)">
            <div class="tree-row" @click="toggle" :tree-id="model.id" :tree-p-id="model.parent_id"> 
                <column v-for="(subItem, subIndex) in columns" v-bind:class="'align-' + subItem.align" :field="subItem.field" :width="subItem.width" :key="subIndex">
                    <span v-if="subItem.type === 'selection'">
                        <space :depth="depth"/>
                        <span v-if = "model.lists && model.lists.length" class="zip-icon" v-bind:class="[model.open ? 'arrow-bottom' : 'arrow-right']">
                        </span>
                        <span v-else class="zip-icon arrow-transparent">
                        </span>
                        <span v-if="subItem.formatter" v-html="subItem.formatter(model)"></span>
                        <span v-else v-html="model[subItem.field]"></span>

                    </span>
                    <span v-else-if="subItem.type === 'action'">
                        <a class="action-item" v-for="(acItem, acIndex) in subItem.actions" :key="acIndex" type="text" size="small" @click.stop.prevent="acItem.onclick(model)">
                            <i :class="acItem.icon" v-html="acItem.formatter(model)"></i>&nbsp;
                        </a>
                    </span>
                    <span v-else-if="subItem.type === 'icon'">
                         {{model[subItem.field]}}
                    </span>
                    <span v-else>
                        {{model[subItem.field]}}
                    </span>
                </column>
                <div class="hover-model" style="display: none">
                    <div class="hover-block prev-block">
                        <i class="el-icon-caret-top"></i>
                    </div>
                    <div class="hover-block center-block">
                        <i class="el-icon-caret-right"></i>
                    </div>
                    <div class="hover-block next-block">
                        <i class="el-icon-caret-bottom"></i>
                    </div>
                </div>
            </div>
            <row v-show="model.open" v-for="(item, index) in model.lists" :model="item" :columns="columns" :key="index" :depth="depth * 1 + 1" v-if="isFolder">
            </row>
        </div>
        
    </template>
複製代碼

動態匹配實現npm

filter(x,y) {
        var rows = document.querySelectorAll('.tree-row')
        this.targetId = undefined
        for(let i=0; i < rows.length; i++) {
          const row = rows[i]
          const rx = this.getElementLeft(row);
          const ry = this.getElementTop(row);
          const rw = row.clientWidth;
          const rh = row.clientHeight;
          if (x > rx && x < (rx + rw) && y > ry && y < (ry + rh)) {
            const diffY = y - ry
            const hoverBlock = row.children[row.children.length - 1]
            hoverBlock.style.display = 'block'
            const targetId = row.getAttribute('tree-id')
            if (targetId == window.dragId){
              this.targetId = undefined
              return
            }
            this.targetId = targetId
            let whereInsert = ''
            var rowHeight = document.getElementsByClassName('tree-row')[0].clientHeight
            if (diffY/rowHeight > 3/4) {
              if (hoverBlock.children[2].style.opacity !== '0.5') {
                this.clearHoverStatus()
                hoverBlock.children[2].style.opacity = 0.5
              }
              whereInsert = 'bottom'
            } else if (diffY/rowHeight > 1/4) {
              if (hoverBlock.children[1].style.opacity !== '0.5') {
                this.clearHoverStatus()
                hoverBlock.children[1].style.opacity = 0.5
              }
              whereInsert = 'center'
            } else {
              if (hoverBlock.children[0].style.opacity !== '0.5') {
                this.clearHoverStatus()
                hoverBlock.children[0].style.opacity = 0.5
              }
              whereInsert = 'top'
            }
            this.whereInsert = whereInsert
          }
        }
      }
複製代碼

更細的邏輯這裏不過多描述,感興趣能夠看一下源代碼,也歡迎指出問題,一塊兒優化,若是對你有幫助,也歡迎star

相關文章
相關標籤/搜索