低代码信创开发核心技术(二):手撕灵活好用的Vue拖拉拽布局系统

这篇具有很好参考价值的文章主要介绍了低代码信创开发核心技术(二):手撕灵活好用的Vue拖拉拽布局系统。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

随着信息化时代的到来,软件已经成为企业和个人不可或缺的工具。然而,许多人在开发软件时遇到了各种问题,比如开发周期长、技术门槛高、成本高昂等等。为了解决这些问题,低代码平台应运而生。低代码平台是一种快速开发工具,它可以帮助开发者快速构建应用程序,从而提高开发效率和降低成本。

近年来,国产软件市场蓬勃发展,越来越多的企业开始关注自主创新和信息化发展。低代码平台作为信息化创新的重要工具,也逐渐受到了广泛关注。同时,随着Vue.js等前端框架的普及,拖拉拽布局系统也成为了低代码平台的核心技术之一。灵活好用的拖拉拽布局系统,能够帮助开发者快速搭建界面,提高开发效率。

在这篇博客中,我们将深入探究基于Vue.js的拖拽布局的实现方法,以及如何使用它来快速构建应用程序的界面。

阅读本文结合请结合上一篇文章理解:
低代码信创开发核心技术(一):基于Vue.js的描述依赖渲染DDR实现模型驱动的组件

效果预览

拖拉拽开发,低代码信创,低代码,vue.js,前端,devops,敏捷开发
通过动图我们可以了解,这个布局功能已经可以实现在元素前添加、元素后添加、元素内添加,并能支持复杂的父子关系嵌套的添加。
拖拉拽开发,低代码信创,低代码,vue.js,前端,devops,敏捷开发
甚至结合Ant Design Vue的选项卡,也可以轻松使用。

知识储备

1、您需要先掌握Vue.js 3.0版本的相关知识。
2、您需要先了解浏览器内置的window对象、事件驱动机制和拖拽相关的API。
3、您需要先了解HTML5和CSS3的相关知识。

整体界面布局

拖拉拽开发,低代码信创,低代码,vue.js,前端,devops,敏捷开发

这里我们设想左边是一个工具栏,通过拖拽到中间工作空间可以把控件拖拽上去,与此同时未来在选中控件之后,在右侧还可以设置控件的相关属性。因为写这篇博客的时候没有找美工,所以尽管丑了点,功能还是设想的比较全面的。

这里我们用FrontendBlocks设计一下,一键生成出来界面布局代码:

文件名:Designer.vue

<template>
	<div class="root">
		<div class="DesignerControls">
			<div class="BtnControl">Label</div>
		</div>
		<div class="DesignerWorkSpace">
		</div>
		<div class="DesignerAttrs">
			<div class="attrItem">
				<div class="attrName">属性名</div>
				<input class="attrValue" />
			</div>
		</div>
	</div>
</template>

<script>
	export default {
		props: ['id', 'text', 'context', 'initData'],
		data() {
			return {}
		},
		mounted() {
			this.context.initControl(this)
		}
	}
</script>

<style>
	html,
	body,
	.root {
		padding: 0;
		margin: 0;
		width: 100%;
		height: 100%;
	}

	.root {
		box-sizing: border-box;
		font-weight: normal;
		font-style: normal;
		text-align: left;
		display: flex;
		position: relative;
		width: 100%;
		flex-direction: row;
		justify-content: flex-start;
		align-items: flex-start;
	}

	.DesignerControls {
		box-sizing: border-box;
		background-color: rgba(243, 243, 243, 1);
		font-weight: normal;
		font-style: normal;
		text-align: left;
		flex-shrink: 0;
		display: flex;
		position: relative;
		width: 180px;
		height: 100%;
		padding: 8px;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
		overflow: hidden scroll;
	}

	.BtnControl {
		box-sizing: border-box;
		border: 1px solid rgba(0, 0, 0, 1);
		border-top: 1px solid rgba(0, 0, 0, 1);
		border-bottom: 1px solid rgba(0, 0, 0, 1);
		border-left: 1px solid rgba(0, 0, 0, 1);
		border-right: 1px solid rgba(0, 0, 0, 1);
		font-size: 12px;
		font-weight: normal;
		font-style: normal;
		text-align: left;
		display: flex;
		position: relative;
		width: 100%;
		padding: 8px;
		margin: 0px 0px 8px 0px;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
	}

	.DesignerWorkSpace {
		box-sizing: border-box;
		font-weight: normal;
		font-style: normal;
		text-align: left;
		position: relative;
		width: 100%;
		height: 100%;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
	}

	.DesignerAttrs {
		box-sizing: border-box;
		background-color: rgba(243, 243, 243, 1);
		font-weight: normal;
		font-style: normal;
		text-align: left;
		flex-shrink: 0;
		display: flex;
		position: relative;
		width: 240px;
		height: 100%;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
		overflow: hidden scroll;
	}

	.attrItem {
		box-sizing: border-box;
		border: 1px solid rgba(167, 167, 167, 1);
		border-top: none;
		border-bottom: 1px solid rgba(167, 167, 167, 1);
		border-left: none;
		border-right: none;
		font-weight: normal;
		font-style: normal;
		text-align: left;
		display: flex;
		position: relative;
		width: 100%;
		padding: 4px;
		flex-direction: row;
		justify-content: flex-start;
		align-items: center;
	}

	.attrName {
		box-sizing: border-box;
		font-size: 12px;
		font-weight: normal;
		font-style: normal;
		text-align: left;
		flex-shrink: 0;
		display: flex;
		position: relative;
		width: 30%;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
	}

	.attrValue {
		box-sizing: border-box;
		border: 1px solid rgba(0, 0, 0, 1);
		border-top: 1px solid rgba(0, 0, 0, 1);
		border-bottom: 1px solid rgba(0, 0, 0, 1);
		border-left: 1px solid rgba(0, 0, 0, 1);
		border-right: 1px solid rgba(0, 0, 0, 1);
		font-weight: normal;
		font-style: normal;
		text-align: left;
		display: flex;
		position: relative;
		width: 100%;
		padding: 5px;
		flex-direction: column;
		justify-content: flex-start;
		align-items: flex-start;
	}
</style>

JavaScript拖拽事件简述

整体拖拽流程如下:
拖拉拽开发,低代码信创,低代码,vue.js,前端,devops,敏捷开发

给需要拖拽的元素绑定 dragstart 事件,该事件在开始拖拽时触发。在该事件中,我们可以设置拖拽数据和拖拽效果。
当鼠标移动到其他元素上时,会触发 dragenter 事件。在该事件中,我们可以设置拖拽目标元素的样式,以反馈给用户当前的拖拽位置。
接着,dragover 事件会持续触发,直到拖拽元素离开了拖拽目标元素。在该事件中,我们可以阻止默认行为,以便能够将元素放置到拖拽目标元素上。
当拖拽元素被放置到拖拽目标元素上时,会触发 drop 事件。在该事件中,我们可以获取拖拽数据,并进行相应的处理操作。
最后,当拖拽完成时,会触发 dragend 事件。在该事件中,我们可以进行一些清理工作,比如重置拖拽元素的样式。

需要注意的是:
拖拽过程中,需要设置拖拽元素和拖拽目标元素的 draggable 属性为 true。
在 dragover 事件中,需要阻止默认行为,以便能够将元素放置到拖拽目标元素上。
在 drop 事件中,需要阻止默认行为,并且需要确保拖拽元素和拖拽目标元素都支持相应的拖拽类型。
在 dragstart 事件中,可以设置拖拽数据和拖拽效果。在 drop 事件中,可以获取拖拽数据,并进行相应的处理操作。
在 dragend 事件中,需要重置拖拽元素的样式,并进行一些清理工作。

是不是很简单呢?

控件栏发起拖拽事件

接下来我们稍作改造,先把Label那个可以拖拽的按钮改成由JSON维护的:

<div v-for="(item) in controlTools" class="BtnControl"
    draggable="true"
    @dragstart="dragStart($event,item.value)"
	@dragend="dragLeaveWorkSpace($event)">
	{{item.name}}
</div>
		data() {
			return {
				controlTools:[{
					name:"Label",
					value:'文本说明'
				},{
					name:"TextBox",
					value:'<input type="text" />'
				},{
					name:"Button",
					value:'<button type="button">搜索</button>'
				}]
			}
		}

这里的我们定义了两个事件处理,一个是dragStart,另一个是dragLeaveWorkSpace。先在methods里把这两个函数实现了,不过我写的时候也不知道dragLeaveWorkSpace应该处理什么,反正先留着空:

dragStart(e,data) {
	e.dataTransfer.setData("content", data);
},
dragLeaveWorkSpace(e) {
	e.preventDefault();
}

然后为了方便测试,我们把工作区改造一下:

<DropTarget class="DesignerWorkSpace">
	<DropTarget class="DemoBox">
		<div class="DemoBox">
			<DropTarget class="DemoBox">
				<div class="DemoBox">
					<DropTarget class="DemoBox">
					</DropTarget>
				</div>
			</DropTarget>
		</div>
	</DropTarget>
	<div class="DemoBox" style="display: flex;flex-direction: row;margin-top: 10px;">
		<DropTarget class="DemoBox" style="width: 100%;padding: 20px;"></DropTarget>
		<DropTarget class="DemoBox" style="width: 100%;padding: 20px;"></DropTarget>
		<DropTarget class="DemoBox" style="width: 100%;padding: 20px;"></DropTarget>
		<DropTarget class="DemoBox" style="width: 100%;padding: 20px;"></DropTarget>
		<DropTarget class="DemoBox" style="width: 100%;padding: 20px;"></DropTarget>
	</div>
</DropTarget>

把DemoBox的样式写上

.DemoBox {
	border: 1px solid #a3a3a3;
	padding: 50px;
	box-sizing: border-box;
	position: relative;
}

实现DropTarget 可拖放区域

我们写个新组件DropTarget.vue,因为上面工作区我们要往里放子元素,所以自然就要用到slot。

<div class="DropTarget"
	@dragenter="dragEnter($event)"
	@dragover="dragOver($event)"
	@drop="drop($event)">
	<slot></slot>
</div>

然后给DropTarget上个背景色,这个不是必须的

.DropTarget {
	background-color: #96969632;
}

接下来写dragEnter方法,考虑到有可能有子元素父元素之间的关系处理,那么就先写个递归函数放methods里,向上不断找父级,只要碰到class里有DropTarget的,那一定是目标组件。

calcRealTarget(element) {
	let target = element;
	if (target.classList.contains("DropTarget")) {
		return target;
	}
	if (!element.parentElement) return null;
	return this.calcRealTarget(element.parentElement)
}

然后我们正式开始写dragEnter方法:

dragEnter(e) {
	let target = this.calcRealTarget(e.target)
	if (target == null) return false;
	e.preventDefault();
	e.stopPropagation();
	// 处理添加
	target.originBgColor = target.style.backgroundColor
	target.style.outline = "1px dashed #74c3ff"
	target.style.outlineOffset = "-1px"
	target.classList.add('draging')
	if (window.currentDropTarget && window.currentDropTarget != target) {
		let oldDropTarget = window.currentDropTarget
		oldDropTarget.style.backgroundColor = oldDropTarget.originBgColor
		oldDropTarget.style.outline = null
		oldDropTarget.classList.remove('draging')
	}
	window.currentDropTarget = target
}

这里有个技巧,使用outline样式,相比于border来说,它不会因为占文档流的像素而使界面位置发生位移,特别是复杂界面处理的过程中,向外差一两个像素都是灾难性的。这里outlineOffset设置-1px,可以有效防止和边框border重叠,观感更好。
代码中,我们每次DragEnter事件中都会往window对象写一个属性currentDropTarget,把当前对象传过去。这里判断一遍,主要是为了防止鼠标从父容器移动到子容器的时候会再次触发DragEnter事件,为了不出现卡顿,展现出丝般顺滑的感觉,这里需要判断一下,一旦发生了同级别之间的目标转移,则立即把前一个组件的样式清掉。
为什么我这里不用dragLeave事件呢?那是因为鼠标拖拽划过父子容器的时候势必会触发一次dragLeave,从而导致样式被莫名清空。

为了配合鼠标动作,有一个直观的展现,那么这里我们用伪类做一个不会吃掉鼠标事件的半透明遮罩,测试一下,瞬间这感觉就上来了。不过要注意的是,可拖放区域一定要显式声明position样式,否则这个遮罩会超出边界。

.draging::before {
	content: ' ';
	width: 100%;
	height: 100%;
	left: 0px;
	top: 0px;
	opacity: 0.3;
	position: absolute;
	background-color: #74c3ff;
	z-index: 99999;
	pointer-events: none;
}

接下来我们编写dragOver事件的函数:

dragOver(e) {
	let target = this.calcRealTarget(e.target)
	if (target == null) return false;

	// 判断什么时候可以显示排序:1、当前事件对象不是容器 2、当前事件对象虽然是容器,但父组件也是容器
	let showOrderLine = false;
	if (target != e.target) {
		showOrderLine = true;
	}
	if (target.parentElement.classList.contains("DropTarget")) {
		showOrderLine = true
	}

	// 清除原有提示状态
	if (window.currentDropBefore && window.currentDropBefore != e.target) {
		let oldDropBefore = window.currentDropBefore
		oldDropBefore.style.borderInlineStart = null
		window.currentDropBefore = null;
	}
	if (window.currentDropAfter && window.currentDropAfter != e.target) {
		let oldDropAfter = window.currentDropAfter
		oldDropAfter.style.borderInlineEnd = null
		window.currentDropAfter = null
	}
	// 需要显示则显示
	if (showOrderLine) {
		if (e.offsetX < (e.target.offsetWidth * 0.25)) {
			e.target.style.borderInlineStart = "2px solid #ff6600"
			window.currentDropBefore = e.target
			if (window.currentDropAfter) {
				window.currentDropAfter.style.borderInlineEnd = null
			}
		} else if (e.offsetX > (e.target.offsetWidth * 0.75)) {
			e.target.style.borderInlineEnd = "2px solid #ff6600"
			window.currentDropAfter = e.target
			if (window.currentDropBefore) {
				window.currentDropBefore.style.borderInlineStart = null
			}
		} else {
			e.target.style.borderInlineStart = null
			e.target.style.borderInlineEnd = null
			window.currentDropBefore = null
			window.currentDropAfter = null
		}
	}
	e.preventDefault();
},

因为我们在实现拖放的同时还要实现排序,所以这里我们就约定一个可拖放区域的左侧25%的区域是同级别向前插入一个元素,右侧25%区域是向同级别后面追加一个元素,只有中间的50%区域是向其中填充子元素,如果前后无需排序,则整片区域都是当做添加子元素,鼠标拖拽到哪个区域,便会有对应区域的样式展示,这里使用borderInlineStart和borderInlineEnd可以很方便的展现样式,为什么用到Inline呢?这是因为outline不支持分别设置外框线。
然后我们实现drop方法:

drop(e) {
	let target = this.calcRealTarget(e.target)
	if (target == null) return false;
	e.preventDefault();
	e.stopPropagation();
	let data = e.dataTransfer.getData('content');

	// 清除因拖拽产生的样式
	if (window.currentDropTarget) {
		window.currentDropTarget.style.backgroundColor = window.currentDropTarget.originBgColor
		window.currentDropTarget.style.outline = null
		window.currentDropTarget.classList.remove('draging')
	}
	if (window.currentDropBefore) window.currentDropBefore.style.borderInlineStart = null
	if (window.currentDropAfter) window.currentDropAfter.style.borderInlineEnd = null

	// 完成拖拽操作并添加DOM元素
	// TODO:这里需要修改一下,我们可以通过DOM元素的.__vnode.ctx.proxy属性获取到VUE的vnode对象
	// 或者是通过VUE当前组件的.$el和实际拿到的DOM元素进行匹配。两种方式都可以找到VUE的代理对象
	// 通过代理对象,可以将这里面的slot换成通过数组来维护,通过DDR方式,递归将JSON渲染成组件
	// 如果不了解数组和控件系统的思想,可以看上一篇文章
	let newNode=document.createElement("div")
	newNode.innerHTML=data
	if (window.currentDropBefore) {
		window.currentDropBefore.parentElement.insertBefore(newNode, window.currentDropBefore)
	} else if (window.currentDropAfter) {
		window.currentDropAfter.after(newNode)
	} else {
		window.currentDropTarget.appendChild(newNode)
	}
}

这里把传递过来的信息接收到变量data里,然后清除掉所有临时加的样式,接下来就是创建元素、把元素放置在合适的位置上即可。
如果结合上篇文章,这里其实最好是用系统预设的组件,比如uiTextBox.vue,全程JSON控制,控制起来非常方便。

dragEnd事件处理

最后就是我们回到Designer.vue里,照着刚才的drag最后清理的逻辑,当拖拽结束时还原样式。这里后续要做二次拖拽(工具栏拖拽到工作区,从工作区一个组件拖拽到另一个组件里),我们可以在这个事件处理中销毁原组件。

dragLeaveWorkSpace(e) {
	e.preventDefault();
	if (window.currentDropTarget) {
		let oldDropTarget = window.currentDropTarget
		oldDropTarget.style.backgroundColor = oldDropTarget.originBgColor
		oldDropTarget.style.outline = null
		oldDropTarget.classList.remove('draging')
		window.currentDropTarget = null
	}
}

总结

本文主要介绍了基于Vue.js的拖拽布局的实现方法和如何使用它来快速构建应用程序的界面。深入探究了基于Vue.js的拖拽布局的实现方法,并展示了其效果预览和整体界面布局。最后,通过包括Vue.js 3.0版本、浏览器内置的window对象、事件驱动机制、拖拽相关API非常简单的实现了拖拽布局机制。
当我们能够创造出快速完成布局的系统之后,我们就可以结合上文所说的控件系统完成控件属性的设置、生成JSON代码保存到后台,然后再从后台读出JSON来渲染界面。
当我们能够快速批量的制造页面之后,就可以开始考虑结合后台整体实现模型驱动架构(MDA:Model Driven Architecture,它是一种软件设计方法论,通过将系统的业务逻辑和技术实现分离,将系统的关注点从技术层面转移到业务层面,提高了软件的可维护性和可重用性。在MDA架构中,模型是软件开发的核心,程序员通过定义模型来描述系统的业务逻辑和功能需求,然后使用模型转换工具将模型转换成最终的代码。),从而向使用部门或客户提供能够支撑其完成信息化创新的基础工具。文章来源地址https://www.toymoban.com/news/detail-665333.html

到了这里,关于低代码信创开发核心技术(二):手撕灵活好用的Vue拖拉拽布局系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数字IC手撕代码-平头哥技术终面手撕真题

     前言:         本专栏旨在记录高频笔面试手撕代码题,以备数字前端秋招,本专栏所有文章提供原理分析、代码及波形,所有代码均经过本人验证。 目录如下: 1.数字IC手撕代码-分频器(任意偶数分频) 2.数字IC手撕代码-分频器(任意奇数分频) 3.数字IC手撕代码-分

    2024年02月05日
    浏览(23)
  • 低代码助力ERP开发:实现负担得起、灵活与高效的解决方案

    企业资源规划工具或 ERP 不再为大型国际企业所保留。如今,从 SME 到大型企业,各种规模的企业都使用 ERP 软件来管理其核心流程。全球ERP 软件市场每年价值超过 250 亿美元,年增长率为 10% 到 20%。如此巨大增长的原因是什么? ERP 系统将您所有不同的工具/应用程序(包括库

    2024年02月19日
    浏览(42)
  • 为业务数据库注入低代码智能:提高开发效率与灵活性的关键实践

    随着公司试图减少工作量并让他们的业务上线,很明显,大多数产品创意和功能需求都可以通过以最少的手动编码构建的可立即运行但可定制的应用程序进行分类。借助迎合公民开发人员的低代码开发平台,低代码开发成为过度劳累的 IT 部门新的具有成本效益的速赢方法。

    2024年02月20日
    浏览(21)
  • Vue:核心技术

    1、{{可以写什么}}: ①在data中声明的变量,函数都可以。②常量都可以。③只要是合法的JavaScript表达式都可以 ④模板表达式都被放在沙盒中,只能访问全局变量的一个白名单:  \\\'Infinity,undefined,NaN,isFinite,isNaN,\\\'  \\\'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,

    2023年04月19日
    浏览(28)
  • GBASE金融信创优秀解决方案鉴赏 · 核心业务系统数据库解决方案

    为此,实验室特别开设金融信创优秀解决方案专栏,集中展示优秀成果。现在,让我们一起来领略下GBASE的优秀解决方案吧~ 可点击阅读原文  → 《金融信创优秀解决方案--核心业务系统数据库解决方案》。 核心业务系统数据库解决方案 方案简介 随着技术的不断创新发展,银

    2024年02月10日
    浏览(34)
  • 车载开发核心技术——SystemUI控制技术

    SystemUI是指车载开发中的一个重要组件,它负责管理和控制车机的用户界面和交互功能。本文将详细介绍SystemUI的各项控制技术,包括音量控制、RingtonePlayer、电源管理、任务管理、通知栏和服务定制,并提供相关代码示例和解析。 SystemUI中的音量控制功能主要用于调节车机的

    2024年02月14日
    浏览(30)
  • Vue2向Vue3过度核心技术路由

    1.思考 单页面应用程序,之所以开发效率高,性能好,用户体验好 最大的原因就是: 页面按需更新 比如当点击【发现音乐】和【关注】时, 只是更新下面部分内容 ,对于头部是不更新的 要按需更新,首先就需要明确: 访问路径 和 组件 的对应关系! 访问路径 和 组件的对

    2024年02月11日
    浏览(36)
  • Vue2向Vue3过度核心技术插槽

    1.作用 让组件内部的一些 结构 支持 自定义 2.需求 将需要多次显示的对话框,封装成一个组件 3.问题 组件的内容部分, 不希望写死 ,希望能使用的时候 自定义 。怎么办 4.插槽的基本语法 组件内需要定制的结构部分,改用****占位 使用组件时, ****标签内部, 传入结构替换slo

    2024年02月11日
    浏览(30)
  • 物联网平台开发核心技术实战-初识

    近期,有部分小伙伴对物联网比较感兴趣,但是又没有实际开发物联网平台的经验。所以,后面我会针对物联网平台开发做一系列的讲解,希望能帮助对这块感兴趣的小伙伴。wx公众: 架构师修炼 ,首发 随着科技的不断进步,物联网正在成为一个日趋重要的领域。物联网可

    2024年02月03日
    浏览(32)
  • VsCode 常用好用插件/配置+开发Vue 必装的插件

    1、实时刷新网页的插件:LiveServer 2、open in browser 支持快捷键与鼠标右键快速在浏览器中打开html文件,支持自定义打开指定的浏览器,包括:Firefox,Chrome,Opera,IE以及Safari 补充一下:LiveServer 和 open in browser 区别:注意观察浏览器地址栏 open in browser:直接打开HTML文件就是通

    2024年02月08日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包