低代码平台的核心诉求,是让运营或非专业开发人员通过拖拽和配置就能产出一个可上线的移动端页面。要做到这一点,系统必须解决三个问题:页面如何被结构化描述、组件如何被动态渲染、编辑器与预览如何实时联动。Vue 3 提供的 Composition API、h 函数以及响应式系统,恰好为这三个问题提供了非常契合的底层能力。本文将从页面描述协议设计、动态渲染引擎实现、编辑器与预览通信三个层面,完整拆解一套基于 Vue 3 的移动端 H5 低代码方案的实现过程。

一、页面描述协议:用 JSON Schema 定义页面结构
低代码引擎的第一步是定义一套页面描述协议(Page Schema)。协议的本质是把页面的结构、组件类型、属性配置、组件间嵌套关系,全部用一份可序列化的 JSON 表达出来。这样编辑器操作产生的结果就是一份 JSON 数据,渲染引擎只负责消费这份 JSON,两者完全解耦。
一个典型的移动端页面描述如下,包含页面级元信息和一个组件树:
{
"pageConfig": {
"title": "活动落地页",
"viewport": "750",
"backgroundColor": "#f5f5f5"
},
"components": [
{
"id": "banner_001",
"type": "McBanner",
"props": {
"imageUrl": "https://ipipp.com/banner.png",
"height": 200
}
},
{
"id": "goods_002",
"type": "McGoodsList",
"props": {
"columns": 2,
"dataSource": "/api/goods/list"
},
"children": []
}
]
}
协议设计上有几个要点值得注意。第一,每个组件必须有全局唯一的 id,它是选中、拖拽、属性修改时定位组件的唯一依据,建议用短id生成库避免随机字符串过长。第二,type 对应物料注册表中登记的组件名,渲染引擎根据它做映射查找。第三,props 中只存放该物料声明的可配置属性,属性面板可以根据物料的 schema 定义自动生成表单,实现配置即表单。
另外建议把事件、样式、数据源分层存放,例如单独的 events 字段描述点击跳转,style 字段描述内联样式覆盖,避免所有配置混在一个对象里导致协议越来越难维护。协议一旦稳定下来,后续的版本迁移(协议升级)也可以通过编写转换函数实现向后兼容。
二、动态渲染引擎:基于 h 函数递归生成组件树
有了协议,接下来是渲染。Vue 3 中最直接的方式是用 h 函数写一个递归渲染组件,把 JSON 的组件树映射为虚拟 DOM 树。核心思路是:封装一个 SchemaRenderer 组件,接收一段组件描述数组,遍历后通过 h 创建真实注册过的物料组件。
import { h, defineComponent, resolveComponent } from 'vue'
const SchemaRenderer = defineComponent({
name: 'SchemaRenderer',
props: {
schema: { type: Object, required: true }
},
setup(props) {
return () => {
const { type, props: compProps, children } = props.schema
// 从物料注册表中解析组件
const material = window.__MATERIALS__[type]
if (!material) {
console.warn('未注册的物料类型: ' + type)
return h('div', '未知组件 ' + type)
}
return h(material.component, compProps, () => {
// 使用函数形式传入插槽,支持子组件递归渲染
const childNodes = (children || []).map(child => h(SchemaRenderer, { schema: child }))
return childNodes
})
}
}
})
export default SchemaRenderer
这里有一个容易被忽略的细节:h 的第三个参数传数组还是传函数,行为完全不同。传函数形式表示使用默认插槽,子节点会作为插槽内容渲染,这对布局类容器组件(如 McContainer、McTabs)至关重要;而直接传数组则只是普通子节点。低代码场景下容器组件通常通过插槽接收子组件,所以推荐统一使用函数形式。
性能方面,如果页面组件数量达到几百个,建议对 SchemaRenderer 做精细化响应式控制。可以把 schema 拆成 shallowRef 加手动触发更新,或者利用 Vue 3 的 provide/inject 把编辑状态(如当前选中组件id)与渲染数据分离,避免编辑器中鼠标hover高亮这类高频状态变化引起整棵组件树的重渲染。
三、物料体系:注册、分类与属性面板自动生成
物料是低代码平台的积木。每个物料需要提供三样东西:组件本身、描述其可配置项的 propSchema、以及可选的默认数据和事件定义。渲染引擎和属性面板都依赖这份注册表工作。
// 物料注册表
const materialRegistry = {
McBreadcrumb: {
component: McBreadcrumb,
label: '轮播图',
group: '基础组件',
icon: 'banner-icon',
// 属性面板根据此配置自动生成表单
propSchema: [
{ key: 'imageUrl', label: '图片地址', type: 'string', format: 'image' },
{ key: 'interval', label: '轮播间隔(ms)', type: 'number', min: 1000 },
{ key: 'autoplay', label: '自动播放', type: 'boolean', default: true }
],
events: [
{ name: 'click', label: '点击轮播项' }
]
},
McGoodsList: {
component: McGoodsList,
label: '商品列表',
group: '业务组件',
propSchema: [
{ key: 'columns', label: '列数', type: 'enum', options: [1, 2, 3] },
{ key: 'dataSource', label: '数据接口', type: 'string' }
]
}
}
属性面板的实现思路是遍历 propSchema,根据 type 字段映射到不同的表单控件:string 映射输入框、boolean 映射开关、enum 映射下拉框、带 format: 'image' 的映射为图片上传组件。这样每接入一个新物料,只需登记一份描述文件,编辑器侧无需写任何表单代码,扩展成本极低。
物料按业务属性分组(基础组件、营销组件、业务组件)可以大幅提升搭建效率。移动端场景下还要注意物料内部的适配实现,推荐统一在物料内使用 rem 或 vw 布局,页面容器根据 pageConfig.viewport 动态设置根字号,保证预览效果与真实手机一致。
四、实时预览:iframe 沙箱与 postMessage 通信
编辑器画布和最终产物运行环境不同,最稳妥的做法是把预览区放进一个 iframe 中,通过 postMessage 双向通信。这样做有三个好处:样式完全隔离,编辑器的 UI 样式不会污染用户页面;预览环境可以独立注入移动端 UA 相关的 viewport 设置;发布时预览页本身就可以直接作为产物输出。
编辑器侧向 iframe 推送 schema 的实现如下:
// 编辑器侧:schema 变化时同步到预览 iframe
import { watch } from 'vue'
watch(schema, (newSchema) => {
iframeEl.contentWindow.postMessage({
type: 'SCHEMA_UPDATE',
payload: newSchema
}, '*')
}, { deep: true })
// 预览页侧:接收并更新
window.addEventListener('message', (event) => {
const { type, payload } = event.data
if (type === 'SCHEMA_UPDATE') {
schema.value = payload
}
})
反向通信同样重要:预览区里用户点击了某个组件,编辑器需要同步选中它并在属性面板展示配置。预览侧给每个物料组件包一层 wrapper,捕获点击事件后通过 postMessage 发送 COMPONENT_CLICK 消息,带上组件 id,编辑器收到后高亮对应的树节点并打开属性面板。选中态的视觉反馈(如描边)建议用 CSS 变量或伪元素实现,而不是往组件树里插入辅助节点,避免污染 schema。
消息通信要注意做节流,拖拽排序时 schema 变化频率极高,建议对 SCHEMA_UPDATE 做大约 100 到 200 毫秒的节流,并配合 JSON 差量对比跳过无变化的更新,否则低端机上预览会出现明显卡顿。
五、移动端适配与真机扫码预览
预览画布通常只是一个 375px 宽的模拟器容器,但真实产出需要适配各种尺寸的手机。方案是在预览页入口根据 viewport 设计稿宽度计算根字号:
function setupViewport(designWidth) {
const scale = window.innerWidth / designWidth
document.documentElement.style.fontSize = 100 * scale + 'px'
}
window.addEventListener('resize', () => setupViewport(375))
setupViewport(375)
物料内部所有尺寸使用 rem,例如 height: 2rem 表示设计稿上的 200px。这样无论预览容器多宽,比例都能保持一致。
真机预览是移动端低代码的刚需功能。实现方式是为当前 schema 生成一个临时访问链接(服务端把 schema 存入 Redis 并生成短码),页面展示二维码,手机扫码后加载同一个预览页并通过 URL 参数拉取 schema。由于预览页与编辑器共用同一套渲染引擎和物料,真机上看到的效果就是最终发布效果,做到了所见即所得。
总结
一套可落地的 Vue 3 中低代码移动端方案,本质上是「协议、引擎、物料、预览」四层能力的组合:JSON Schema 协议保证页面可描述、可存储、可迁移;基于 h 函数的递归渲染引擎让 JSON 变成真实可交互的组件树;物料注册表配合属性面板自动生成机制把扩展成本降到最低;iframe 加 postMessage 的预览架构则同时解决了隔离性与实时性。在此基础上,再叠加撤销重做(操作命令栈)、数据mock、一键发布等能力,就能逐步演进为一个完整的中低代码搭建平台。