mirror of https://github.com/ElemeFE/element
Tree: support drag and drop node (#9251)
parent
2098e36b51
commit
d7c4fd2632
|
@ -151,6 +151,52 @@
|
||||||
}]
|
}]
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
const data6 = [{
|
||||||
|
id: 1,
|
||||||
|
label: '一级 1',
|
||||||
|
children: [{
|
||||||
|
id: 4,
|
||||||
|
label: '二级 1-1',
|
||||||
|
children: [{
|
||||||
|
id: 9,
|
||||||
|
label: '三级 1-1-1'
|
||||||
|
}, {
|
||||||
|
id: 10,
|
||||||
|
label: '三级 1-1-2'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
id: 2,
|
||||||
|
label: '一级 2',
|
||||||
|
children: [{
|
||||||
|
id: 5,
|
||||||
|
label: '二级 2-1'
|
||||||
|
}, {
|
||||||
|
id: 6,
|
||||||
|
label: '二级 2-2'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
id: 3,
|
||||||
|
label: '一级 3',
|
||||||
|
children: [{
|
||||||
|
id: 7,
|
||||||
|
label: '二级 3-1'
|
||||||
|
}, {
|
||||||
|
id: 8,
|
||||||
|
label: '二级 3-2',
|
||||||
|
children: [{
|
||||||
|
id: 11,
|
||||||
|
label: '三级 3-2-1'
|
||||||
|
}, {
|
||||||
|
id: 12,
|
||||||
|
label: '三级 3-2-2'
|
||||||
|
}, {
|
||||||
|
id: 13,
|
||||||
|
label: '三级 3-2-3'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
|
||||||
let id = 1000;
|
let id = 1000;
|
||||||
|
|
||||||
const regions = [{
|
const regions = [{
|
||||||
|
@ -191,6 +237,27 @@
|
||||||
handleNodeClick(data) {
|
handleNodeClick(data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
},
|
},
|
||||||
|
handleDragStart(node, ev) {
|
||||||
|
console.log('drag start', node);
|
||||||
|
},
|
||||||
|
handleDragEnter(node, ev) {
|
||||||
|
console.log('tree drag enter: ', node.label);
|
||||||
|
},
|
||||||
|
handleDragLeave(node, ev) {
|
||||||
|
console.log('tree drag leave: ', node.label);
|
||||||
|
},
|
||||||
|
handleDragEnd(from, target, position, ev) {
|
||||||
|
console.log('tree drag end: ', target.label);
|
||||||
|
if (position !== null) {
|
||||||
|
console.log(`target position: parent node: ${position.parent.label}, index: ${position.index}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
allowDrop(from, target) {
|
||||||
|
return target.data.label !== '二级 3-1';
|
||||||
|
},
|
||||||
|
allowDrag(node) {
|
||||||
|
return node.data.label.indexOf('三级 3-1-1') === -1;
|
||||||
|
},
|
||||||
loadNode(node, resolve) {
|
loadNode(node, resolve) {
|
||||||
if (node.level === 0) {
|
if (node.level === 0) {
|
||||||
return resolve([{ name: 'region1' }, { name: 'region2' }]);
|
return resolve([{ name: 'region1' }, { name: 'region2' }]);
|
||||||
|
@ -300,6 +367,7 @@
|
||||||
data3,
|
data3,
|
||||||
data4: JSON.parse(JSON.stringify(data2)),
|
data4: JSON.parse(JSON.stringify(data2)),
|
||||||
data5: JSON.parse(JSON.stringify(data2)),
|
data5: JSON.parse(JSON.stringify(data2)),
|
||||||
|
data6,
|
||||||
regions,
|
regions,
|
||||||
defaultProps,
|
defaultProps,
|
||||||
props,
|
props,
|
||||||
|
@ -995,6 +1063,107 @@
|
||||||
```
|
```
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
### 可拖拽节点
|
||||||
|
|
||||||
|
通过draggable属性可让节点变为可拖拽,节点只能放到相同level节点旁边。
|
||||||
|
|
||||||
|
:::demo
|
||||||
|
```html
|
||||||
|
<el-tree
|
||||||
|
:data="data6"
|
||||||
|
node-key="id"
|
||||||
|
default-expand-all
|
||||||
|
@node-drag-start="handleDragStart"
|
||||||
|
@node-drag-enter="handleDragEnter"
|
||||||
|
@node-drag-leave="handleDragLeave"
|
||||||
|
@node-drag-end="handleDragEnd"
|
||||||
|
draggable
|
||||||
|
:allow-drop="allowDrop"
|
||||||
|
:allow-drag="allowDrag">
|
||||||
|
</el-tree>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
data6: [{
|
||||||
|
id: 1,
|
||||||
|
label: '一级 1',
|
||||||
|
children: [{
|
||||||
|
id: 4,
|
||||||
|
label: '二级 1-1',
|
||||||
|
children: [{
|
||||||
|
id: 9,
|
||||||
|
label: '三级 1-1-1'
|
||||||
|
}, {
|
||||||
|
id: 10,
|
||||||
|
label: '三级 1-1-2'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
id: 2,
|
||||||
|
label: '一级 2',
|
||||||
|
children: [{
|
||||||
|
id: 5,
|
||||||
|
label: '二级 2-1'
|
||||||
|
}, {
|
||||||
|
id: 6,
|
||||||
|
label: '二级 2-2'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
id: 3,
|
||||||
|
label: '一级 3',
|
||||||
|
children: [{
|
||||||
|
id: 7,
|
||||||
|
label: '二级 3-1'
|
||||||
|
}, {
|
||||||
|
id: 8,
|
||||||
|
label: '二级 3-2',
|
||||||
|
children: [{
|
||||||
|
id: 11,
|
||||||
|
label: '三级 3-2-1'
|
||||||
|
}, {
|
||||||
|
id: 12,
|
||||||
|
label: '三级 3-2-2'
|
||||||
|
}, {
|
||||||
|
id: 13,
|
||||||
|
label: '三级 3-2-3'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
defaultProps: {
|
||||||
|
children: 'children',
|
||||||
|
label: 'label'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDragStart(node, ev) {
|
||||||
|
console.log('drag start', node);
|
||||||
|
},
|
||||||
|
handleDragEnter(node, ev) {
|
||||||
|
console.log('tree drag enter: ', node.label);
|
||||||
|
},
|
||||||
|
handleDragLeave(node, ev) {
|
||||||
|
console.log('tree drag leave: ', node.label);
|
||||||
|
},
|
||||||
|
handleDragEnd(from, target, position, ev) {
|
||||||
|
console.log('tree drag end: ', target.label);
|
||||||
|
if (position !== null) {
|
||||||
|
console.log(`target position: parent node: ${position.parent.label}, index: ${position.index}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
allowDrop(from, target) {
|
||||||
|
return target.data.label !== '二级 3-1';
|
||||||
|
},
|
||||||
|
allowDrag(node) {
|
||||||
|
return node.data.label.indexOf('三级 3-1-1') === -1;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
### Attributes
|
### Attributes
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
| --------------------- | ---------------------------------------- | --------------------------- | ---- | ----- |
|
| --------------------- | ---------------------------------------- | --------------------------- | ---- | ----- |
|
||||||
|
@ -1017,6 +1186,9 @@
|
||||||
| accordion | 是否每次只打开一个同级树节点展开 | boolean | — | false |
|
| accordion | 是否每次只打开一个同级树节点展开 | boolean | — | false |
|
||||||
| indent | 相邻级节点间的水平缩进,单位为像素 | number | — | 16 |
|
| indent | 相邻级节点间的水平缩进,单位为像素 | number | — | 16 |
|
||||||
| lazy | 是否懒加载子节点,需与 load 方法结合使用 | boolean | — | false |
|
| lazy | 是否懒加载子节点,需与 load 方法结合使用 | boolean | — | false |
|
||||||
|
| draggable | 是否开启拖拽节点功能 | boolean | — | false |
|
||||||
|
| allow-drag | 判断节点能否被拖拽 | Function(Node) | — | — |
|
||||||
|
| allow-drop | 拖拽时判定位置能否被放置 | Function(fromNode, toNode) | — | — |
|
||||||
|
|
||||||
### props
|
### props
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
@ -1061,6 +1233,10 @@
|
||||||
| current-change | 当前选中节点变化时触发的事件 | 共两个参数,依次为:当前节点的数据,当前节点的 Node 对象 |
|
| current-change | 当前选中节点变化时触发的事件 | 共两个参数,依次为:当前节点的数据,当前节点的 Node 对象 |
|
||||||
| node-expand | 节点被展开时触发的事件 | 共三个参数,依次为:传递给 `data` 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 |
|
| node-expand | 节点被展开时触发的事件 | 共三个参数,依次为:传递给 `data` 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 |
|
||||||
| node-collapse | 节点被关闭时触发的事件 | 共三个参数,依次为:传递给 `data` 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 |
|
| node-collapse | 节点被关闭时触发的事件 | 共三个参数,依次为:传递给 `data` 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 |
|
||||||
|
| node-drag-start| 节点开始拖拽时触发的事件 | 共两个参数,依次为:被拖拽节点对应的 Node、Vue传来的drag event。 |
|
||||||
|
| node-drag-enter| 拖拽进入其他节点时触发的事件 | 共两个参数,依次为:所进入节点对应的 Node、Vue传来的drag event。 |
|
||||||
|
| node-drag-leave| 拖拽离开某个节点时触发的事件 | 共两个参数,依次为:所离开节点对应的 Node、Vue传来的drag event。(注意:上个节点的leave事件有可能在下个节点enter之后执行) |
|
||||||
|
| node-drag-end | 拖拽结束时触发的事件 | 共四个参数,依次为:被拖拽节点对应的 Node、结束拖拽时最后指向的节点、被拖拽节点的放置位置{ parent: 位置的父节点, index: 在父节点中的序号 }、Vue传来的drag event。|
|
||||||
|
|
||||||
### Scoped slot
|
### Scoped slot
|
||||||
| name | 说明 |
|
| name | 说明 |
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
@import "common/var";
|
@import "common/var";
|
||||||
|
|
||||||
@include b(tree) {
|
@include b(tree) {
|
||||||
|
position: relative;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
background: $--color-white;
|
background: $--color-white;
|
||||||
color: $--tree-text-color;
|
color: $--tree-text-color;
|
||||||
|
@ -21,6 +22,13 @@
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
color: mix($--color-primary, rgb(158, 68, 0), 50%);
|
color: mix($--color-primary, rgb(158, 68, 0), 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include e(drag-indicator) {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: $--color-primary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include b(tree-node) {
|
@include b(tree-node) {
|
||||||
|
@ -46,6 +54,17 @@
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $--tree-node-hover-color;
|
background-color: $--tree-node-hover-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-tree.dragging & {
|
||||||
|
cursor: move;
|
||||||
|
|
||||||
|
& * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-tree.dragging.drop-not-allow & {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include e(expand-icon) {
|
@include e(expand-icon) {
|
||||||
|
|
|
@ -6,6 +6,11 @@ export default class TreeStore {
|
||||||
this.currentNode = null;
|
this.currentNode = null;
|
||||||
this.currentNodeKey = null;
|
this.currentNodeKey = null;
|
||||||
|
|
||||||
|
this.dragSourceNode = null;
|
||||||
|
this.dragTargetNode = null;
|
||||||
|
this.dragTargetDom = null;
|
||||||
|
this.allowDrop = true;
|
||||||
|
|
||||||
for (let option in options) {
|
for (let option in options) {
|
||||||
if (options.hasOwnProperty(option)) {
|
if (options.hasOwnProperty(option)) {
|
||||||
this[option] = options[option];
|
this[option] = options[option];
|
||||||
|
|
|
@ -16,6 +16,14 @@
|
||||||
:aria-expanded="expanded"
|
:aria-expanded="expanded"
|
||||||
:aria-disabled="node.disabled"
|
:aria-disabled="node.disabled"
|
||||||
:aria-checked="node.checked"
|
:aria-checked="node.checked"
|
||||||
|
:draggable="tree.draggable"
|
||||||
|
@dragstart.stop="handleDragStart"
|
||||||
|
@dragenter.stop="handleDragEnter"
|
||||||
|
@dragleave.stop="handleDragLeave"
|
||||||
|
@dragover.stop="handleDragOver"
|
||||||
|
@dragend.stop="handleDragEnd"
|
||||||
|
@drop.stop="handleDrop"
|
||||||
|
ref="node"
|
||||||
>
|
>
|
||||||
<div class="el-tree-node__content"
|
<div class="el-tree-node__content"
|
||||||
:style="{ 'padding-left': (node.level - 1) * tree.indent + 'px' }">
|
:style="{ 'padding-left': (node.level - 1) * tree.indent + 'px' }">
|
||||||
|
@ -199,6 +207,92 @@
|
||||||
handleChildNodeExpand(nodeData, node, instance) {
|
handleChildNodeExpand(nodeData, node, instance) {
|
||||||
this.broadcast('ElTreeNode', 'tree-node-expand', node);
|
this.broadcast('ElTreeNode', 'tree-node-expand', node);
|
||||||
this.tree.$emit('node-expand', nodeData, node, instance);
|
this.tree.$emit('node-expand', nodeData, node, instance);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDragStart(ev) {
|
||||||
|
if (typeof this.tree.allowDrag === 'function' && !this.tree.allowDrag(this.node)) {
|
||||||
|
ev.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ev.dataTransfer.effectAllowed = 'move';
|
||||||
|
ev.dataTransfer.setData('text/plain', this.node.label);
|
||||||
|
this.node.store.dragSourceNode = this.node;
|
||||||
|
this.node.store.dragFromDom = this.$refs.node;
|
||||||
|
this.node.store.allowDrop = true;
|
||||||
|
this.tree.$emit('node-drag-start', this.node, ev);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDragEnter(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
const store = this.node.store;
|
||||||
|
const from = store.dragSourceNode;
|
||||||
|
let node = this.node;
|
||||||
|
let dom = this.$refs.node;
|
||||||
|
|
||||||
|
if (!from) return;
|
||||||
|
|
||||||
|
while (node.level > from.level && node.level > 1) {
|
||||||
|
node = node.parent
|
||||||
|
dom = this.$parent.$refs.node;
|
||||||
|
}
|
||||||
|
store.dragTargetNode = node;
|
||||||
|
store.dragTargetDom = dom;
|
||||||
|
|
||||||
|
if (!this.tree.dropAt) {
|
||||||
|
ev.dataTransfer.dropEffect = 'none';
|
||||||
|
store.allowDrop = false;
|
||||||
|
} else {
|
||||||
|
ev.dataTransfer.dropEffect = 'move';
|
||||||
|
store.allowDrop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tree.$emit('node-drag-enter', this.node, ev);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDragLeave(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (!this.node.store.dragSourceNode) return;
|
||||||
|
this.tree.$emit('node-drag-leave', this.node, ev);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDragOver(ev) {
|
||||||
|
ev.dataTransfer.dropEffect = this.node.store.allowDrop ? 'move' : 'none';
|
||||||
|
ev.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDrop(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDragEnd(ev) {
|
||||||
|
const from = this.node.store.dragSourceNode;
|
||||||
|
const target = this.node.store.dragTargetNode;
|
||||||
|
let position = this.tree.dropAt;
|
||||||
|
|
||||||
|
if (!from) return;
|
||||||
|
|
||||||
|
if (typeof this.tree.allowDrop === 'function' && !this.tree.allowDrop(from, target)) {
|
||||||
|
position = null;
|
||||||
|
}
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.dataTransfer.dropEffect = 'move';
|
||||||
|
|
||||||
|
if (target && from && from !== target && position) {
|
||||||
|
const index = from.parent.childNodes.indexOf(from);
|
||||||
|
from.parent.childNodes.splice(index, 1);
|
||||||
|
if (from.parent.childNodes.length === 0) {
|
||||||
|
from.parent.isLeaf = true;
|
||||||
|
}
|
||||||
|
position.parent.childNodes.splice(position.index, 0, from);
|
||||||
|
from.parent = position.parent;
|
||||||
|
from.parent.isLeaf = false;
|
||||||
|
}
|
||||||
|
this.tree.$emit('node-drag-end', from, target, position, ev);
|
||||||
|
this.node.store.dragTargetNode = null;
|
||||||
|
this.node.store.dragSourceNode = null;
|
||||||
|
this.node.store.dragTargetDom = null;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="el-tree"
|
class="el-tree"
|
||||||
:class="{ 'el-tree--highlight-current': highlightCurrent }"
|
:class="{
|
||||||
|
'el-tree--highlight-current': highlightCurrent,
|
||||||
|
dragging: !!store.dragSourceNode,
|
||||||
|
'drop-not-allow': !store.allowDrop
|
||||||
|
}"
|
||||||
role="tree"
|
role="tree"
|
||||||
>
|
>
|
||||||
<el-tree-node
|
<el-tree-node
|
||||||
|
@ -16,6 +20,12 @@
|
||||||
<div class="el-tree__empty-block" v-if="!root.childNodes || root.childNodes.length === 0">
|
<div class="el-tree__empty-block" v-if="!root.childNodes || root.childNodes.length === 0">
|
||||||
<span class="el-tree__empty-text">{{ emptyText }}</span>
|
<span class="el-tree__empty-text">{{ emptyText }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!!dropAt"
|
||||||
|
class="el-tree__drag-indicator"
|
||||||
|
:style="{top: dragIndicatorOffset}"
|
||||||
|
ref="drag-indicator">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -81,6 +91,12 @@
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
draggable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
allowDrag: Function,
|
||||||
|
allowDrop: Function,
|
||||||
props: {
|
props: {
|
||||||
default() {
|
default() {
|
||||||
return {
|
return {
|
||||||
|
@ -116,6 +132,40 @@
|
||||||
},
|
},
|
||||||
treeItemArray() {
|
treeItemArray() {
|
||||||
return Array.prototype.slice.call(this.treeItems);
|
return Array.prototype.slice.call(this.treeItems);
|
||||||
|
},
|
||||||
|
dragIndicatorOffset() {
|
||||||
|
if (!this.dropAt) return;
|
||||||
|
|
||||||
|
const dom = this.store.dragTargetDom;
|
||||||
|
if (this.store.dragSourceNode.level !== this.store.dragTargetNode.level) {
|
||||||
|
return (dom.offsetTop + dom.querySelector('.el-tree-node__content').scrollHeight) + 'px';
|
||||||
|
} else {
|
||||||
|
return (dom.offsetTop + dom.scrollHeight) + 'px';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dropAt() {
|
||||||
|
let target = this.store.dragTargetNode;
|
||||||
|
let from = this.store.dragSourceNode;
|
||||||
|
if (!target || !from) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof this.allowDrop === 'function' && !this.allowDrop(from, target)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.level === from.level - 1) {
|
||||||
|
return {
|
||||||
|
parent: target,
|
||||||
|
index: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (target.level === from.level) {
|
||||||
|
return {
|
||||||
|
parent: target.parent,
|
||||||
|
index: target.parent.childNodes.indexOf(target) + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue