从零实现一套低代码(保姆级教程) --- 【3】实现Button组件和画布区的拖拽
摘要
目前是每天更新一篇, 因为我不止要写文章,这些代码也是我正在敲的。可能速度没有那么快,但是这个频率感觉还是可以的。
本篇是这个系列的第三篇,如果你是第一次看到这个文章,那你应该会对低代码有那么一丢丢兴趣或者很有兴趣。从标题来看,也知道我这个系列就是实现一个低代码的项目。
那如果你想知道,我实现的项目的样子是什么样的,可以访问下面的链接:
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
XinBuilder 点击跳转
如果你有兴趣,跟着实现这么一套代码,那么你可以从第一章节开始阅读:
从零实现一套低代码(保姆级教程) — 【1】初始化项目,实现左侧组件列表
因为上一篇文章,没有额外的提交,所有的代码都写在了文章里,所以这里就直接继续上一篇开始说。
在上一篇结束后,我们实现了左侧组件拖拽到画布区的效果!!!
但是因为我们组件里面只有一个文本,所以今天我们主要来实现组件,以及画布区中组件的拖拽。

1.组件的实现
在上一节中,我们在Button组件中,从props里面接收了style,从而实现组件在画布区的定位效果。
但是之所以显示文本,是因为我们写的就是文本,现在我们给它改成antD里面的Button组件。
import { Button as AntButton } from 'antd'export default function Button(props: any) {const {style} = propsreturn (<div style={style}><AntButton>按钮</AntButton></div>)
}
这里注意一下,因为我的组件就叫Button,如果引入antD的Button时,没有改名会引起报错
。
你可以有两种解决办法,一种是像我这样,另一种就是修改自己这个组件的名,例如不叫Button,叫XButton即可
当你修改完后,再拖拽组件,那么在画布区显示的就不是一段文本了!!!!

2.实现画布区的拖拽
现在呢,我们拖拽到画布区的组件,就定死在那里了,不能拖拽,现在我们要给画布区的组件添加拖拽的效果。
现在请读者打开mainPart下的index.jsx文件,看一下最终的return。
我们在遍历comList的时候,返回的Com外层应该包一层div用来实现拖拽的效果。

同时也要给这个外层的div一个拖拽的方法,onDragStart,由于在画布区拖拽组件,也只能拖拽到画布区,所以我们不需要像上次一样,做一些禁止默认行为的处理了。
// 画布区的组件拖拽方法const onDragStart = (com: ComJson) => {return () => {}}return (<div onDrop={onDrop} onDragOver={onDragOver} onDragEnter={onDragEnter} className='mainCom'>{comList.map(com => {const Com = components[com.comType as keyof typeof components];// 在外面包一层控制拖拽的divreturn <div draggable onDragStart={onDragStart(com)}><Com style={com.style} /></div>})}</div>)
OK。现在请打开浏览器看一下实现的效果吧,是不是发现有点问题,每次你在画布区拖拽后,都会生成一个新的组件,而不是组件的移动。这是为什么呢?

答案在我们写的onDrop方法里,因为每次拖拽完都会触发onDrop方法,每次都会push一个新的组件。
所以我们应该是从左侧组件列表拖拽的时候,进行push。如果是在画布区拖拽,我们只需要更新style属性即可。
那怎么判断是画布区拖拽的组件还是左侧栏拖拽的组件呢?我们可以在这里的onDragStart里,将window.nowCom设定为一个特殊的值。在onDrop根据这个值去判断走哪条分支。
// 用于保存当前画布区拖拽的节点const [dragCom, setDragCom] = useState<ComJson | null>(null)const onDrop = (e: any) => {const endLeft = e.clientX;const endTop = e.clientY;const style = {position: 'absolute',left: endLeft + 'px',top: endTop + 'px',zIndex:100}// 判断当前拖拽的节点是从画布区拖拽,还是左侧组件栏拖拽if(window.nowCom === 'renderCom' && dragCom) {dragCom.style = style}else{comList.push({comType: window.nowCom,style})}setComList([...comList])}const onDragStart = (com: ComJson) => {return () => {// 设置拖拽的节点和nowCom的固定值window.nowCom = 'renderCom';setDragCom(com)}}
现在请再看一下效果,在画布区的拖拽就已经解决了。
但是呢,是不是位置不太准确呢。
3.计算组件拖拽后的位置
好像在画布区拖拽完组件,新的位置总是差了那么一点,这是为什么呢?
先给一点提示,如果你拖拽的时候,鼠标位置在组件的左上角,那就没有问题了。
答案是,你给组件的left和top的定位,是基于鼠标位置的,也就是说,你把鼠标距离顶部和左部的距离,给了组件,那如果组件有自己的宽高,那么这个定位就是不准的。
所以为了算出正确的位置,我们应该计算出鼠标向左移动的距离和向上移动的距离。
然后加上组件本身的left和top值。
组件位置.left = 组件旧位置.left + 鼠标向左移动的距离
组件位置.top= 组件旧位置.top+ 鼠标向上移动的距离
那怎么计算鼠标移动的距离呢?我们可以在dragStart中,保存鼠标的位置。drop事件里,再保存一下鼠标的位置,诶?两者的差值不就是鼠标移动的位置嘛?
简单画一下流程图:

// 用来保存鼠标的开始位置和结束位置const distance = useRef<Distance>({startLeft: void 0,startTop: void 0,endLeft: void 0,endTop: void 0})const onDragStart = (com: ComJson) => {return (e: any) => {window.nowCom = 'renderCom';setDragCom(com);// 开始位置distance.current.startLeft = e.clientX;distance.current.startTop = e.clientY;}}const onDrop = (e: any) => {// 鼠标的结束位置distance.current.endLeft = e.clientX;distance.current.endTop = e.clientY;let style: any;if(window.nowCom === 'renderCom' && dragCom && dragCom.style) {// 根据鼠标位置的差值计算组件位置dragCom.style = {...dragCom.style,left: parseInt(dragCom.style.left) + (e.clientX - (distance.current.startLeft || 0)) + 'px',top: parseInt(dragCom.style.top) + (e.clientY - (distance.current.startTop || 0)) + 'px'}}else{style = {position: 'absolute',left: distance.current.endLeft + 'px',top: distance.current.endTop + 'px',zIndex:100}comList.push({comType: window.nowCom,style})}setComList([...comList])}
基于上面的实现,我们就完成了组件在画布区的拖拽了。
本章内容会提交在github上:
https://github.com/TeacherXin/XinBuilder2
commit: 第三节:实现Button组件和画布区的拖拽
博主补充
那如果现在你已经完成了所有的过程,你可以自己把其他组件的代码也补充一下。
我会在github上提交一段代码,用来补充文本框组件的代码,内容可以在github上查看
https://github.com/TeacherXin/XinBuilder2
commit: 第三节:实现Input组件代码
第二个问题,为什么鼠标的位置我们通过useRef来进行存储。因为这四个变量不需要更新去渲染组件,所以没必要通过useState去管理。但是呢,每次更新组件的时候还需要拿到之前的值,所以我们使用useRef进行管理。
第三个问题,现在我们有comList保存所有的组件数据,dragCom保存在画布区拖拽的组件。这个在后面,我们会采用redux进行管理,会有所更改,目前的话我们先使用这种模式来把整个流程串通。
最后,博主的TS可能没有那么熟练,如果有建议的话,博主也会积极采纳的!!!!
相关文章:
从零实现一套低代码(保姆级教程) --- 【3】实现Button组件和画布区的拖拽
摘要 目前是每天更新一篇, 因为我不止要写文章,这些代码也是我正在敲的。可能速度没有那么快,但是这个频率感觉还是可以的。 本篇是这个系列的第三篇,如果你是第一次看到这个文章,那你应该会对低代码有那么一丢丢兴趣…...
仓储1、10、11代电子标签接口文档
标签注册 仓储1代注册 侧面按钮连按三次, 注册成功:红灯变绿灯 仓储10代注册 右下角左下角组合按键触发注册 注册成功:右上角绿灯变红灯 仓储11代注册 磁体靠近条码附近,触发标签注册到系统 注册成功:闪红灯边绿…...
iOS将framework转为xcframework
拆分framework 先把framework拷贝到两个文件夹下边,这里只需要armv7、arm64、x86_64。 mkdir iphoneos iphonesimulator cp -R mysdk.framework iphoneos cp -R mysdk.framework iphonesimulator 把iphoneos中的模拟器指令集删除,只保留armv7和arm64 …...
2018年第七届数学建模国际赛小美赛C题共享单车对城市交通的影响解题全过程文档及程序
2018年第七届数学建模国际赛小美赛 C题 共享单车对城市交通的影响 原题再现: 共享自行车改变了许多城市的交通状况,许多大城市引入共享自行车来解决交通问题。我们需要定量评估共享自行车对城市交通的影响,以及相关的经济、社会和环境影响。…...
【数据结构】线段树算法总结(单点修改)
知识概览 用作单点修改的线段树有4个操作: pushup:由子节点的信息计算父节点的信息build:初始化一棵树modify:修改一个区间query:查询一个区间 线段树用一维数组来存储: 编号是x的节点,它的父节…...
数据分析:小红书过节“仪式感”营销种草
导语 过年的氛围是越来越浓,走亲访友,过节送礼都准备起来!据千瓜数据显示,“轻松买到仪式感”热度攀升,作为站内扶持的新兴话题,11月上线以来浏览量超2.5亿,笔记数超过20万篇。 看来ÿ…...
Zookeeper-应用实战
Zookeeper Java客户端实战 ZooKeeper应用的开发主要通过Java客户端API去连接和操作ZooKeeper集群。 ZooKeeper官方的Java客户端API。 第三方的Java客户端API,比如Curator。 ZooKeeper官方的客户端API提供了基本的操作:创建会话、创建节点、读取节点、更新数据、…...
2017年第六届数学建模国际赛小美赛A题飓风与全球变暖解题全过程文档及程序
2017年第六届数学建模国际赛小美赛 A题 飓风与全球变暖 原题再现: 飓风(也包括在西北太平洋被称为“台风”的风暴以及在印度洋和西南太平洋被称为“严重热带气旋”)具有极大的破坏性,往往造成数百人甚至数千人死亡。 许多气…...
Node.js使用Express框架写服务端接口时,如何将接口拆分到不同文件中
项目目录结构说明: node.js连接mysql数据库步骤可参考:Node.js 连接 MySQL | 菜鸟教程 1、拆分之前的写法,未区分模块,所有接口api都写在了入口文件app.js中; 需求:想要将接口api拆分成根据不同的业务模块…...
Unity | Shader基础知识(第八集:案例<漫反射材质球>)
目录 一、本节介绍 1 上集回顾 2 本节介绍 二、什么是漫反射材质球 三、 漫反射进化史 1 三种算法结果的区别 2 具体算法 2.1 兰伯特逐顶点算法 a.本小节使用的unity自带结构体。 b.兰伯特逐顶点算法公式 c.代码实现——兰伯特逐顶点算法 2.2 代码实现——兰伯特逐…...
NCV8460ADR2G在汽车和工业应用中高压侧驱动如何破?
NCV8460ADR2G是一款完全保护的高压侧驱动器,可用于开关各种负载,如灯泡、电磁阀和其他致动器。该器件可以通过有源电流限制和高温关断针对过载情况进行内部保护。 诊断状态输出引脚提供了高温以及开关状态开路负载情况的数字故障指示。 特性:…...
在打日志时,如何使用snowflake-id快速方便得随机获取query的唯一id
步骤一:安装snowflake-id pip install snowflake-id步骤二:代码示例 from snowflake import SnowflakeGeneratorgen SnowflakeGenerator(42)for i in range(100):val next(gen)print(val)参考文档: https://pypi.org/project/snowflake-…...
Linux之yum管理器
目录 yum管理器 yum相关指令 yum list yum list | grep yum install yum remove 拓展 1.yum install -y man-pages 2.切换yum源 3.yum install -y epel-release 4. yum install -y lrzsz rz指令 sz指令 在window系统上,我们会在电脑自带的应用商…...
ubuntu 搭建本地私有pip源
# 搭建本地私有pip源 pip install pip2pi# 创建目录 mkdir /data/work/PyPip/ mkdir /data/work/PyPip/packages cd /data/work/PyPip/# 创建需要从外网源同步的package touch requirements_roop.txt# 批量同步 pip2tgz /data/work/PyPip/packages -r requirements_roop.txt# 同…...
声音克隆:让你的声音变得无所不能
什么是声音克隆? 声音克隆是一种利用人工智能技术,根据一段声音样本,生成与之相似或完全相同的声音的过程。声音克隆可以用于多种场景。 声音克隆的原理是利用深度学习模型,从声音样本中提取声音特征,然后根据目标文…...
hadoop02_HDFS的API操作
HDFS的API操作 1 HDFS 核心类简介 Configuration类:处理HDFS配置的核心类。 FileSystem类:处理HDFS文件相关操作的核心类,包括对文件夹或文件的创建,删除,查看状态,复制,从本地挪动到HDFS文件系统中等。…...
使用C语言将ASCII明文编码为GSM短信体格式
一、背景介绍 GSM(Global System for Mobile Communications)是全球移动通信系统的简称,而GSM 03.38是GSM系统中用于短信编码的标准。GSM 03.38字符集采用7-bit编码,与ASCII的8-bit编码有所不同。为了将ASCII编码的文本转换为GSM…...
docker搭建mysql8.0.32,实现主从复制(一主两从)
安装docker的步骤、使用命令就不写了,本文章是基于会使用docker、linux基本命令的基础上来写的。 开始步骤: 1. 拉取 mysql 镜像 docker pull mysql:8.0.32 2. 启动容器并运行mysql a. 准备mysql的配置文件(该配置文件是:mysq…...
AOP springboot
1. 2. Around(“execution(* com.example.demo.controller..(…))”) 代表所有的类下面所有的方法任意参数 3....
Python Flask 基础入门第六课: Flask 全局变量 current_app, g 以及 session各自如何使用 有什么差异
全局变量 current_app, g 以及 session 全局变量差异汇总表current_app章节1 current_app - 当前应用实例current_app的基本概念current_app的作用current_app的使用 章节2:current_app的上下文什么是应用上下文?current_app与应用上下文的关系current_a…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...
