从零实现一套低代码(保姆级教程) --- 【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…...
GLM-4V-9B开源模型部署教程:4-bit量化+Streamlit+消费级GPU全适配
GLM-4V-9B开源模型部署教程:4-bit量化Streamlit消费级GPU全适配 你是不是也遇到过这样的困扰:想本地跑一个真正能看图说话的多模态大模型,结果发现显存不够、环境报错、图片上传后模型乱输出,甚至直接卡死?官方Demo看…...
R语言孟德尔随机化环境搭建:手把手教你搞定gwasvcf、gwasglue等包的安装报错(附本地安装包)
R语言孟德尔随机化环境搭建:从报错到成功的全流程指南 第一次在R中安装孟德尔随机化相关工具包时,那种挫败感我至今记忆犹新。明明按照教程一步步操作,却不断遭遇各种报错——网络超时、依赖缺失、API限制...这些看似简单的问题足以让一个生物…...
终极指南:Heynote版本更新与数据迁移全攻略
终极指南:Heynote版本更新与数据迁移全攻略 【免费下载链接】heynote A dedicated scratchpad for developers 项目地址: https://gitcode.com/gh_mirrors/he/heynote Heynote作为开发者专用的即时记事本工具,定期更新能带来更稳定的性能和更丰富…...
探索多约束多目标粒子群算法在微电网优化运行中的应用
多约束多目标粒子群算法的微电网优化运行代码在如今追求能源高效利用与可持续发展的时代,微电网的优化运行显得尤为关键。而多约束多目标粒子群算法为微电网优化运行提供了一种极具潜力的解决方案。今天咱就来唠唠相关的代码实现。 粒子群算法基础回顾 粒子群算法&a…...
革命性智能求职助手:AI驱动的多平台简历投递解决方案
革命性智能求职助手:AI驱动的多平台简历投递解决方案 【免费下载链接】get_jobs 💼【找工作最强助手】全平台自动投简历脚本:(boss、前程无忧、猎聘、拉勾、智联招聘) 项目地址: https://gitcode.com/gh_mirrors/ge/get_jobs 你是否还…...
华大单片机实战:MT25QL128/256 FLASH驱动避坑指南(HOLD引脚必看)
华大单片机深度实战:MT25QL系列FLASH驱动设计与HOLD引脚关键配置解析 在嵌入式存储解决方案中,NOR Flash因其高可靠性和快速随机读取特性,成为众多工业级应用的首选。MT25QL系列作为美光推出的高性能SPI NOR Flash产品,凭借其宽电…...
PCIe设备内存映射IO(MMIO)详解:Non-Prefetchable与Prefetchable到底有啥区别?
PCIe设备内存映射IO(MMIO)深度解析:Non-Prefetchable与Prefetchable的设计哲学与工程实践 当你第一次在PCIe设备的规格书中看到"Non-Prefetchable"和"Prefetchable"这两个术语时,是否感到困惑?这两…...
用Python爬B站弹幕做情感分析:从数据抓取到SnowNLP实战,附完整代码
用Python解码B站弹幕情绪:从数据采集到情感建模的全链路实践 打开B站热门视频,满屏弹幕如潮水般涌来——这些实时滚动的文字背后,究竟藏着观众怎样的集体情绪?是"爷青回"的怀旧狂欢,还是"破防了"的…...
Lucky Lillia Bot技术架构深度解析:OneBot 11协议在NTQQ平台的实现方案
Lucky Lillia Bot技术架构深度解析:OneBot 11协议在NTQQ平台的实现方案 【免费下载链接】LuckyLilliaBot NTQQ的OneBot API插件 项目地址: https://gitcode.com/gh_mirrors/li/LuckyLilliaBot 在即时通讯机器人开发领域,协议标准化与平台适配一直…...
3个终极窗口隐藏技巧:如何用Boss-Key打造你的数字隐身衣
3个终极窗口隐藏技巧:如何用Boss-Key打造你的数字隐身衣 【免费下载链接】Boss-Key 老板来了?快用Boss-Key老板键一键隐藏静音当前窗口!上班摸鱼必备神器 项目地址: https://gitcode.com/gh_mirrors/bo/Boss-Key 你有没有经历过这样的…...
