前言
Corgi-ICode
制作起因:懒!
在维护类似的管理后台时,总会做一些相似的页面,复制出来之后又要修改、删除等等,很是麻烦呢。
后来我写了一个脚本,通过写入一些配置数据,直接通过node
编译成vue
文件,使用起来倒也,但有缺点就是经常会忘记一些属性,导致经常要翻组件文档。因此打算做一个可视化的编辑器来生成模板。
Corgi-ICode
页面设计和常规的低代码平台类似,左侧是一些组件和模板,中间是拖拽面板,右边是配置面板可以配置选中组件的一些属性、数据。
但区别在于,这个项目只会生成对应的Vue SFC
代码,把生成的代码当作页面的基础模板再做后续的业务开发。
涉及到的技术:Vue3
、Vite
、Element-plus
、monaco-editor
、vuedraggable
项目结构
首先,UI层与组件库并不关联,通过调用Core
中的import
功能去加载组件库的数据,加载出的数据都维护在Core
中,UI引用这些数据进行页面渲染(组件列表、模板列表、组件配置表等),凡是会对主数据造成影响的功能都需要到Core
中调用。
Core
中维护了操作主要数据的方法以及公用组件,比如 DraggableArea
功能组件,除了UI中的操作区域需要拖拽外,组件库中的Form
、Grid
、Card
等组件也都需要有内部的拖拽功能。
最后组件库,由于UI与组件库并不关联,所以理论上可以兼容Vue3
的各种组件库,但由于工作量太大,现在只做了Element-plus
。
拖拽功能
这里使用了vue.draggable.next来做拖拽功能。
由于是B端项目,没有复杂的页面排版,不需要定位之类的操作,所以只需要维护一个List
,并编写一个用于渲染组件的高阶组件即可。
<!-- 组件菜单 -->
<el-collapse-item
v-for="item in menu"
:key="item.title"
:title="item.title"
:name="item.title"
>
<draggable
:list="item.children"
item-key="type"
:sort="false"
:clone="cloneNewWidget"
:group="{ name: 'dragGroup', pull: 'clone', put: false }"
class="flex justify-between flex-wrap"
>
<template #item="{ element }">
<div>
{{ element.title }}
</div>
</template>
</draggable>
</el-collapse-item>
在菜单中调用clone
钩子,为拖拽中的组件配置一个唯一的Key,方便之后的组件操作。
<!-- 操作面板 -->
<draggable
:list="list"
handle=".moveArea"
ghost-class="ghostClass"
item-key="key"
group="dragGroup"
@add="addEnd"
>
<template v-if="!list.length" #footer>
<div class="text-center opacity-60 h-40 leading-40">
{{ empty || '拖拽区域' }}
</div>
</template>
<template #item="{ element }">
<HandleComp :item="element">
<slot :item="element" />b
</HandleComp>
</template>
</draggable>
在拖拽结束后触发事件add
,此时就可以拖拽更新后的组件列表全部打上父级的Key,建立父子关系也是方便后续的维护工作。同时也可以进行历史记录的操作,在拖拽完成后在历史记录的List
中插入一条新的数据。
结构设计
类似于常规的低代码平台,拖拽、配置完成之后都需要导出一串JSON
数据,用于渲染和编译。
// 单个
export interface IWidgetItem {
title: string // 标题
type: string // 组件类型
icon?: FunctionalComponent<SVGAttributes, {}>
component?: string // 调用组件名称
key: string // 自动生成的key
form: IWidgetItemForm // 属性配置
noForm?: boolean // 是否为form下组件
children?: IWidgetItem[]
parent?: string
validateFn?: Function // 表单组件校验方法
updateDataFn?: () => void // 用来更新组件内的数据
}
export type IWidgetItemForm = Record<
string,
{
label: string // 名称
type: keyof typeof IWidgetOptions // 输入组件
value: any
isShow?: (options: any) => boolean
changeCb?: (options: any) => void
}
>
组件方面定义了其自身的type
、key
、children
等字段,而对于属性的配置方面,在UI
层中创建了如Input
、Select
、Code
等用于不同场景的属性配置的输入组件,在一个组件被拖入拖拽面板之后,右侧的配置栏会取得当前活跃组件的form
属性配置表,根据每一条属性的type
匹配到对应的输入组件,然后再进行每条属性的组件渲染。
组件库加载到的组件配置表
input的配置面板
右侧配置面板中每条属性的改动都需要记录到属性的value
字段,同时会体现在拖拽面板中。
将每个必要属性配置完成后就会得到一条IWidgetItem[]
形式的数据。
模板编译
对IWidgetItem[]
进行遍历,通过每一个组件的组件名找到预先在组件库中编写好的编译函数,传入配置的属性值、表单属性等信息,生成模板数据
/**
* 编译函数
* options:配置的属性
* formDataName:父级form 的key
*/
export type renderWidgetCode = (options: Record<string, any>, formDataName?: string) => {
template: string | ((arg: string) => string) // 代码模板
formData?: Record<string, any> // 该组件内用到的formData字段
privateVar?: Record<string, any> // 组件内的私有变量
formDataName?: string // 父级form 的key
importList?: Record<string, any> // 组件内的引入列表
componentName?: string // 子组件名称
componentTemplate?: string // 子组件的模板
endScript?: string // 其他代码 hooks等
}
const run: renderWidgetCode = (options, _formDataName) => {
const attrs = [
'type',
'placeholder',
'clearable',
'maxlength',
'minlength',
'showWordLimit',
]
const attrsStr = attrs
.map(attr => formatArrt(attr, options[attr]))
.filter(Boolean)
.join(' ')
return {
formData: {
[options._key]: options.value,
},
template: `<el-form-item label="${options.label}" prop="${options._key}">
<el-input ${_formDataName ? `v-model="${_formDataName}.${options._key} "` : ''}${attrsStr} />
</el-form-item>
`,
}
}
之后就是将各个组件所生成的数据结果进行混合,最终都生成String
拼装在一起。
const baseTemplate = `
<template>
${templateStr}
</template>
<script setup>
${importListStr + formDataStr + validateListStr + widgetVariableStr + endScript}
</script>
`
写在最后
本项目的初衷就是做一个简单的可视化的代码模板编辑器,所以最后生成的代码会比较简陋,还需要后续再进行弥补。
如果真要做成低代码的形式还需要后台的支持,编写更多更完整的组件。(但是我真的不喜欢低代码)
因为项目是用Vue3
写的,所以最后生成的代码是Vue3 setup
形式的,而作者本人在公司维护的一直是Vue2 Options
形式的代码,所以这个工具也用不上😂,只能当个练手的玩具。
如果真的有需要的话,我也会尽量维护👌。
参考项目