本文将带大家一起探索低代码开发的核心 —— 页面设计器
。
我们知道,低代码开发平台都是通过拖拉拽可视化的页面设计器进行页面开发的,在这一章节,我们来探索一下页面设计器的实现方式。下图中,我们截取了几款优秀的低代码产品的页面设计器界面。
image.png
可以看到,大多数的页面设计器都包含了如下所示的几个区域:
操作栏
,我们可进行页面的保存、预览、查看json信息、查看代码等操作;组件列表
,当然也可以添加一些切换,让我们的左侧区域支持查看页面树信息、配置数据源等其他操作;画布
区域,我们可以将左侧的组件拖动到画布中,当然也支持画布中组件的赋值、删除等操作;属性配置
区域,当我们在画布中选中某个组件时,可以在右侧的属性配置区域罗列出当前组件可支持动态配置的属性,修改了属性后可以在画布中看到对应组件的样式变化。图片截取自宜搭,仅用于区域展示,与本篇内容无关
下面,我们按上述的区域划分来看一下页面设计器区域都是如何实现的。
首先,我们来看一下左侧的组件列表,列表中的每个组件,我们都需要使用一段json
来进行描述,这段json
我们将它称之为 元数据,元数据中描述了当前组件的中文名称,在列表中显示的图标及描述,和组件可进行配置的一些动态属性。我们以输入框组件为例,它的元数据大致可以定义为如下的样子:
{
code: "MyInput",
name: "输入框",
desc: "输入框的描述",
icon: "input",
props: {
name: "字段名称",
label: "label名称",
labelCol: "",
wrapperCol: "",
required: false,
}
}
复制代码
那么左侧的组件列表实际上就是这样的一个元数据对象组成的数组遍历而来的。
再来看一下将左侧组件列表的组件拖动到画布是如何实现的。拖动又分为顺序排列布局的拖动及自由布局拖动。顺序排列布局的拖动是指拖动到画布中的组件是自上而下顺序排列的,可以通过拖动调整上下顺序,当然我们也可以增加分栏这样的布局类型组件,实现组件的左右排列;自由布局拖动是指拖动到画布中的组件位置是自由的,我们松开鼠标的位置,就是这个组件在画布中的位置。考虑到我们主要服务的是B端项目,需要尽可能的使用户体验保持一致,这里呢我们采用的是顺序排列布局的拖动。这样用户拖动设计出的页面差异性不会太大,页面布局上又相对规整。
拖动插件由于我们是vue
技术栈,选选择了vuedraggable
插件。像react
技术栈也有类似的插件,大家很容易可以搜索到。对于vuedraggable
组件的安装及说明这里我们就不赘述了,直接上demo。
<template>
<a-row style="padding: 20px">
<a-col span="10">
<h3>列表区域</h3>
<draggable
class="dragArea list-group"
:list="list1"
:sort="false"
:group="{ name: 'people', pull: 'clone', put: false }"
>
<div class="list-group-item" v-for="element in list1" :key="element.name" >
{{ element.name }}
</div>
</draggable>
</a-col>
<a-col span="10" offset="4">
<h3>目标区域</h3>
<draggable
class="dragArea list-group"
:list="list2"
group="people"
>
<div class="list-group-item" v-for="element in list2" :key="element.name" >
{{ element.name }}
</div>
</draggable>
</a-col>
</a-row>
</template>
<script>
import draggable from "vuedraggable";
export default {
components: {
draggable
},
data() {
return {
list1: [
{ name: "组件1", id: 1 },
{ name: "组件2", id: 2 },
{ name: "组件3", id: 3 },
{ name: "组件4", id: 4 }
],
list2: [
{ name: "画布组件1", id: 5 },
{ name: "画布组件2", id: 6 },
{ name: "画布组件3", id: 7 }
]
};
}
};
</script>
<style scoped>
.list-group-item {
height: 30px;
border: 1px solid #888888;
}
</style>
复制代码
呈现的样式如下图所示:
image.png
上面的demo定义了两个区域,列表区域和目标区域,并定义了两个数组,list1
和list2
。列表区域和目标区域分别使用list1
数组和list2
数组进行遍历渲染。当我们将列表区域的组件3
拖动到目标区域时,我们打印list2
变量的数据,就会 发现组件3
被复制到了list2
中,即复制到了目标区域。细心的小伙伴已经发现,唉!这不就是我们页面设计器组件拖动到画布中的实现方式嘛!是的,设计器中的拖动原理就是这样简单。
支持拖动的区域需要使用<draggable>
组件进行包裹,<draggable>
组可以添加onAdd
、onStart
、onEnd
及move
事件回调函数,我们可以在这些事件中添加一些我们需要的逻辑。例如,我们可以在onAdd
函数中对我们添加到list2
数组列表中的对象动态的添加一个唯一值id
,用于我们区分同一个页面拖入两个相同组件的情况。
下面让我们对上面的简单demo稍加改动:
列表区域
list1
数组的中的每一项修改为我们之前定义好的组件元数据。画布区域
我们现在知道,画布中的列表实际也是通过组件元数据数组进行渲染的,而每个原数据项都对应了一个真实的组件,这样我们只需要将元数据项替换成UI组件进行渲染就可以了。在代码中大致是如下的样子:
<template>
<div v-for="item in list2" :key="item.id">
<my-input v-if="item.code === 'MyInput'" :data="item"/>
<my-select v-if="item.code === 'MySelect'" :data="item"/>
...
</div>
</template>
复制代码
哈哈,有的小伙伴已经看出来了,这样写不太优雅,我们用动态组件优化一下。
<template>
<div v-for="item in list2" :key="item.id">
<component
:data="item"
:is="item.code"
/>
</div>
</template>
复制代码
2 . 画布中组件支持删除、复制、拖动操作
image.png
拖动
我们的demo示例中,目标区域list2
是支持可拖动排序的
复制
我们已经知道,画布中的组件是通过list2
遍历渲染出的。当点击复制操作时,只需要将当前被点击复制按钮的组件所对应的元数据添加到list2
中就可以了。这里需要注意,在复制元数据的时候,我们需要将id
属性值进行累加计算,这样才能区分被复制的组件和复制生成的组件。
删除
同理,删除操作,我们只需要将list2
中的组件通过被复制组件id
进行过滤就可以了。
到这里,有些小伙伴可能有些疑问,目前。组件拖动到画布进行显示已经问题不大了。那么组件中是否可以再拖入组件,就像我们在vue
编程中进行组件的嵌套一样呢?通过一些优秀低代码产品,我们可以发现,他们组件列表都是进行分类显示的,布局类组件就是这样一类可以在组件内部再进行拖动的组件了。这类组件包括栅格组件、容器组件、多页签组件、卡片组件等。我们知道,list2
就是最终页面渲染的组件列表,它是一个对象数组的数据结构,为了让它支持嵌套组件,我们需要在组件的元数据对象上增加一个属性,这个属性用来描述该组件下又嵌套了哪些组件,我们就命名这个属性为children
。那么,包含嵌套组件的页面数据大致就是下面所示的样子。
[{
code: "MyCard",
name: "卡片",
props: {
...卡片组件相关配置属性
},
children: [{
code: "MyContainer",
name: "容器",
props: {
...容器组件相关配置属性
},
children: [{
....
}]
}]
}]
复制代码
为了满足嵌套组件的需求,我们需要做两个方面的调整。
我们在容器类组件内部再次引用<draggable>
组件,组件的list
参数值为容器组件元数据的children
数组,然后在draggable
组件内部使用插槽将children
进行渲染。容器组件的模板大致是下面的样子
<template>
<div>
<draggable class="dragArea list-group" :list="data.children" handle=".drag-icon"
@add="handleAdd" @start="handleStart" @end="handleEnd">
<slot>
</slot>
</draggable>
</div>
</template>
复制代码
2 . 画布能够按照嵌套组件进行显示
对于无嵌套组件的页面,渲染时我们只需要对list2
数组进行遍历渲染就可以了。但是具有嵌套组件的页面这种简单的for
循环就无法满足了,我们需要对组件进行深层循环遍历。例如下面的实例代码。
export default {
props: {
data: Array,
},
methods: {
renderTree (list, id) {
return list.map((item) => {
return (
<content-item data={item} id={id} >
{item.children && item.children.length ? this.renderTree(item.children, item.id) : null}
</content-item>
);
});
}
},
render: function (h) {
return (
<div>
{this.renderTree(this.data)}
</div>
);
}
};
复制代码
这样我们就扫清了拖动的一切障碍。下面,让我们把目光聚焦到页面设计器右侧的属性配置区域。
其实到这里,很多小伙伴应该已经大致能够推理出属性配置后画布中组件根据配置进行显示的联动是如何进行的了。原理同样非常简单。我们可以对画布中的组件添加点击事件,当点击某个组件时,我们能够获取到当前点击组件的组件类型,例如输入框、下拉选择等等,针对每一种组件,我们已经提前在元数据中的props
属性定义了这个组件能够进行动态控制的参数,我们只需要将这些参数以合适的表单形式展示在右侧的属性配置区域就可以了,例如,按钮组件的props
中有一个text
属性,用来控制按钮的显示文案,那么我们就在右侧属性控制区域用一个输入框来做为控制这个属性的表单形式,当修改数据时,我们找到list2
中该组件所在的元数据对象,然后将该对象中props
属性中text
属性值修改为输入框中的内容。每个组件都会接受这个组件对应的元数据props
参数,然后根据参数值进行渲染。例如按钮组件,现在按钮的文案时,我们可以使用props.text
进行显示。
从上面的文章中可以看出,一个页面实际就是用一段带有层级结构的json
来进行描述的。
保存时实际就是将这段json
进行保存操作,我们可以将json
存储到数据库中。
在上面讲解画布区域时,我们已讲到组件如何通过json进行渲染。预览以及真实的页面渲染实际和画布中组件的展示实现原理完全一致。其中的区别有两点:(1)画布中的组件不支持交互操作,这里,我们需要屏蔽画布中组件的交互操作。我们可以通过css
中的after
伪类,设置content
为""
来实现。(2)画布中的组件需要包裹一个div
,这个div
包含了复制、删除等功能。
其实整个页面设计器的核心就是json
,其它各种功能也都是围绕json
进行。我相信大家仔细读完这篇文章,再看其它功能时也可以推断出其实现的原理。
好啦,至此页面设计器的组件列表、画布和属性配置三个区域的联动我们就都实现了。目前,我们的页面设计器设计出的界面还是静态的,画布中的组件也都是独立而毫无关联的。在真实的业务场景,组件间的通讯是非常常见的。在后面的章节中,我们讲会重点介绍低代码中如何进行 组件间通讯
的配置。我们一起手拉手,搭建自己的低代码平台~!
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8