前端低代码平台实现思路-数据格式
2020年做了一个代码生成小工具,虽然用的人不多,不过也是自己从零到一搞出来的,最近也在看低代码平台的一些竞品,所以现在想一想,有必要把自己的一些实现思路捋一捋。其实网上一搜低代码平台什么的,有很多例子,不过我这里仅仅把自己走过的坑列出来,避免其他人再走了。
这次主要是讲数据格式,为啥把这个放在前面,因为这个是决定你后面整体的实现方案和后续扩展,如果数据格式没有定义好,那后面升级扩展可都是大问题了。其实这个格式的定义,我前前后后推翻了3次,就是考虑到后面的扩展升级问题。
简单粗暴型
第一次数据格式定义是很简单的扁平的,但是后面发现做到组件的嵌套和插槽的嵌套,那就没办法实现了,所以第一次就这么失败了,这里给一个低代码平台的json格式,和我当时定义的非常类似。
{
"type": "input",
"icon": "icon-input",
"options": {
"width": "100%",
"defaultValue": "",
"required": false,
"placeholder": "",
"size": "",
"disabled": false
},
"name": "单行文本",
"key": "1643163772265",
"model": "input_1643163772265",
"rules": []
}
这么定义,虽然简单明了,但是后面嵌套就实现不了,而且他这个数据格式的定义,其实就只能实现表单这种方式(并且是把饿了么组件在平台上面又封装了一遍),没有考虑嵌套问题。如果仅仅实现动态表单,这种的倒也没问题。但是我觉得把组件在平台上面封装一下,也不够优雅,后面平台想要支持业务组件、开源组件那不得挨个封装,频繁修改平台代码啊……
面面俱到型
第二次封装,开始考虑到嵌套问题,然后json结构类似下面的格式:
{
"id": "",
"components": "g-button-group",
"classify": "basic",
"tag": "el-button-group",
"title": "按钮组",
"props": {
"class": {
"key": "class",
"label": "class类名",
"value": "",
"el": "input",
"type": "string"
}
},
"children": [
{
"tag": "el-button",
"props": {
"type": {
"key": "type",
"label": "按钮类型",
"value": "undefined",
"el": "select",
"type": "string",
"options": [
{
"value": "",
"label": "默认"
},
{
"value": "primary",
"label": "重要"
},
{
"value": "success",
"label": "成功"
},
{
"value": "warning",
"label": "警告"
},
{
"value": "danger",
"label": "危险"
},
{
"value": "info",
"label": "提示"
},
{
"value": "text",
"label": "文本"
}
]
},
"plain": {
"key": "plain",
"label": "是否朴素",
"value": "undefined",
"el": "switch",
"type": "boolean"
},
"round": {
"key": "round",
"label": "是否圆角",
"value": "undefined",
"el": "switch",
"type": "boolean"
}
},
"children": [
{
"tag": "",
"props": {},
"text": "默认按钮"
}
]
},
{
"tag": "el-button",
"props": {
"type": {
"key": "type",
"label": "按钮类型",
"value": "undefined",
"el": "select",
"type": "string",
"options": [
{
"value": "",
"label": "默认"
},
{
"value": "primary",
"label": "重要"
},
{
"value": "success",
"label": "成功"
},
{
"value": "warning",
"label": "警告"
},
{
"value": "danger",
"label": "危险"
},
{
"value": "info",
"label": "提示"
},
{
"value": "text",
"label": "文本"
}
]
},
"plain": {
"key": "plain",
"label": "是否朴素",
"value": "undefined",
"el": "switch",
"type": "boolean"
},
"round": {
"key": "round",
"label": "是否圆角",
"value": "undefined",
"el": "switch",
"type": "boolean"
}
},
"children": [
{
"tag": "",
"props": {},
"text": "默认按钮"
}
]
}
]
}
这次是考虑了嵌套问题,但是后面发现,用这种嵌套格式也是有很大的问题,因为当时考虑所有的嵌套都是用children
这个来去定义的,那就没办法区分以下几种嵌套了:
- 插槽和作用域插槽嵌套;
- 组件默认的有嵌套,例如页签和表格
所以这次定义的数据格式又失败了,关键这次定义的数据格式里面还带上了一些描述属性的信息,这些其实对于组件的渲染是没必要的,数据也有冗余,后面组件属性变化,之前保持的数据是没办法继承。
精简全能格式
结合之前的两次失败经验,然后根据 Vue 深入数据对象 中的定义查看了一下,这次我又重新的定义了一下,看看这次的格式:
{
"id": "000",
"title": "html",
"tag": "div",
"style": {
"color": "red",
"border": "1px dashed red",
"padding": "10px"
},
"domProps": {
"innerHTML": "任意html文本"
}
},
{
"id": "1000",
"title": "iframe",
"tag": "iframe",
"attrs": {
"src": "https://wangdaodao.com",
"width": "100%",
"height": "200px"
}
},
{
"id": "100",
"title": "img",
"tag": "img",
"attrs": {
"src": "https://www.runoob.com/wp-content/uploads/2017/01/vue.png",
"width": "200px",
"height": "200px"
}
},
{
"id": "001",
"title": "文字链接",
"tag": "el-link",
"props": {
"href": "https://github.com/",
"target": "_blank",
"type": "primary"
},
"slots": [
{
"name": "default",
"tag": "template",
"children": [
{
"title": "html",
"tag": "span",
"domProps": {
"innerHTML": "任意html文本"
}
}
]
}
]
},
{
"id": "033",
"key": "input-33",
"title": "复合输入框",
"tag": "el-input",
"ref": "a-input",
"props": {
"value": ""
},
"slots": [
{
"name": "prepend",
"tag": "template",
"children": [
{
"tag": "el-select",
"title": "选择器",
"props": {
"value": "",
"size": "medium",
"clearable": true
},
"style": {
"width": "100px"
},
"subTag": [
{
"tag": "el-option",
"props": {
"label": "选择器a",
"value": "a"
}
},
{
"tag": "el-option",
"props": {
"label": "选择器b",
"value": "b"
}
},
{
"tag": "el-option",
"props": {
"label": "选择器c",
"value": "c"
}
}
]
}
]
},
{
"name": "append",
"tag": "template",
"children": [
{
"id": "4",
"title": "按钮",
"tag": "el-button",
"props": {
"icon": "el-icon-search"
},
"slots": [
{
"name": "default",
"tag": "template",
"children": [
{
"title": "html",
"tag": "span",
"domProps": {
"innerHTML": "搜索"
}
}
]
}
]
}
]
}
],
"events": [
{
"name": "blur",
"function": "console.log('失去焦点时触发')"
},
{
"name": "focus",
"function": "console.log('获得焦点时触发')"
},
{
"name": "change",
"function": "console.log('change触发')"
}
]
}
这次定义的数据格式,基本上可以满足各种嵌套和组件的描述。各种的嵌套、常规的具名匿名插槽、表格的作用域插槽、标准的html组件、组件的自定义方法都是可以通过这套json去描述的。
这次定义的格式,其实还考虑了后面组件属性的升级维护,这个我会在后面把整体的架构图画好了,再详细的说一下,为啥要这么设计。这次先把数据格式定义放这里,抛砖引玉的把现在我想到的数据格式的定义整理出来,如果有更好的描述,可以留言讨论哈,我觉得我这里应该还有一些没有考虑到的地方。
最后放下篇文章用到的知识点,可以提前看一看:
动态组件:https://cn.vuejs.org/v2/guide/components-dynamic-async.html
渲染函数:https://cn.vuejs.org/v2/guide/render-function.html