项目须要一个能够拖拽排序的树形表格,github上搜了一下,并为找到,大部分都不支持拖拽,因此本身实现了一个简单的组件,已开源 源代码在这里,并发布到npm上,若是有相似需求能够试一下,chrome上没有任何问题javascript
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>
</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