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

楊金凱發表於2018-10-19

專案需要一個可以拖拽排序的樹形表格,github上搜了一下,併為找到,大部分都不支援拖拽,所以自己實現了一個簡單的元件,已開源原始碼在這裡,併發布到npm上,如果有類似需求可以試一下,chrome上沒有任何問題

效果圖如下:

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>
複製程式碼
// statetreeData: { 
lists: [], columns: []
}// methodsonTreeDataChange(list) {
this.treeData.lists = list
},複製程式碼

資料來源(lists)配置

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

lists 配置示例

[ { 
"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)配置

引數 可選值 描述
type ‘selection’, ‘actions’ selection會顯示摺疊圖示,actions指操作欄
title String 表格標題
field String 單元格內容取值使用
width Number 單元格寬度
align left,center,right 單元格對齊方式,預設局左對齊
formatter Function 自定義單元格顯示內容,引數為當前行資料

columns 配置示例

[ { 
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>
'

}
} ]
},]複製程式碼

實現原理


元件結構

dragTreeTable.vue是入口元件,定義整體結構row是遞迴元件(核心元件)clolmn單元格,內容承載space控制縮排複製程式碼

核心點

1. 核心元件row,通過元件遞迴自己實現樹形結構,並非用JSX方式2. 通過v-html傳入函式,返回自定義html,實現單元格內容自定義3. 通過H5的新特性draggable,實現拖拽,拖拽的同時需要匹配對應的行,並區分上/中/下,分別對應上移/下移/中間插入,並高亮,drop時對陣列進行重新排列,生成新的陣列複製程式碼

遞迴呼叫寫法如下

<
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>
複製程式碼

動態匹配實現

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

來源:https://juejin.im/post/5bc9355a5188255c3853955c

相關文章