低代码设计器如果只解决"拖拽组件",很快就会遇到一系列工程问题:页面结构无法稳定表达,组件属性和运行语义混在一起,预览环境不可控,版本差异难以追踪,出码只能依赖模板拼接,插件扩展也容易变成各自维护状态的孤岛。
一个可持续演进的低代码平台,首先需要解决的不是"左边放什么面板,右边放什么属性",而是要先回答几个更底层的问题:
txt
应用应该用什么协议描述?
设计器内核应该管理什么?
插件如何扩展能力而不破坏状态一致性?
运行时如何解释 Schema 中的状态、事件和表达式?
物料、Setter、数据源、资源和出码如何形成生态?
这篇文章从整体架构出发,梳理一套应用级低代码设计器的技术架构、业务架构和设计引擎分层。它不是某一个功能点的实现教程,而是一篇面向设计原理和架构边界的综述。
术语速览
下面这些词会在全文反复出现,先给一个简要说明,方便对照阅读:
txt
AppSchema 描述整个应用的协议数据,是设计器真正编辑的对象
Designer Core 设计器内核,统一管理状态、命令、历史、校验
Workbench 工作台,插件的容器和交互入口(页面管理、属性面板等都是 Workbench 里的插件)
Renderer 把 SchemaNode 渲染成真实组件树的模块
Runtime 负责解释运行语义(状态、表达式、事件、生命周期)的模块
Simulator 画布的隔离运行环境,通常用 iframe 实现
Setter 属性编辑器,负责把 Schema 字段暴露成可操作的 UI
Codegen 出码模块,把 AppSchema 转换成可运行的工程代码
一句话定义低代码设计器
这套低代码设计器可以被定义为:
txt
以 AppSchema 为核心协议,
以 Designer Core 为状态和命令中枢,
以插件化 Workbench 为交互入口,
以 Renderer / Runtime / Simulator 为执行层,
以物料、Setter 和设计器插件为扩展生态,
最终服务于预览、版本管理、源码生成和 AI 辅助开发的应用设计平台。
这里有几个关键词。
第一,核心不是 UI,而是 AppSchema。画布、属性面板、页面树、路由面板、资源面板、主题面板和版本面板,都只是编辑 AppSchema 的不同入口。UI 可以换,插件可以增删,运行模式可以扩展,但协议必须稳定。
第二,设计器需要一个内核。设计器不是几个 Vue 组件拼起来的后台页面,它需要统一管理当前文档、选区、拖拽状态、命令、历史、校验、工作台布局和持久化状态。
第三,插件不是业务真相。插件负责展示信息、收集输入、注册能力入口,然后通过统一命令修改应用协议。插件不应该绕过内核直接改 Schema。
第四,运行时必须独立。画布不是静态预览图,而是一个受控运行环境。表达式、状态、方法、生命周期、资源解析、模块加载和事件执行,都需要运行时负责解释。
第五,生态比单点功能更重要。一个低代码设计器真正有生命力,不是内置了多少组件,而是能否持续接入物料、Setter、设计器插件、运行时插件、数据源协议和出码能力。
总体分层架构
从架构视角看,一个应用级低代码设计器可以拆成六层: 
txt
Presentation Layer
官网、工作台 Shell、插件面板、画布、Inspector、Toolbar
Designer Engine Layer
Designer Core、Command、Selection、History、Validation、Workspace
Application Protocol Layer
AppSchema、DocumentSchema、SchemaNode、Route、Theme、AssetRef、DataSource
Runtime Engine Layer
Renderer、RuntimeContext、Expression、Simulator、Asset Resolver、Module Loader
Extension Ecosystem Layer
Material、Setter、Designer Plugin、Runtime Plugin、Codegen Plugin
Persistence & Delivery Layer
Draft、Version、Migration、Diff、Preview、Codegen、Publish
这几层的关系不是简单的上下调用,而是围绕同一份应用协议协作。
用户在工作台中操作插件和画布,插件把意图交给 Designer Core,Core 通过命令修改 AppSchema,校验系统重新计算诊断信息,运行时和模拟器重新消费 Schema,最终形成预览、保存、发布或出码产物。
核心链路可以概括为:
txt
用户操作
-> Workbench / Plugin / Canvas / Inspector
-> Designer Command
-> History Transaction
-> Mutate AppSchema
-> Validation / Diagnostics
-> Runtime / Simulator
-> Preview / Persist / Codegen
这个链路里最重要的原则是:Schema mutation 必须收敛到 Designer Core。
设计器引擎内核
设计器引擎内核是整个低代码平台的"大脑"。它不负责具体 UI 长什么样,而是负责保证设计行为的一致性和可追踪性。
一个典型的 Designer Core 至少需要管理这些状态:
ts
interface DesignerState {
schema: AppSchema
currentDocument: CurrentDocumentRef
selectedNodeId: string | null
hoveredNodeId?: string | null
activePanel: string
runtimeMode: 'design' | 'preview'
viewport: CanvasViewport
dragState: DesignerDragState
history: DesignerHistoryState
persistence: DesignerPersistenceState
workspace: DesignerWorkspaceState
}
这里要特别注意:DesignerState 不等于 AppSchema。
AppSchema 描述应用是什么。它会被保存、发布、迁移、diff,将来还会进入源码生成。
DesignerState 描述用户当前如何编辑这个应用。它包含选区、面板、拖拽、画布缩放、打开的 tabs、工作台布局等会话状态。这些状态帮助用户编辑,但不是应用本身。
命令系统
低代码设计器中,所有真正改变应用结构的操作都应该进入命令系统。
例如用户在属性面板里修改按钮文案,表面上只是一个输入框变化,但完整链路应该是:
txt
Inspector input
-> plugin event
-> designer.commands.updateNodeProp
-> history transaction
-> update SchemaNode.props
-> validation recompute
-> simulator receives schema
-> renderer rerender
如果属性面板直接修改节点对象,短期看起来简单,长期会产生很多问题:
- History 不知道这次修改。
- Validation 无法准确定位变化。
- 版本 diff 和保存状态会失真。
- iframe simulator 和主设计器状态可能不一致。
- 未来协同编辑无法抽象操作边界。
所以内核设计的第一原则是:
txt
UI 可以很多,Schema mutation 只能有一个入口。
History 与 Transaction
撤销重做不是简单保存数组快照。低代码设计器里的操作有时是单次更新,有时是连续拖拽,有时是多个字段一起变化。
因此更合理的模型是 transaction:
txt
begin transaction
update node props
update selected node
recompute diagnostics
commit transaction
push history entry
这样可以把多个底层变化合并成用户可理解的一次操作,例如"修改按钮属性""移动组件""新增页面""恢复版本"。
Validation 与 Diagnostics
低代码设计器不能等到出码或运行时才发现错误。协议校验应该在编辑阶段持续运行。
典型诊断包括:
- 节点引用了不存在的物料。
- 本地组件出现循环引用。
- 路由 path 冲突。
- 表达式引用了不存在的状态字段。
- 数据源参数缺失。
- 资源引用找不到对应 asset。
- 组件 props 类型不匹配。
Diagnostics 不只是错误提示,它也是 AI 辅助、出码检查和发布前校验的基础上下文。
应用业务架构:AppSchema 如何描述真实应用
低代码设计器真正编辑的不是 DOM,而是一个应用模型。 
这个应用模型可以用 AppSchema 表达:
txt
AppSchema
pages
components
pageGroups
componentGroups
routes
router
layouts
dataSources
assets
modules
theme
globals
materialPackages
schemaVersion
这些字段共同描述一个真实应用需要的业务结构。
页面是业务入口,组件是可复用业务单元,路由决定用户如何进入页面,数据源决定业务数据如何接入,资产和模块决定应用依赖的外部资源,主题和全局状态决定应用级运行环境。
DocumentSchema:统一页面和组件
页面和本地组件可以统一抽象为 DocumentSchema:
ts
type DocumentKind = 'page' | 'component'
interface DocumentSchema {
id: string
kind: DocumentKind
name: string
code: string
props: Record<string, ValueDeclaration>
emits: Record<string, EmitDeclaration>
slots: Record<string, SlotDeclaration>
state: Record<string, ValueDeclaration>
methods: Record<string, MethodDeclaration>
computed?: Record<string, ComputedDeclaration>
watch?: Record<string, WatchDeclaration>
css?: string
componentsTree: SchemaNode[]
}
页面和组件不是两套完全不同的东西。它们都可以有组件树、状态、方法、样式、props、emits 和 slots。
区别在于:
txt
Page
可以被 route 指向
可以有 SEO
可以声明 route params/query
是应用入口之一
Component
可以被页面或其他组件引用
可以作为本地组件物料出现
更强调对外契约
统一成 DocumentSchema 之后,画布、结构树、逻辑面板、属性面板、历史记录、校验和出码能力都可以复用。
SchemaNode:描述组件实例和运行语义
页面或组件内部的结构由 SchemaNode 描述:
ts
interface SchemaNode {
id: string
componentName: string
componentType?: 'builtin' | 'local'
componentId?: string
props: Record<string, NodePropValue>
condition?: JSExpression
show?: JSExpression
loop?: SchemaNodeLoop
model?: SchemaNodeModel
events?: Record<string, EventBinding>
layout?: SchemaNodeLayout
children?: SchemaNode[]
slots?: Record<string, SchemaSlotValue>
}
这里的字段可以分成三类。
第一类是组件实例信息:
txt
componentName
componentType
componentId
props
children
slots
第二类是运行语义:
txt
condition
show
loop
model
events
第三类是布局和设计态结构信息:
txt
layout
slotProps
id
这三类不能混在一起理解。props 是传给组件的属性,show 和 loop 不是组件 props,而是运行时解释的节点语义。layout 也不应该散落成不可分析的 className 字符串,而应该尽量保持结构化,方便校验、响应式布局和出码。
Runtime 架构:设计器为什么需要独立运行时
画布不是普通的静态预览,它需要执行低代码协议里的运行语义。
运行时可以拆成几部分:
txt
SchemaRenderer
负责把 SchemaNode 渲染成真实组件树
RuntimeContext
负责 state、computed、methods、watch、lifecycle
Expression Engine
负责解释 JSExpression、条件渲染、循环、模型绑定
Asset Resolver
负责把 AssetRef 解析成真实 URL 或资源对象
Module Loader
负责把应用声明的外部模块加载到 ctx.modules
Simulator
负责隔离设计器外壳和用户应用运行环境
Renderer 和 Runtime 必须分开。
Renderer 只关心如何渲染节点,Runtime 负责解释运行语义。这样做可以避免画布组件越来越膨胀,也能让同一套 Runtime 服务于设计态画布、预览模式、未来出码验证和测试环境。
iframe simulator 的价值
应用级低代码设计器通常需要 iframe simulator。
原因很直接:用户页面的样式、事件、路由、全局变量和第三方依赖不应该污染设计器外壳。
iframe simulator 可以提供隔离边界:
txt
Parent Designer
Workbench、Inspector、Plugins、Selection
Iframe Runtime
User App、Renderer、RuntimeContext、Assets、Modules
Bridge
schema sync、selection report、hover report、drop event、runtime error
这样设计器可以在外部管理编辑状态,在 iframe 内部运行用户应用。两者通过 bridge 协作,而不是共享一堆隐式全局状态。
引擎生态:物料、Setter、插件和出码扩展
低代码设计器的长期价值取决于生态扩展能力。
一个平台不可能靠内置组件覆盖所有业务场景。它必须允许团队持续扩展物料、属性设置器、设计器插件、运行时能力和交付插件。
物料生态
物料是低代码设计器能识别、展示、拖拽、配置和渲染的组件能力单元。
典型物料来源包括:
txt
内置组件
原生 HTML 物料
本地 Schema 组件
远程物料包
团队业务组件库
一个物料不仅仅是 Vue 组件本身,还需要描述设计器元信息:
ts
interface MaterialDefinition {
name: string
title: string
category: string
runtimeComponent: unknown
propsSchema: PropSchema[]
snippets: MaterialSnippet[]
setters?: SetterDefinition[]
}
设计器靠这些元信息生成物料面板、默认拖拽片段、属性面板和运行时渲染能力。
Setter 生态
Setter 是属性编辑器。
简单属性可以用文本、数字、颜色、开关、选择器来编辑。复杂属性则需要更强的 Setter:
txt
表达式编辑器
事件动作编排器
数据源绑定器
样式编辑器
响应式布局编辑器
资源选择器
路由参数编辑器
Setter 的作用不是把表单做漂亮,而是把复杂协议用可理解的方式暴露给用户。它是"Schema 协议"和"用户操作"之间的翻译层。
设计器插件生态
设计器插件负责扩展工作台能力。
常见插件包括:
txt
页面管理
组件管理
资源管理
主题配置
路由管理
全局状态
数据源管理
物料包管理
结构树
逻辑面板
版本管理
Schema 查看器
历史记录
插件不应该拥有业务真相。它应该通过 Designer Context 读取状态,通过 commands 修改 Schema,通过 diagnostics 展示问题,通过 contribution 注册 UI 入口。
一个抽象的插件接口可以是:
ts
interface DesignerPlugin {
id: string
title: string
area: 'activity' | 'document' | 'bottom' | 'toolbar'
setup(ctx: DesignerContext): void
}
插件注册的是能力入口,而不是新的状态孤岛。
交付扩展
低代码平台最终要交付应用,而不仅是交付一份配置。
交付相关扩展包括:
txt
Preview
Schema Diff
Version Publish
Vue3 Codegen
API Codegen
AI Generate Suggestion
Deployment Adapter
其中出码能力尤其依赖协议稳定。只有 AppSchema、运行语义、资源引用、模块依赖和路由声明足够清晰,Codegen 才能从"字符串模板拼接"变成"协议到工程项目的确定性翻译"。
三类状态边界
低代码设计器里最容易混乱的是状态。
至少要分清三类状态。
第一类是业务应用状态:
txt
AppSchema
pages
components
routes
theme
globals
assets
modules
dataSources
这是应用的一部分,需要保存、发布、迁移、diff,将来还会出码。
第二类是设计器工作区状态:
txt
selectedNodeId
hoveredNodeId
activePanel
openedTabs
leftPanelOpen
bottomPanelOpen
viewport
dragState
这些状态帮助用户编辑,但不是应用本身。
第三类是运行时临时状态:
txt
ctx.state
ctx.computed
runtime errors
loaded modules
resolved asset urls
watch stops
mounted lifecycle state
这些状态用于模拟和预览应用运行,不应该直接写回 Schema。
把这三类状态混在一起,是很多低代码项目后期难以维护的根源。
为什么这套架构适合 AI 和源码生成
AI 生成代码最怕缺少稳定、完整、可验证的上下文。
低代码设计器恰好在做相反的事情:把业务应用需要的结构、语义和约束沉淀为协议。
这些信息包括:
txt
页面结构
组件契约
事件绑定
状态声明
数据源声明
路由声明
主题 token
资源引用
版本差异
校验错误
当这些上下文存在于 AppSchema 和 diagnostics 里,AI 就不再只能"看图猜代码"或"看一句话猜需求"。它可以基于明确上下文做补全、解释、修复、重构和出码建议。
源码生成也是同理。
Codegen 不应该是把一堆字符串拼起来,而应该是:
txt
AppSchema
-> normalized application model
-> route files
-> Vue SFC
-> stores / composables
-> assets / modules
-> buildable project
低代码协议提供确定性,AI 提供生成和推理能力。两者结合,比单独依赖任何一方都更稳。
架构设计原则总结
低代码设计器不是把 UI 拖出来,而是把应用语义结构化。
要让这个系统长期可演进,需要守住几个原则:
txt
协议优先
所有能力最终都要回到 AppSchema。
命令收口
所有 Schema 修改都进入 Designer Core。
状态分层
应用状态、设计器状态和运行时状态必须分开。
运行时隔离
用户应用的执行环境不能污染设计器外壳。
生态插件化
物料、Setter、设计器插件和交付能力都应该可扩展。
交付可验证
预览、校验、版本、diff、出码和发布必须共享同一份协议。
如果说低代码设计器的第一阶段是"让用户能拖拽搭页面",那么应用级低代码设计引擎的目标应该更进一步:
txt
把应用结构、业务语义、运行上下文和工程交付链路统一到一套可演进的协议和引擎体系里。
只有做到这一点,低代码平台才能从演示工具变成真正可维护、可扩展、可出码、可被 AI 理解的工程系统。