# 低代码平台 **Repository Path**: listen-carefully/low-code-platform ## Basic Information - **Project Name**: 低代码平台 - **Description**: 垂直开发低代码平台(系统定制) - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-07-09 - **Last Updated**: 2025-07-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Yong 低代码平台 一个基于 React 和 TypeScript 的本地化低代码平台 ## 核心理念 1. **可视化开发范式**:所见即所得的设计体验 2. **标准化输出**:生成无痕、可独立运行的React项目 3. **数据驱动**:组件-模型-服务三层联动 4. **模块化架构**:插件式设计保证扩展性 ## 基本实现 纵观大部分低码平台,主要都是由以下四部分组成(画的有点简陋): ![输入图片说明](public/1.png) 接下来我们会对每个部分都挑两三个重点来讲解一下🚗。 ### 1、协议 在讲解每个模块之前,我们先来说一个东西,就是协议(其实就是规范,更朴素点叫做格式),它主要包括物料协议、平台搭建协议和其他协议等等。 低代码工具开发要先约定协议,因为这个东西贯穿低代码的整条链路: - 当我们想扩展物料时,需要实现相应的协议 - 当我们把左侧一个物料拖到中间画布区时,需要通过协议来通信和解析 - 当我们选中画布区域的组件时,要想通过右边设置面板进行属性设置时,也需要通过协议来通信和解析 - 当我们准备预览和发布代码时,也需要通过协议来生成代码 协议是低码平台的基石,它的主要目的就是约束和扩展,约定的好,事半功倍;约定不好,版版重构。约定优于配置说的就是这个道理。如果维护到后期发现协议很难拓展了,那基本只能重来了,只不过你多了些经验。协议本质上就是一堆 interface(就是固定格式)。 物料协议 ``` interface IGraduationComponent { // 基础标识 componentName: string; // 组件唯一标识(英文) title: string; // 显示名称(中文) category: 'basic' | 'container' | 'data'; // 分类:基础/容器/数据组件 // 布局配置 initialLayout: { w: number; // 默认宽度(栅格单位) h: number; // 默认高度(行高倍数) minW?: number; // 最小宽度 minH?: number; // 最小高度 }; // 属性配置规范 propSchema: Array<{ name: string; // 属性字段名 propType: 'string' | 'number' | 'boolean' | 'options' | 'modelField'; label: string; // 属性显示名 defaultValue: any; // 默认值 options?: string[]; // 当propType=options时的选项 bindable?: boolean; // 是否可绑定数据模型 }>; // 系统设计特化字段 isCRUD: boolean; // 是否CRUD组件 requiredModel?: string; // 需要的数据模型类型 } ``` 画布区域协议: ``` interface IGraduationCanvas { // 页面结构 pages: Array<{ id: string; // 页面ID name: string; // 页面名称 layoutType: 'grid' | 'free'; // 布局类型 children: IComponentInstance[]; // 组件树 }>; // 数据模型(系统设计核心) models: Array<{ name: string; // 模型名称 fields: Array<{ name: string; // 字段名 type: 'string' | 'number' | 'boolean' | 'date'; label: string; // 显示名称 }>; }>; } // 组件实例规范 interface IComponentInstance { id: string; // 实例ID type: string; // 对应物料componentName layout: { // 当前布局 x: number; y: number; w: number; h: number; }; props: Record; // 属性值 dataBindings?: Record< // 数据绑定(系统设计特化) string, // 属性名 { model: string, field: string } // 绑定目标 >; } ``` 属性协议 ``` // 属性设置器规范(系统设计简化版) type PropertySetter = | 'TextInput' // 文本输入 | 'NumberInput' // 数字输入 | 'BooleanSwitch' // 布尔开关 | 'ModelFieldSelect' // 模型字段选择器(系统设计核心) | 'OptionsSelect'; // 选项选择 // 属性面板规范 interface IPropertyConfig { componentId: string; // 当前编辑组件ID setters: Array<{ propName: string; // 属性名 setterType: PropertySetter; // 使用的设置器 label: string; // 显示标签 options?: string[]; // 选项(当setterType=OptionsSelect) bindableModels?: string[]; // 可绑定模型(当setterType=ModelFieldSelect) }>; } ``` 生成协议: ``` interface IGenerationConfig { // 技术栈规范(系统设计固定) techStack: { frontend: 'react', backend: 'node', database: 'sqlite' }; // 痕迹消除规则(系统设计核心需求) sanitizeRules: { removeTempProps: boolean; // 移除临时属性 renameVariables: boolean; // 变量名随机化 formatCode: boolean; // 代码格式化 addCredits: boolean; // 添加学生信息注释 }; // 系统设计特化配置 projectInfo: { author: string; // 学生姓名 studentId: string; // 学号 className: string; // 班级 }; } ``` 输出协议: ``` interface IOutputProject { // 文件结构规范 fileStructure: { // 前端 'frontend/src/pages': string[]; 'frontend/src/components': string[]; 'frontend/src/services': string[]; // 后端 'backend/models': string[]; 'backend/routes': string[]; // 数据库 'database/migrations': string[]; // 启动脚本 'start.bat'?: string; // Windows启动脚本 'start.sh'?: string; // Mac/Linux启动脚本 }; // 系统设计特化字段 readmeContent: string; // 自述文件内容 } ``` ### 2、物料区 最左侧的物料区吧,这是低代码的起点,协议也是从这边开始的,先约定个最简单的物料协议吧: 物料区其实没啥功能,我们会有一个 componentList,里面是各种组件的基本信息,直接循环渲染即可。同时还会顺便生成一个 componentMap,主要是方便后续我们通过组件名来快速获取是组件的元信息,比如下面这样: 物料区本身并不复杂,这里就说三个注意点: - 组件的分类: - 容器组件(这类组件主要用来协助布局和嵌套) - 基本组件(常见的有图片、文本、输入框、视频等) - 集成度稍高一点的组件(表格、表单、图表、自定义组件、业务组件) - **如何扩展自定义组件?** - 这是最为重要的功能之一,我们要想让一个第三方组件在当前平台可用,直接拿来肯定是不行的,必须要进行适配: - 如果这个第三方组件是已有的,我们需要简单适配一下(适配器模式),也就是这个第三方组件需要实现 IComponent 这个接口 - 如果这个第三方组件是待开发的,那低码平台一般会提供脚手架让使用方去基于一个固定的模板去开发,这样协议自然也就对上了 - 通常情况下,低码平台自身会有个物料管理平台对组件进行统一的管理和操作,简单点做的话我们可以直接把开发好的组件发到 npm 上,把 npm 当做物料平台用 - 但我们更期望的是物料统一,既然用了别人的平台,那就去用别人的组件,尽量去复用平台自身已有的组件库。我们不生产组件(或者少生产),只是消费组件😁。 ### 3、画布区 这里我们先说说画布区的几种实现方式吧: - 自由画布:拖到哪里元素就放到哪里,配合容器组件可以协助布局以实现自适应 - 流式布局:组件从左到右从上往下自然排列,比如 H5 就是单纯的从上往下平铺开来 - 自动布局:也是拖哪放哪,但是原来的地方如果已经有组件则会被挤开,被挤开的元素还会有个递归挤开的过程 - 栅格画布:类似前端组件库中的栅格布局,一行 24 列,一行 12 列这样划分填充,看起来比较规范也比较整齐 - 网格画布:类似棋盘一样的网格,每个网格都是 8px(看 UI 规范),拖拽组件的时候会自动吸附到这些网格线上,这样可以让整体看起来更加错落有致(如果你有一些设计规范,可能会需要用到这种画布) - 混合布局:效率和通用性的权衡 ### 4、属性设置区 接下来我们简单讲下右侧的属性设置区,这个区域通常会支持三种基本的设置:props && 样式 && 事件。一般来说我们的操作是这样的: - 点选画布区的某个组件 - 触发设置某个全局变量为当前组件 - 右侧属性面板就会根据当前组件的 componentName 从 componentMap 中找到组件对应的 setters(可配置项),这个 setters 其实就是一开始在物料协议里面约定的 props,但是和 props 可能有点小区别,需要自己手动写个函数转一下;或者可以直接在物料协议里面多添加一个 setters 字段来专门描述有哪些属性可以支持配置。两种方式都是可以的 - 然后也是单纯的循环渲染 setters - 再把当前组件的 state(初始值)赋值给 setters 属性设置区的几个注意点: - 首先如果我们修改了属性设置区的表单项,我们实际上是去修改全局的 componentTree,然后画布区自然就会根据这个新的 componentTree 自动渲染,有点单向数据流的意思(就是修改数据的入口只有一个),也方便排查问题。把数据放到全局上,很多通信的过程就可以省掉了 - 一个常常提到的问题就是如何实现联动,比如字段 2 的显隐依赖于字段 1 的值,类似这种功能通常有两种实现方式: - 一种类似发布订阅,我们可以在字段 2 中监听(on)来自字段 1 的 emit 事件,只是多了你就很难知道各自有哪些依赖关系了 - 另一种方式就是利用全局数据了,比如我们把字段 1 和字段 2 都放在全局数据中,然后在字段 2 中新增一个 visible 的属性设置器,其值是一个模板表达式,形如:`{{ globalData.field1 && ... && globalData.fieldN }}`,因为数据是全局的所以很方便能够直接获取到,在实际渲染的过程中就会动态执行上面那个表达式来确定组件渲不渲染。此外,因为数据是全局的,跨组件或者跨页面共享数据也会变得轻而易举 - 再一个常常提到的问题就是如何处理点击事件?如果做的开放点、简单点,我们可以直接让用户自己写函数,然后运行的时候用 `eval` 或者 `new Function` 执行一下就行。但是这样会有个问题,就是安全性、稳定性和效率不够,所以我们需要进行一些限制,这个通常有两种方法: - 一种是暴露固定方法,只接收参数,比如我这个点击的结果就是跳转到某个页面,也就是执行 `window.open` 这个方法,那我们就不允许用户直接书写这个代码,而是先内置一个全局封装好的 `jumpToPage(url)` 方法,然后在属性设置的时候只允许输入 url 并进行简单校验 - 但是固定方法是很难满足我们的一些需求的,最终还是得支持让用户可以自己写脚本,于是乎我们就得让这个脚本具有良好的隔离性,也就是沙箱或者对代码进行校验等,这里就简单说一下沙箱的方式: - with + new Function(用 with 改变作用域实现隔离、用 try catch 捕获错误保障稳定性) - iframe 沙箱:在一个空的 iframe 里面执行这些未知的代码可以最大程度的实现隔离,其余方式都可以通过原型链进行逃逸(其实 iframe 通过 parent 也能逃逸) - 等一个新的 API:ShadowRealm ### 5、顶部操作区 上面那几个组成部分其实已经构成了低代码中最核心的几个部分,我们可以称之为 Core(内核)。对于顶部操作区,通常可以有前进、后退、清空等操作,但是这些操作并不算是必须的,它们通常以插件的形式存在,也方便大家一起维护和扩展,这就是微内核架构: - 物料区也可以有插件:主要表现就是用脚手架来扩展物料 - 画布区也可以有插件:比如选中组件的时候可以扩展复制、删除、辅助线、上移下移等功能 - 设置区也可以有插件:比如颜色选择器,可以追加自定义表单项 - 同技术栈:如果用的技术栈和低代码平台是一样的话,我们可以使用运行时渲染,开发一个 preview 的页面,页面里面有固定的渲染器(和画布区有点像),也会加载对应的组件库,剩下的就是远程获取页面的 json 数据(也就是上面那一坨代码,当然也可以称之为 schema),传递给渲染器,然后直接展示即可。 ## 模块之间如何进行解耦呢: - 通过全局的发布订阅模式来通信 - 通过全局数据来共享,而不是通过直接相互传递数据 - 其实你把每个模块都写成独立的项目,开发起来自然就会强迫自己解耦了 - 解耦最大的好处就是当你要重构或者替换某一个模块时,可以直接替换单个模块而不影响其他地方的逻辑 - 除了全局变量,我能在组件里面维护自己的状态,我们可以新增一个 state 属性,来维护组件自身的一些状态,并且我们可以通过维护原型链的方式来层层向上获取父元素数据,比如这样:`state.__proto__ = parent.state` - 怎么支持跨平台:低代码平台搭建好后最终得到的是一个 json,而这个 json 本身就是以一种通用的语言来描述页面结构、表现和行为的,它和平台无关。要想跨平台、跨框架,只需要做一个适配层去解析这些 json,导出成各平台、各框架需要的样子即可。举个例子来说,我们在解析的过程中肯定会需要创建元素,而 vue 和 react 都有各自的 createElement 方法,那到时候只需要把创建元素的方法换成各框架各自的方法就行了。思路听起来很简单,但是每一种适配起来都很繁琐 - 辅助线的实现(测距、对齐、吸附):需要遍历所有物体拿到对应的 x、y 值,只不过在遍历较多组件的时候可以有些策略,比如是否在可视区、和目标元素的距离、超过十条或者 5ms 就停止遍历等等 - 版本维护:低码平台通常会有 version 字段来维护每个迭代,并且会有好多种 version,包括但不限于:协议有版本号、依赖有版本号、组件有版本号、每次发布也有版本号 - 怎么调用接口:通常会有一个单独的模块来配置所有接口,然后再在右侧的属性设置面板中就挑选对应的接口即可 - 如何监控和埋点:接个监控和埋点库,并在全局暴露方法,方便用户在右侧属性面板添加自定义事件 - 怎么调试?如果你是前端或许看看控制台报错还能知道是什么问题,但如果你是非研发同学,那就完全没招了。对此,就需要低码平台开发一个调试模块,形如 vue 和 react 的 devtool 和 vConsole 调试面板,当然这对非研发同学还是不够友好,我们最好需要将具体的错误和建议在页面上醒目的展示出来 - D2C && AI+:低码平台需要我们自己搭建页面,D2C 则又省了一步,直接解析设计稿,经过繁琐的转换后转成了我们的 json,然后就直接生成了页面。可行是可行,现在也有很多这样的产品,并且结合 AI 能够为其增色不少。但是吧,我觉得这玩意几乎维护不下去。 ## 小结 如果你能看到这里,那说..明...文章写的还行,哈哈哈嗝🥳。这里我就简单的用几句话对本篇文章进行一个总结: - 低代码最重要的就是:方向 - 低代码的基石就是:协议 - 低代码的最大的优势:可视和高效 - 低代码所有的操作都是在操作一个 json ## 加油 ![输入图片说明](public/2.png)