简介:这份资源是一套面向无编程基础用户与企业开发者的无代码编辑器项目源码,核心为 mtbird-master 框架,通过拖拽组件、可视化样式配置与模板复用,帮助使用者快速生成小程序、H5 页面和网站,降低快应用开发的技术门槛。压缩包共 677 个文件,约 5.67MB,以 tsx 与 ts 源码为主体,配合 less、css 样式文件、json 配置、md 与 mdx 文档,以及 png、svg 等图标资源,整体结构接近完整的前端工程目录,便于二次开发与模块化阅读。目前已有 157 人学习下载。项目覆盖无代码开发、拖拽布局、响应式样式配置、多端小程序适配、H5 交互页面制作及企业级应用集成等知识点,并可能内置组件库、版本管理与预览发布能力,适合希望研究低代码平台实现原理、搭建可视化搭建系统或快速产出业务页面的开发者参考借鉴。
1. 拖拽就能出页面:这个无代码编辑器到底解决了谁的痛点
上周有个做企业内训的朋友找我,说他们市场部要做一个活动报名 H5,设计稿都出了,但前端排期排到两周后。我问他:你们有没有试过拖拽式的无代码编辑器?他愣了一下,说以为那种东西只能做做简单的表单。实际上,一个成熟的无代码编辑器能做到的事情远超想象——组件拖拽、样式实时配置、响应式适配、一键导出小程序或 H5 代码,整个流程不需要写一行 JSX。这次拆的这个项目,就是一套面向企业场景的无代码页面搭建系统,核心思路是把「页面即数据」这件事做到极致:画布上的每个组件都是一份 JSON schema,编辑器负责把 schema 渲染成真实页面,导出时再根据目标平台生成对应的代码或配置。它适合两类人:一类是业务侧想快速出活动页、落地页的运营和产品,另一类是想研究低代码/无代码引擎架构的开发者。下面我从目录结构开始,把这个项目怎么跑、怎么改,一层层拆开。
2. 项目结构与运行链路:从 mtbird-master 到浏览器里的画布
2.1 目录骨架与模块职责
拿到压缩包解压后,根目录是mtbird-master,这是一个典型的 monorepo 结构。我一般会先看packages/下面有哪些子包,因为无代码编辑器的核心能力通常按「编辑器内核 / 组件库 / 渲染器 / 平台适配层」拆开。这个项目里能看到几个关键目录:
packages/editor:编辑器主界面,包含画布、组件面板、属性配置面板、图层树。packages/components:内置组件库,每个组件一个文件夹,里面是组件实现加一份 schema 描述文件。packages/renderer:渲染引擎,负责把 JSON schema 转成 React 组件树,同时处理样式注入和事件绑定。packages/shared:公共类型定义、工具函数、常量。examples/:示例项目,通常是一个最小化的宿主应用,用来演示如何把编辑器嵌入到自己的系统里。
样式文件在正文里被反复提到——animate.min.css、iconfont.css、App.css、style.module.css、index.css。这说明项目用了 CSS Modules 做样式隔离,同时引入了 animate.css 做动画预设,iconfont 做图标字体。style.module.css出现多次,大概率是不同包各自维护自己的模块化样式,避免全局污染。这种样式方案在无代码编辑器里很常见,因为编辑器本身要渲染用户页面,用户页面的样式不能和编辑器 UI 的样式互相干扰。
2.2 本地启动:三条命令跑起来
项目用 pnpm workspace 管理依赖,这是 monorepo 的常见做法。先确认本地 Node 版本,建议 16 以上。然后按顺序执行:
# 安装全局 pnpm(如果还没装) npm install -g pnpm # 进入项目根目录,安装所有子包依赖 cd mtbird-master pnpm install # 启动编辑器开发服务,通常 editor 包下有 dev 脚本 pnpm --filter @mtbird/editor dev如果--filter后面的包名不确定,可以直接看packages/editor/package.json里的name字段。启动成功后终端会输出本地地址,一般是http://localhost:3000或http://localhost:8000。打开浏览器就能看到编辑器界面:左侧组件面板、中间画布、右侧属性配置区。
注意:如果安装依赖时卡在某个原生模块编译,先检查系统是否装了 Python 和 make 工具链。无代码编辑器本身不依赖原生模块,但 monorepo 里可能有些构建工具需要。
2.3 一个组件从拖拽到渲染的完整链路
理解这条链路,后面改代码才不会迷路。当用户从左侧面板拖一个按钮到画布上时,发生了这几件事:
- 拖拽事件被编辑器内核捕获,生成一个组件实例的初始 schema,包含
type、props、style、children等字段。 - 编辑器把这份 schema 插入到当前页面的 schema 树里,同时更新图层树和属性面板。
- 渲染器监听到 schema 变化,递归遍历 schema 树,根据
type找到对应的 React 组件,把props和style传进去。 - 组件渲染到画布上,样式通过 CSS Modules 或内联样式注入,动画类名从 animate.css 里取。
整个过程是单向数据流:schema 变 → 渲染变。编辑器本身不直接操作 DOM,所有操作都落在 schema 上。这也是为什么导出时能生成不同平台的代码——因为 schema 是平台无关的中间层。
3. 核心模块拆解:schema 设计、组件注册与样式配置
3.1 schema 结构:页面即 JSON
无代码编辑器的灵魂是 schema 设计。这个项目里,一个页面就是一棵 schema 树,每个节点长这样:
{ "id": "btn_001", "type": "Button", "props": { "text": "立即报名", "size": "large" }, "style": { "backgroundColor": "#1677ff", "borderRadius": "8px", "marginTop": "16px" }, "children": [] }id是唯一标识,用于图层树选中和属性面板绑定。type对应组件库里的组件名。props是组件的行为属性,style是视觉样式。children支持嵌套,容器类组件靠它实现布局。
这种设计的妙处在于:导出到小程序时,把type映射成小程序的组件标签,style转成对应的样式对象;导出到 H5 时,直接渲染成 React 组件。同一份 schema,多个目标平台。
3.2 组件注册机制:新增一个组件要改哪些文件
组件库在packages/components下,每个组件一个目录。以 Button 为例,目录结构通常是:
Button/ index.tsx # 组件实现 schema.ts # 组件的默认 schema 和属性配置 style.module.css # 组件样式index.tsx是普通的 React 组件,接收props和style。schema.ts导出两份东西:一份是拖拽时的默认 schema,一份是属性面板的配置项。属性面板根据配置项自动生成表单控件——比如text字段是字符串,就渲染输入框;size是枚举,就渲染下拉选择。
新增组件的步骤:
// packages/components/Button/schema.ts export const defaultSchema = { type: 'Button', props: { text: '按钮', size: 'default' }, style: { backgroundColor: '#1677ff', color: '#fff', padding: '8px 16px' } }; export const propConfig = [ { key: 'text', label: '按钮文字', type: 'string' // 属性面板渲染成输入框 }, { key: 'size', label: '尺寸', type: 'enum', options: ['small', 'default', 'large'] } ];然后在组件库的入口文件里注册这个组件,编辑器左侧面板就会多出一个可拖拽的按钮。属性面板也会根据propConfig自动生成对应的配置项。这套机制的好处是:新增组件不需要改编辑器内核,只需要在组件库里加文件、注册。
3.3 样式配置面板:从 CSS 属性到可视化控件
右侧属性面板的样式配置区,本质上是把 CSS 属性映射成可视化控件。这个项目里,样式配置项按类别分组:布局(display、flex、margin、padding)、文字(fontSize、color、fontWeight)、背景(backgroundColor、backgroundImage)、边框(border、borderRadius)、动画(animationName、animationDuration)。
动画配置直接对接 animate.css。用户选一个动画名,比如fadeIn,编辑器把animate__fadeIn类名写进组件的className,同时引入animate.min.css。预览时动画立即生效,导出时类名和样式文件一起打包。
/* 编辑器内部注入动画类名的逻辑,简化示意 */ .component-wrapper { /* 基础样式来自 schema.style */ } .component-wrapper.animate__fadeIn { animation-name: fadeIn; animation-duration: 1s; }样式配置面板的难点在于响应式。同一个组件在桌面端和移动端可能需要不同的fontSize或padding。这个项目里,schema 的style字段支持媒体查询断点,属性面板上有一个设备切换器,切换后编辑的是对应断点下的样式。导出时,这些断点样式会转成 CSS 媒体查询或小程序的响应式单位。
4. 避坑与排查:跑起来之后最容易翻车的几个地方
4.1 画布组件不渲染,控制台报 Unknown component
现象:拖拽组件到画布,画布空白,控制台提示找不到组件类型。
原因:组件没有在组件库入口注册,或者注册的type和 schema 里的type不一致。大小写敏感。
解决:检查packages/components/index.ts里的注册列表,确认组件已导出。再检查 schema 里的type字段是否和注册名完全一致。
4.2 样式错乱,编辑器 UI 被用户页面样式覆盖
现象:拖入某个组件后,编辑器左侧面板的按钮样式变了。
原因:用户页面的样式没有做隔离,全局样式泄漏到了编辑器 UI。
解决:确认所有组件样式都走 CSS Modules,style.module.css里的类名经过哈希处理。如果组件里用了全局样式,改成模块化写法。编辑器画布区域最好再加一层 Shadow DOM 或 iframe 隔离,这个项目目前用的是 CSS Modules 加命名空间前缀。
4.3 导出小程序后样式丢失
现象:H5 预览正常,导出到小程序后部分样式不生效。
原因:小程序不支持某些 CSS 属性,比如position: fixed在部分场景下受限,或者backgroundImage的路径没有转成小程序可识别的格式。
解决:在导出适配层里加一个样式转换表,把不支持的属性降级或替换。图片资源路径要转成小程序能访问的绝对路径或 CDN 地址。
4.4 拖拽排序时 schema 树和图层树不同步
现象:画布上组件顺序变了,但图层树顺序没变,选中组件时高亮错位。
原因:拖拽操作只更新了 schema 树,没有触发图层树的重新渲染。或者图层树用了索引做 key,排序后索引变化导致 React 复用错误。
解决:图层树的 key 用组件id,不要用数组索引。拖拽结束后统一走一次 schema 更新流程,图层树从 schema 派生,不单独维护状态。
4.5 动画预览正常,导出后动画不播放
现象:编辑器里动画正常,导出的 H5 页面动画不动。
原因:animate.min.css没有被打包进导出产物,或者类名被 CSS Modules 哈希了,和 animate.css 里的类名对不上。
解决:动画类名不要走 CSS Modules,用全局类名。导出时把animate.min.css作为静态资源一起输出,并在页面入口引入。
5. 二次开发与改造:从改一个组件到接入企业权限体系
5.1 改造组件库:加一个带数据绑定的表格组件
企业场景里,表格是最常用的组件之一。内置组件库如果没有表格,可以自己加一个。核心是让表格支持数据源配置——用户可以在属性面板里填一个 API 地址,表格自动拉数据渲染。
// packages/components/Table/index.tsx import React, { useEffect, useState } from 'react'; import styles from './style.module.css'; interface TableProps { api?: string; // 数据接口地址 columns?: string[]; // 列名,逗号分隔 style?: React.CSSProperties; } const Table: React.FC<TableProps> = ({ api, columns = [], style }) => { const [data, setData] = useState<any[]>([]); useEffect(() => { if (!api) return; // 常见做法是加一层请求封装,这里简化成 fetch fetch(api) .then(res => res.json()) .then(setData) .catch(err => console.error('表格数据加载失败', err)); }, [api]); return ( <table className={styles.table} style={style}> <thead> <tr> {columns.map(col => <th key={col}>{col}</th>)} </tr> </thead> <tbody> {data.map((row, i) => ( <tr key={i}> {columns.map(col => <td key={col}>{row[col]}</td>)} </tr> ))} </tbody> </table> ); }; export default Table;然后在schema.ts里定义api和columns的配置项,属性面板就会自动生成两个输入框。用户填完接口地址和列名,画布上就能看到真实数据。这里要注意跨域问题——企业内网接口通常需要编辑器宿主应用做代理,或者让用户填同源接口。
5.2 接入企业权限:页面级和组件级控制
企业应用绕不开权限。无代码编辑器生成的页面,往往需要根据登录用户的角色决定哪些组件可见、哪些可编辑。这个项目里,schema 的props可以扩展一个permission字段:
{ "type": "Button", "props": { "text": "删除", "permission": "admin" } }渲染器在渲染前检查当前用户角色,不匹配就不渲染这个组件。编辑器侧则可以在属性面板里加一个角色选择器,让页面搭建者指定组件的可见角色。导出时,权限判断逻辑会一起打包进去,或者由宿主应用在运行时注入。
5.3 版本控制与回滚:schema 快照的存储策略
无代码编辑器的一个隐藏需求是版本管理。运营改乱了页面,想回到昨天的版本。常见做法是每次保存时把整棵 schema 树存一份快照,带上时间戳和操作人。回滚时把快照重新加载到编辑器。
存储上,schema 树不大,直接存 JSON 字段就行。如果页面很多,可以按页面 ID 分表,每个页面存多条版本记录。回滚操作本质上是把某个历史版本的 schema 设为当前版本,编辑器重新渲染。
5.4 导出适配层:一份 schema 如何生成多端代码
导出是无代码编辑器价值闭环的最后一环。这个项目里,导出逻辑在packages/renderer的export目录下,按目标平台分文件:exportH5.ts、exportMiniProgram.ts、exportSite.ts。
以 H5 导出为例,核心是把 schema 树转成 React 组件代码字符串:
// 简化示意:把 schema 节点转成 JSX 字符串 function schemaToJSX(node: SchemaNode): string { const { type, props, style, children } = node; const propsStr = Object.entries(props) .map(([k, v]) => `${k}=${JSON.stringify(v)}`) .join(' '); const styleStr = `style={${JSON.stringify(style)}}`; const childrenStr = children?.map(schemaToJSX).join('\n') || ''; return `<${type} ${propsStr} ${styleStr}>${childrenStr}</${type}>`; }实际项目里不会这么简单,还要处理事件绑定、数据源、条件渲染。但核心思路一致:schema 是中间表示,导出器是后端。改导出规则只需要改对应的导出器,不影响编辑器内核。
5.5 一个我踩过的坑:schema 字段命名不要用保留字
最后说一个血泪经验。早期我在 schema 的props里用了一个字段叫children,结果和 React 的children冲突,渲染时组件把 schema 的子节点当成了 React 子元素,导致嵌套组件全部渲染错位。排查了半天才定位到。从那以后我每次设计 schema 字段都强制走一遍命名检查:避开 React 保留字、避开目标小程序平台的保留字、避开 CSS 属性名。这个习惯帮我省了很多后悔药。希望帮到你。
本文还有配套的精品资源,点击获取