左右两部分 中间选择按钮(添加,删除)

本文介绍了一段使用JavaScript实现的功能,当用户在左侧下拉菜单中选择选项时,该选项会自动移动到右侧下拉菜单中;反之,右侧下拉菜单的选择项也可以通过点击按钮移动到左侧。此功能适用于需要动态调整选项展示的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<script type="text/javascript">
window.onload = function() {
    // 获取两个 select 和两个 button
    var select_left = document.getElementById("left"),
        select_right = document.getElementById("right"),
        button_add = document.getElementById("add"),
        button_delete = document.getElementById("delete");
                            
    // 点击添加
    button_add.onclick = function() {
        // 获取左边被选择的 option 的索引
        var idx = select_left.selectedIndex;
		
        // 如果左边有 option 被选择
        if(idx !== -1) {
		alert(select_left[idx].value);
            // 将此 option 移动到右边
            select_right.appendChild(select_left[idx]);
        }else{
			alert('必须选择一个');
		}
    };
                            
    // 点击删除
    button_delete.onclick = function() {
        // 获取右边被选择的 option 的索引
        var idx = select_right.selectedIndex;
        // 如果右边有 option 被选择
        if(idx !== -1) {
            // 将此 option 移动到左边
            select_left.appendChild(select_right[idx]);
        }
    };
};
</script>
<select id="left" size="10" style="width: 100px;">
<option value="cpp">++</option>
<option value="java">JAVA</option>
<option value="html">HTML</option>
<option value="css">CSS</option>
<option value="js">JavaScript</option>
<option value="php">PHP</option>
</select>
        
<div style="display: inline-block;">
<input type="button" id="add" value="添加" />
<input type="button" id="delete" value="删除" />
</div>
        
<select id="right" size="10" style="width: 100px;">
        
</select>


<think>我们正在使用Vue2和ElementUI的el-tree组件实现双树数据穿梭拖拽功能。根据引用[1]和引用[2]的示例,我们可以构建一个包含两棵树的界面:左侧树作为数据源(可拖出),右侧树作为目标(可拖入)。同时,我们需要处理拖拽事件和节点检查(多选)事件。主要步骤:1.设置两棵树,分别绑定不同的数据源(leftData和rightData)。2.左侧树开启多选(show-checkbox)和拖拽(draggable),并禁止在内部拖拽(allow-drop返回false)。同时监听拖拽开始事件(node-drag-start)和节点勾选事件(check-change)。3.右侧树开启拖拽(draggable),并设置允许放置的条件(allow-drop)以及处理放置事件(node-drop)。4.实现拖拽逻辑:当从左侧树拖拽节点到右侧树时,将节点数据添加到右侧树,并从左侧树移除(或保留,取决于需求)。同时,我们也可以支持多选拖拽,即一次拖拽多个选中的节点。注意:由于el-tree的拖拽事件默认只传递被拖拽的节点,我们需要在拖拽开始时记录当前选中的节点(多选节点),以便在放置时处理多个节点。具体实现:模板部分(Template):```html<template><divclass="tree-transfer"><divclass="left-tree"><el-treeref="leftTree":data="leftData"node-key="id"show-checkboxdraggable:allow-drop="handleLeftAllowDrop"@node-drag-start="handleLeftDragStart"@check="handleLeftCheck":default-expanded-keys="leftExpandedKeys"></el-tree></div><divclass="right-tree"><el-treeref="rightTree":data="rightData"node-key="id"draggable:allow-drop="handleRightAllowDrop"@node-drop="handleRightDrop":default-expanded-keys="rightExpandedKeys"></el-tree></div></div></template>```脚本部分(Script):```javascriptexportdefault{data(){return{leftData:[],//左侧树数据rightData:[],//右侧树数据leftExpandedKeys:[],//左侧默认展开的节点rightExpandedKeys:[],//右侧默认展开的节点checkedLeftNodes:[],//左侧选中的节点(多选)};},methods:{//左侧树不允许内部拖拽(只允许拖出)handleLeftAllowDrop(draggingNode,dropNode,type){returnfalse;},//右侧树允许放置的条件:只能放置到节点上(inner)或者节点前后(prev/next),这里我们通常允许放置在节点上(成为子节点)或者节点前后(成为兄弟节点)//但根据需求,我们可能只允许放置在节点上(inner)或者只允许成为兄弟节点(prev/next),这里我们允许所有外部拖入,并限制内部拖拽?实际上,我们只关心从左侧拖拽过来的节点,所以可以判断draggingNode的来源handleRightAllowDrop(draggingNode,dropNode,type){//我们只允许从左侧树拖拽过来的节点(通过自定义属性标记?或者根据数据判断)//由于el-tree没有直接提供来源信息,我们在handleLeftDragStart中给draggingNode.data添加标记if(draggingNode.data.from==='left'){//允许放置在内部、前面或后面returntype!=='none';//实际上,只要不是'none',就是允许的放置位置}//如果是右侧树内部的节点,我们可以允许调整顺序(可选需求)//这里我们只处理跨树拖拽,所以右侧树内部拖拽不允许(或者允许,根据需求)returnfalse;},//左侧树开始拖拽时,记录当前选中的节点(多选)handleLeftDragStart(node){//获取当前选中的节点this.checkedLeftNodes=this.$refs.leftTree.getCheckedNodes();//如果当前拖拽的节点不在选中的节点中,则只拖拽当前节点(或者将当前节点加入选中节点?这里我们根据需求:如果拖拽一个未选中的节点,则只拖拽这个节点)//我们可以检查node是否在checkedLeftNodes中,如果不在,则设置checkedLeftNodes为[node]if(!this.checkedLeftNodes.some(item=>item.id===node.id)){this.checkedLeftNodes=[node];}//给被拖拽的节点标记来源(用于右侧树判断)node.data.from='left';},//左侧树节点勾选事件handleLeftCheck(checkedNodes,checkedKeys){//更新选中的节点this.checkedLeftNodes=checkedNodes;},//右侧树放置事件handleRightDrop(draggingNode,dropNode,type,event){//注意:draggingNode是拖拽节点对应的TreeNode(el-tree的节点对象),dropNode是放置的目标节点,type是放置的位置('before','after','inner')//我们需要处理的是多个节点的拖拽,但el-tree的拖拽事件只传递了当前拖拽的节点(即draggingNode),而我们之前记录了多个节点(this.checkedLeftNodes)//因此,我们需要遍历this.checkedLeftNodes,将每个节点添加到右侧树,并从左侧树移除//由于我们可能拖拽多个节点,我们需要避免重复添加(如果同一个节点被多次拖拽)//同时,我们还需要考虑节点在树中的唯一性(node-key="id")//获取目标位置:如果type为'inner',则添加到dropNode的子节点;如果为'before'或'after',则添加到dropNode的父节点下,并在相应的位置//但是,el-tree的node-drop事件中,一次只能处理一个节点的放置,而我们实际上只拖拽了一个节点(尽管我们记录了多个),因此我们需要在放置事件中处理多个节点?//问题:el-tree的拖拽事件设计为单节点拖拽。因此,我们需要改变策略:在放置事件中,我们只处理当前拖拽节点(即draggingNode对应的节点),而多选节点则通过其他方式(比如按钮)来转移。//但是,我们可以尝试在拖拽开始时,将多个节点数据存储起来,然后在放置事件中处理多个节点。然而,el-tree的拖拽事件并没有提供这样的接口。//因此,我们可能需要调整需求:只支持单个节点的拖拽穿梭。或者,我们通过其他方式实现多选节点的拖拽:比如在左侧树选中多个节点后,将第一个节点作为拖拽节点,然后在放置事件中,将选中的所有节点都转移过去。//这里我们采用:在放置事件中,将this.checkedLeftNodes中的所有节点都添加到右侧树,并从左侧树移除。//注意:由于我们拖拽的节点(draggingNode)是this.checkedLeftNodes中的一个,所以我们可以先处理这个节点(因为el-tree已经自动处理了这个节点的放置),然后我们再处理其他节点?但是el-tree的放置事件是在其内部处理完成后触发的,我们不能再重复添加这个节点。//因此,我们需要在放置事件中,排除已经由el-tree自动添加的节点(即draggingNode对应的节点),然后添加其他选中的节点。//然而,el-tree的拖拽事件并没有自动将节点从左侧树删除,也没有自动添加到右侧树,因为我们是从外部树拖拽过来的,所以我们需要手动处理所有节点的转移。//实际上,我们并没有启用左侧树的allow-drop,所以左侧树不会接受放置,因此el-tree不会自动删除左侧树的节点。同样,右侧树也不会自动添加节点,因为我们没有提供数据绑定的自动更新。所以我们需要手动更新数据。//因此,在handleRightDrop中,我们需要://1.将this.checkedLeftNodes中的所有节点数据添加到右侧树合适的位置。//2.从左侧树中移除这些节点。//但是,我们如何确定这些节点在右侧树中的位置?//根据放置的目标节点(dropNode)和放置类型(type)来确定。//由于我们处理的是多个节点,我们需要将它们作为一组节点添加到目标位置。//步骤://1.复制选中的节点数据(深拷贝,避免引用问题)constnodesToAdd=JSON.parse(JSON.stringify(this.checkedLeftNodes));//2.根据放置位置,将复制的节点数组添加到右侧树数据中//我们需要找到目标节点在右侧树数据中的位置(如果是inner,则添加到dropNode的children中;如果是before或after,则添加到dropNode的父节点的children中,并插入到相应位置)//注意:右侧树的数据是rightData,它是一个数组。而树形结构是嵌套的,所以我们需要递归查找目标节点。//由于树结构嵌套,我们可以编写一个工具函数来找到目标节点在数据中的路径,然后进行插入。//但是,这比较复杂。另一种思路是:我们利用el-tree的node-key,通过key找到节点,然后使用el-tree的append,insertBefore,insertAfter方法(需要获取节点对象)。但是,这些方法需要TreeNode对象,而我们在数据层操作并不直接使用TreeNode。//因此,我们直接操作数据(rightData和leftData)来实现。//由于操作树形数据较为复杂,这里我们简化:只支持将节点添加到目标节点的内部(作为子节点)或者作为目标节点的兄弟节点(前或后)。//具体操作://如果type为'inner',则添加到dropNode的子节点数组末尾。//如果type为'before'或'after',则将节点添加到dropNode的父节点的子节点数组中,并插入在dropNode的前面或后面。//我们需要一个函数来遍历树找到目标节点(通过node-key,即id)并返回节点对象及其父节点和兄弟数组。//由于时间关系,我们这里只处理第一层(如果右侧树只有一层)?但实际可能是多层。//这里我们写一个通用的方法,用于在树形数据中查找节点(带父节点信息):constfindNodeInTree=(tree,nodeId,parent=null)=>{for(leti=0;i<tree.length;i++){constnode=tree[i];if(node.id===nodeId){return{node,parent,index:i,siblings:tree};}if(node.children&&node.children.length){constresult=findNodeInTree(node.children,nodeId,node);if(result)returnresult;}}returnnull;};//在右侧树数据中查找目标节点(dropNode)constdropInfo=findNodeInTree(this.rightData,dropNode.data.id);if(!dropInfo){//如果没有找到,可能是根节点?或者放置目标为空(比如空树),那么我们就添加到根节点if(type==='inner'){//如果是inner,且没有找到节点,则无法放置在内部,我们添加到根节点末尾(作为新的根节点)this.rightData.push(...nodesToAdd);}else{//空树时,before和after没有意义,我们也添加到根节点this.rightData.push(...nodesToAdd);}}else{if(type==='inner'){//放置在目标节点内部if(!dropInfo.node.children){//如果目标节点没有子节点,则初始化this.$set(dropInfo.node,'children',[]);}dropInfo.node.children.push(...nodesToAdd);}else{//放置在目标节点的前面或后面constsiblings=dropInfo.siblings;constindex=dropInfo.index;if(type==='before'){siblings.splice(index,0,...nodesToAdd);}else{siblings.splice(index+1,0,...nodesToAdd);}}}//3.从左侧树中移除这些节点//同样,我们需要遍历左侧树,根据节点id删除节点constremoveNodesFromTree=(tree,nodeIds)=>{for(leti=0;i<tree.length;i++){constnode=tree[i];if(nodeIds.includes(node.id)){tree.splice(i,1);i--;//因为删除了一个元素,索引回退}elseif(node.children&&node.children.length){removeNodesFromTree(node.children,nodeIds);}}};constnodeIdsToRemove=nodesToAdd.map(node=>node.id);removeNodesFromTree(this.leftData,nodeIdsToRemove);//4.清空左侧树的选中状态this.$refs.leftTree.setCheckedKeys([]);this.checkedLeftNodes=[];//5.更新右侧树视图(由于我们直接操作数据,Vue的响应式会自动更新,但注意我们使用了$set来确保新增属性的响应式)},},mounted(){//初始化数据//假设从后台获取数据this.leftData=[{id:'1',label:'节点1'},{id:'2',label:'节点2',children:[{id:'3',label:'节点3'},{id:'4',label:'节点4'}]}];this.rightData=[{id:'5',label:'节点5'}];//默认展开所有节点this.leftExpandedKeys=['1','2','3','4'];this.rightExpandedKeys=['5'];}};```样式部分(Style):```css.tree-transfer{display:flex;justify-content:space-around;}.left-tree,.right-tree{width:45%;border:1pxsolid#ebeef5;padding:10px;}```注意:以上代码是一个基本实现,可能还需要根据具体需求调整,例如:-节点拖拽时的样式调整-节点唯一性校验(避免重复添加)-性能优化(大量节点时)-添加动画效果另外,由于跨树拖拽涉及复杂的数据操作,建议进行充分的测试。相关问题:1.如何实现el-tree节点拖拽时的自定义样式?2.如何优化el-tree在大数据量下的性能?3.如何实现节点拖拽的限制条件(如某些节点不允许拖拽)?</think>在 Vue2 中使用 Element UI 的 `el-tree` 组件实现双树数据穿梭拖拽功能,可以通过以下步骤实现: ### 解决方案 #### 1. 基础结构设置 ```html <template> <div class="tree-transfer"> <!-- 左侧树 (数据源) --> <el-tree ref="leftTree" :data="leftData" node-key="id" show-checkbox draggable :allow-drop="handleLeftAllowDrop" @node-drag-start="handleLeftDragStart" @check-change="handleCheckChange"> </el-tree> <!-- 右侧树 (目标) --> <el-tree ref="rightTree" :data="rightData" node-key="id" draggable :allow-drop="handleRightAllowDrop" @node-drop="handleRightDrop"> </el-tree> </div> </template> ``` #### 2. 核心逻辑实现 ```javascript export default { data() { return { leftData: [ // 左侧树数据源 { id: 1, label: '节点1' }, { id: 2, label: '节点2', children: [...] } ], rightData: [], // 右侧树数据源 draggedNodes: [] // 存储拖拽的节点 } }, methods: { // 左侧树允许拖拽条件(禁止内部拖拽) handleLeftAllowDrop() { return false; // 只允许拖出到右侧树 }, // 右侧树允许放置条件 handleRightAllowDrop(draggingNode, dropNode, type) { // 只允许从左侧树拖入 return draggingNode.data.from === 'left'; }, // 开始拖拽时记录选中节点 handleLeftDragStart(node) { // 获取所有选中的节点 this.draggedNodes = this.$refs.leftTree.getCheckedNodes(); // 标记来源(用于右侧树验证) node.data.from = 'left'; // 如果当前节点未选中,则只拖拽单个节点 if (!this.draggedNodes.some(n => n.id === node.id)) { this.draggedNodes = [node.data]; } }, // 处理节点放置 handleRightDrop(draggingNode, dropNode, type) { // 1. 复制节点数据(避免引用问题) const clonedNodes = this.draggedNodes.map(node => ({ ...node, from: 'right' // 标记已转入右侧 })); // 2. 根据放置位置处理 if (type === 'inner') { // 作为子节点 if (!dropNode.data.children) { this.$set(dropNode.data, 'children', []); } dropNode.data.children.push(...clonedNodes); } else { // 作为同级节点 const parent = dropNode.parent; const siblings = parent.data.children || parent.data; const index = siblings.findIndex(n => n.id === dropNode.data.id); siblings.splice(type === 'before' ? index : index + 1, 0, ...clonedNodes); } // 3. 从左侧树移除已转移节点 this.removeFromLeftTree(this.draggedNodes); // 4. 清除选中状态 this.$refs.leftTree.setCheckedKeys([]); }, // 从左侧树删除节点 removeFromLeftTree(nodes) { const removeIds = new Set(nodes.map(n => n.id)); const removeNode = (data) => { return data.filter(item => { if (removeIds.has(item.id)) return false; if (item.children) item.children = removeNode(item.children); return true; }); }; this.leftData = removeNode(this.leftData); }, // 处理多选变化 handleCheckChange(currentNode, isChecked) { // 更新多选状态 } } } ``` #### 3. 关键配置说明 1. **跨树标识** - 在拖拽开始时标记 `node.data.from = 'left'` - 在右侧树通过 `allow-drop` 验证来源 2. **多选支持** - 使用 `show-checkbox` 开启多选 - 通过 `getCheckedNodes()` 获取所有选中节点 3. **放置位置处理** - `type` 参数判断放置位置: - `'before'`:作为目标节点前同级 - `'after'`:作为目标节点后同级 - `'inner'`:作为子节点 4. **数据更新** - 使用 `$set` 确保新增属性的响应式 - 深拷贝避免数据引用问题 #### 4. 样式优化 ```css .tree-transfer { display: flex; justify-content: space-around; } .el-tree { width: 45%; border: 1px solid #ebeef5; border-radius: 4px; padding: 15px; } ``` ### 相关注意事项 1. **节点唯一性** 确保 `node-key` 使用的字段(如 `id`)在整个双树体系中唯一 2. **性能优化** - 大数据量时使用 `lazy` 加载 - 操作完成后手动更新视图: ```javascript this.$nextTick(() => { this.$refs.rightTree.update(); }); ``` 3. **扩展功能** - 添加穿梭按钮:通过 `@check-change` 记录选中节点,提供按钮手动穿梭 - 限制拖拽类型:在 `allow-drop` 中增加业务逻辑判断 ### 相关问题 1. 如何实现 `el-tree` 节点拖拽时的动画效果? 2. 当树数据量过大时,如何优化 `el-tree` 的渲染性能? 3. 如何实现跨树拖拽时的实时数据同步验证? 4. 怎样在 Vue2 中实现树节点的批量编辑功能? > 引用说明:本实现参考了 Element UI 官方文档中关于树形控件拖拽和多选的用法[^1],并结合 Vue2 的数据响应式特性实现跨树数据同步[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值