当前位置: 首页 > news >正文

简单的拖拉拽功能

<template> <div class="drag-page"> <!-- 左侧画布 --> <div class="canvas" @dragover.prevent @drop="onDrop"> <div v-for="item in canvasItems" :key="item.id" class="canvas-btn" :style="{ left: item.x + 'px', top: item.y + 'px' }" @mousedown.stop="(e) => startMove(e, item)" @contextmenu.prevent="(e) => openContextMenu(e, item)" > {{ item.label }} </div> </div> <!-- 右侧按钮库 --> <div class="button-group"> <div v-for="btn in buttonLib" :key="btn.id" class="lib-btn" draggable="true" @dragstart="(e) => startDragLib(e, btn)" > {{ btn.label }} </div> </div> <!-- 右键菜单 --> <div v-if="contextMenu.show" class="context-menu" :style="{ left: contextMenu.x + 'px', top: contextMenu.y + 'px' }" > <div class="menu-item" @click="handleEdit">编辑</div> <div class="menu-item" @click="handleDelete">删除</div> </div> <!-- 编辑弹窗 --> <el-dialog v-model="editVisible" title="编辑按钮" width="400px"> <el-input v-model="currentItem.label" placeholder="请输入按钮文字" /> <template #footer> <el-button @click="editVisible = false">取消</el-button> <el-button type="primary" @click="editVisible = false">确定</el-button> </template> </el-dialog> </div> </template> <script setup> import { ref, reactive } from 'vue' // 按钮库 const buttonLib = ref([ { id: 1, label: '确定' }, { id: 2, label: '取消' }, { id: 3, label: '编辑' }, { id: 4, label: '删除' }, ]) // 画布上的按钮 const canvasItems = ref([]) let dragItem = null // 拖拽右侧到左侧 function startDragLib(e, btn) { dragItem = btn } function onDrop(e) { if (!dragItem) return canvasItems.value.push({ id: Date.now(), label: dragItem.label, x: e.offsetX - 40, y: e.offsetY - 15, }) dragItem = null } // 画布内拖动 let moving = null let startX = 0 let startY = 0 function startMove(e, item) { moving = item startX = e.clientX - item.x startY = e.clientY - item.y document.addEventListener('mousemove', movingHandler) document.addEventListener('mouseup', stopMove) } function movingHandler(e) { if (!moving) return moving.x = e.clientX - startX moving.y = e.clientY - startY } function stopMove() { moving = null document.removeEventListener('mousemove', movingHandler) document.removeEventListener('mouseup', stopMove) } // 右键菜单 const contextMenu = reactive({ show: false, x: 0, y: 0, }) const currentItem = ref(null) const editVisible = ref(false) // 打开右键菜单 function openContextMenu(e, item) { currentItem.value = item contextMenu.x = e.clientX contextMenu.y = e.clientY contextMenu.show = true // 点击别处关闭 document.addEventListener('click', closeContextMenu) } // 关闭菜单 function closeContextMenu() { contextMenu.show = false document.removeEventListener('click', closeContextMenu) } // 编辑 function handleEdit() { editVisible.value = true closeContextMenu() } // 删除 function handleDelete() { if (!currentItem.value) return canvasItems.value = canvasItems.value.filter( (i) => i.id !== currentItem.value.id ) closeContextMenu() } </script> <style scoped> .drag-page { display: flex; gap: 20px; padding: 20px; height: 600px; } .canvas { flex: 1; border: 2px dashed #ccc; background: #f9f9f9; position: relative; overflow: hidden; } .canvas-btn { position: absolute; padding: 6px 14px; background: #409eff; color: #fff; border-radius: 4px; cursor: move; user-select: none; white-space: nowrap; } .button-group { width: 180px; display: flex; flex-direction: column; gap: 10px; } .lib-btn { padding: 10px; border: 1px solid #ddd; background: #fff; text-align: center; cursor: grab; } /* 右键菜单样式 */ .context-menu { position: fixed; z-index: 9999; width: 120px; background: #fff; border: 1px solid #eee; border-radius: 4px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); overflow: hidden; } .menu-item { padding: 8px 12px; cursor: pointer; } .menu-item:hover { background: #f5f5f5; } </style>
http://www.jsqmd.com/news/695049/

相关文章:

  • 别再乱连了!Altium Designer里Net Label、Port、Sheet Entry到底怎么选?一张图帮你理清
  • 从‘网红脸’到‘可控艺术’:用StyleGAN系列玩转人脸编辑的保姆级避坑指南
  • Python处理图片:用Pillow保存JPEG/PNG时,如何平衡‘体积’与‘画质’?一份实测指南
  • Docker部署vLLM大模型推理服务全攻略(2026年4月实测)
  • 时序数据库选型指南:我们是怎么评估和选型的
  • 全新租赁小程序系统源码 基于ThinkPHP+UniApp开发的租赁商城小程序
  • LinkedList 源码深度解析
  • 别再纠结SMA和EMA了!用Python的TA-Lib库5分钟搞定双均线交易策略回测
  • 从一次线上故障排查,我重新认识了Linux的nanosleep:它真的‘睡’得准吗?
  • ShortCut MoE模型分析
  • Windows多显示器DPI缩放终极指南:SetDPI命令行工具实战详解
  • 重庆漏水检测电话,消防管道漏水检测,自来水管道漏水检测,精准定位测漏,水管漏水检测(东哥漏水检测) - 品牌企业推荐师(官方)
  • 别再被‘WebSocket is already CLOSING’搞懵了!手把手教你用Node.js + 前端实现心跳保活与自动重连
  • C++26反射不是未来——是现在!3大主流构建系统(CMake 3.29+/Bazel 7+/Meson 1.5+)反射支持配置对比表
  • 浙江省cppm报名机构及联系方式(公示) - 品牌企业推荐师(官方)
  • 当你的微信视频通话响起时,5G核心网在背后做了什么?—— 深入解读Network Triggered Service Request
  • PS人像合成踩坑指南:解决发丝抠不干净、背景脱节问题
  • 赛博朋克2077存档编辑器:5步完全掌控你的游戏数据
  • 从Element Plus到Iconfont:在Vue3项目中优雅混用两套图标库的实战指南
  • 一线观察:杨浦全铝定制生产商的真实表现
  • 从飞机抗气流到轮船抗海浪:手把手拆解PID控制器在真实世界里的‘抗干扰’实战
  • FSEC赛车背后的‘数据大脑’:我们如何用C#和nRF24L01搭建了一套无线数据采集与可视化系统
  • Spring Boot项目里,用weixin-java-miniapp搞定小程序登录和发消息(保姆级配置)
  • 小程序搭建费用解析:预算有限怎么办
  • 别再乱传数据了!Vue3组件通信保姆级指南:从defineProps到mitt,5种方式一次讲透
  • 深入解析C++多态:虚函数与动态联编
  • 昆明考电工证怎么考?报考条件、流程及正规报名全指南 - 品牌企业推荐师(官方)
  • 深圳沙井高低温可靠性实验室
  • 避坑指南:在Windows和Ubuntu上部署Realsense D435i+YOLOv5环境,解决驱动和CUDA版本冲突
  • 用Python+Matplotlib复现光电效应实验:从数据采集到可视化分析全流程