vue2使用el-tree实现两棵树间节点的拖拽复制

发布于:2025-05-29 ⋅ 阅读:(26) ⋅ 点赞:(0)

原文链接两棵el-tree的节点跨树拖拽实现

参照这篇文章,把它做成组件,新增左侧树(可拖出)被拖节点变灰提示;

拖拽中
在这里插入图片描述
拖拽后
在这里插入图片描述
TreeDragComponent.vue

<template>
    <!-- 两棵el-tree的节点跨树拖拽实现 -->
    <div class="tree-drag">
        <!-- 左侧树(可拖出) -->
        <el-tree
        :data="treeData1"
        ref="tree1"
        class="tree" 
        node-key="id"
        draggable
        default-expand-all
        :allow-drop="returnFalse"
        :props="defaultProps"
        @node-drag-start="handleDragstart"
        @node-drag-end="handleDragend"
        >
        <!-- 使用作用域插槽自定义节点样式 -->
        <span slot-scope="{ node, data }" :class="{ 'custom-red': data.isHighlighted }">
            {{ node.label }}
        </span>
        </el-tree>
        
        <!-- 右侧树(可拖入) -->
        <el-tree
        :data="treeData2" 
        ref="tree2"
        class="tree" 
        node-key="id"
        draggable
        default-expand-all
        :props="defaultProps"
        :allow-drop="returnTrue"
        ></el-tree>
    </div>
</template>
    
<script>
export default {
name: "TreeDrag",
props:['treeData1','treeData2'],
data() {
    return {
        defaultProps: {
        children: 'children',
        label: 'name'
    },
    };
},
methods: {

    // (1) 手动触发:该节点向父组件通知拖拽开始(只不过目标树是右侧树)
    // 经过这个移花接木的操作,右侧的树会误以为是自身的节点触发了tree-node-drag-start事件,它会将被拖拽节点保存在组件内
    handleDragstart (node, event) {
    this.$refs.tree2.$emit('tree-node-drag-start', event, {node: node});
    },

    // (2) 鼠标滑过阶段
    // 当鼠标拖拽着左侧树上的节点从右侧树上的节点划过(也就是触发dragover事件)时,右侧树会误以为是在拖拽自己的节点,因此会在tree-node-drag-over事件中自动执行位置计算,所以这一阶段无需我们干预。

    // (3)鼠标释放阶段
    // 尽管此时右侧树已经误以为被拖拽的是自身节点,但被拖拽的节点此时仍然是左侧树的子组件,所以当鼠标释放时,它只能向左侧树(即它的父组件)触发tree-node-drag-end事件。由于左侧树不允许释放,所以此时节点并没有发生移动。
    // 为了让右侧树收到鼠标释放的通知,我们开始进行第二次移花接木,即把左侧树上发生的tree-node-drag-end事件以同样的方式触发给右侧树
    handleDragend (draggingNode, endNode, position, event) {
        //不只是做节点移动,而是拖拽复制,也就是要保留左侧树上的原节点
        //真正要保留原节点很难实现,所以选择在移动后恢复左侧树上的节点
        // 插入一个空节点用于占位
        let emptyData = {id: (+new Date), children: []};
        this.$refs.tree1.insertBefore(emptyData, draggingNode);

        this.$refs.tree2.$emit('tree-node-drag-end', event);
        this.$nextTick(() => {
            // 如果是移动到了当前树上,需要清掉空节点
            if (this.$refs.tree1.getNode(draggingNode.data)) {
                this.$refs.tree1.remove(emptyData);
            } else {
                // 如果移动到了别的树上,需要恢复该节点,并清掉空节点
                let data = JSON.parse(JSON.stringify(draggingNode.data));
                this.createHighlightedNode(data); // 添加标记字段
                this.$refs.tree1.insertAfter(data, this.$refs.tree1.getNode(emptyData));
                this.$refs.tree1.remove(emptyData);
            }
        })
    },

    // 递归创建带高亮标记的节点
    createHighlightedNode(data) {
        // 给当前节点添加 isHighlighted 属性
        data.isHighlighted = true;

        // 检查是否有子节点
        if (data.children && Array.isArray(data.children)) {
            // 递归处理每个子节点
            data.children.forEach(child => {
                this.createHighlightedNode(child);
            });
        }

        return data;
    },
    
    // 允许内部拖拽
    returnTrue () {
        // 可传参数:draggingNode, dropNode, type
        // 校验所有拖拽节点是否允许放入(如只能放入同级或特定父级)
        // return this.draggingNodes.every(node => 
        //     node.level <= 2 &&  // 限制最大层级
        //     node.type !== 'system'  // 限制类型
        // )
    return true;
    },
    // 禁止内部拖拽
    returnFalse () {
    return false;
    }
}
};
</script>

<style lang="scss" scoped>
.tree-drag {
    display: flex;
    justify-content: space-between;
    .tree {
        flex-grow: 1;
        .custom-red {
            color: #E4E4E4 !important; /* 红色背景 */
            font-size: 14px !important;
        }
    }

}
</style>
    

Parent.vue

<TreeDrag :treeData1="treeDataLeft" :treeData2="treeDataRight"></TreeDrag>

element-ui源码中用于定义树节点的element-ui\packages\tree\src\tree-node.vue组件
在这里插入图片描述

下一篇:在此基础上加功能,el-tree拖拽事件,限制同级拖拽,获取拖拽后节点的前后节点,同级拖拽合并父节点name且子节点加入目标节点里


网站公告

今日签到

点亮在社区的每一天
去签到