<template> <div class="el-tree" :class="{ 'el-tree--highlight-current': highlightCurrent, 'is-dragging': !!dragState.draggingNode, 'is-drop-not-allow': !dragState.allowDrop, 'is-drop-inner': dragState.dropType === 'inner', }" role="tree" > <app-element-tree-node v-for="child in root.childNodes" :node="child" :props="props" :render-after-expand="renderAfterExpand" :show-checkbox="showCheckbox" :key="getNodeKey(child)" :render-content="renderContent" @node-expand="handleNodeExpand" > </app-element-tree-node> <div class="el-tree__empty-block" v-if="isEmpty"> <span class="el-tree__empty-text">{{ emptyText }}</span> </div> <div v-show="dragState.showDropIndicator" class="el-tree__drop-indicator" ref="dropIndicator"></div> </div> </template> <script> import { Tree } from 'element-ui'; import AppElementTreeNode from './app-element-tree-node.vue'; export default { mixins: [Tree], components: { AppElementTreeNode }, created() { let dragState = this.dragState; this.$on('tree-node-drag-start', async (event, treeNode) => { if (typeof this.allowDrag === 'function' && !(await this.allowDrag(treeNode.node))) { event.preventDefault(); dragState.draggingNode = null; return false; } event.dataTransfer.effectAllowed = 'move'; // wrap in try catch to address IE's error when first param is 'text/plain' try { // setData is required for draggable to work in FireFox // the content has to be '' so dragging a node out of the tree won't open a new tab in FireFox event.dataTransfer.setData('text/plain', ''); } catch (e) {} dragState.draggingNode = treeNode; this.$emit('node-drag-start', treeNode.node, event); }); this.$on('tree-node-drag-over', async (event, treeNode) => { const dropNode = this.findNearestComponent(event.target, 'AppElementTreeNode'); const oldDropNode = dragState.dropNode; if (oldDropNode && oldDropNode !== dropNode) { this.removeClass(oldDropNode.$el, 'is-drop-inner'); } const draggingNode = dragState.draggingNode; if (!draggingNode || !dropNode) return; let dropPrev = true; let dropInner = true; let dropNext = true; let userAllowDropInner = true; if (typeof this.allowDrop === 'function') { dropPrev = await this.allowDrop(draggingNode.node, dropNode.node, 'prev'); userAllowDropInner = dropInner = await this.allowDrop(draggingNode.node, dropNode.node, 'inner'); dropNext = await this.allowDrop(draggingNode.node, dropNode.node, 'next'); } event.dataTransfer.dropEffect = dropInner ? 'move' : 'none'; if ((dropPrev || dropInner || dropNext) && oldDropNode !== dropNode) { if (oldDropNode) { this.$emit('node-drag-leave', draggingNode.node, oldDropNode.node, event); } this.$emit('node-drag-enter', draggingNode.node, dropNode.node, event); } if (dropPrev || dropInner || dropNext) { dragState.dropNode = dropNode; } if (dropNode.node.nextSibling === draggingNode.node) { dropNext = false; } if (dropNode.node.previousSibling === draggingNode.node) { dropPrev = false; } if (dropNode.node.contains(draggingNode.node, false)) { dropInner = false; } if (draggingNode.node === dropNode.node || draggingNode.node.contains(dropNode.node)) { dropPrev = false; dropInner = false; dropNext = false; } const targetPosition = dropNode.$el.getBoundingClientRect(); const treePosition = this.$el.getBoundingClientRect(); let dropType; const prevPercent = dropPrev ? (dropInner ? 0.25 : dropNext ? 0.45 : 1) : -1; const nextPercent = dropNext ? (dropInner ? 0.75 : dropPrev ? 0.55 : 0) : 1; let indicatorTop = -9999; const distance = event.clientY - targetPosition.top; if (distance < targetPosition.height * prevPercent) { dropType = 'before'; } else if (distance > targetPosition.height * nextPercent) { dropType = 'after'; } else if (dropInner) { dropType = 'inner'; } else { dropType = 'none'; } const iconPosition = dropNode.$el.querySelector('.el-tree-node__expand-icon').getBoundingClientRect(); const dropIndicator = this.$refs.dropIndicator; if (dropType === 'before') { indicatorTop = iconPosition.top - treePosition.top; } else if (dropType === 'after') { indicatorTop = iconPosition.bottom - treePosition.top; } dropIndicator.style.top = indicatorTop + 'px'; dropIndicator.style.left = iconPosition.right - treePosition.left + 'px'; if (dropType === 'inner') { this.addClass(dropNode.$el, 'is-drop-inner'); } else { this.removeClass(dropNode.$el, 'is-drop-inner'); } dragState.showDropIndicator = dropType === 'before' || dropType === 'after'; dragState.allowDrop = dragState.showDropIndicator || userAllowDropInner; dragState.dropType = dropType; this.$emit('node-drag-over', draggingNode.node, dropNode.node, event); }); }, methods: { findNearestComponent(element, componentName) { let target = element; while (target && target.tagName !== 'BODY') { if (target.__vue__ && target.__vue__.$options.name === componentName) { return target.__vue__; } target = target.parentNode; } return null; }, removeClass(el, cls) { if (!el || !cls) return; var classes = cls.split(' '); var curClass = ' ' + el.className + ' '; for (var i = 0, j = classes.length; i < j; i++) { var clsName = classes[i]; if (!clsName) continue; if (el.classList) { el.classList.remove(clsName); } else if (hasClass(el, clsName)) { curClass = curClass.replace(' ' + clsName + ' ', ' '); } } if (!el.classList) { el.setAttribute('class', trim(curClass)); } }, addClass(el, cls) { if (!el) return; var curClass = el.className; var classes = (cls || '').split(' '); for (var i = 0, j = classes.length; i < j; i++) { var clsName = classes[i]; if (!clsName) continue; if (el.classList) { el.classList.add(clsName); } else if (!hasClass(el, clsName)) { curClass += ' ' + clsName; } } if (!el.classList) { el.setAttribute('class', curClass); } }, }, }; </script>