前言

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

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

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

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

效果预览

通过动图我们可以了解,这个布局功能已经可以实现在元素前添加、元素后添加、元素内添加,并能支持复杂的父子关系嵌套的添加。 甚至结合Ant Design Vue的选项卡,也可以轻松使用。

知识储备

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

整体界面布局

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

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

文件名:Designer.vue

JavaScript拖拽事件简述

整体拖拽流程如下:

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

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

是不是很简单呢?

控件栏发起拖拽事件

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

draggable="true"

@dragstart="dragStart($event,item.value)"

@dragend="dragLeaveWorkSpace($event)">

{{item.name}}

data() {

return {

controlTools:[{

name:"Label",

value:'文本说明'

},{

name:"TextBox",

value:''

},{

name:"Button",

value:''

}]

}

}

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

dragStart(e,data) {

e.dataTransfer.setData("content", data);

},

dragLeaveWorkSpace(e) {

e.preventDefault();

}

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

把DemoBox的样式写上

.DemoBox {

border: 1px solid #a3a3a3;

padding: 50px;

box-sizing: border-box;

position: relative;

}

实现DropTarget 可拖放区域

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

@dragenter="dragEnter($event)"

@dragover="dragOver($event)"

@drop="drop($event)">

然后给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架构中,模型是软件开发的核心,程序员通过定义模型来描述系统的业务逻辑和功能需求,然后使用模型转换工具将模型转换成最终的代码。),从而向使用部门或客户提供能够支撑其完成信息化创新的基础工具。

好文阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: