如果低代码平台只能把组件拖到页面上,它解决的是页面搭建效率问题。但营销页面真正复杂的地方,往往不在“有什么组件”,而在“组件之间发生了什么”。
比如一个活动页:
用户点击按钮 -> 上报点击埋点 -> 判断用户是否登录 -> 未登录则调起登录 -> 已登录则调用领券接口 -> 领取成功后刷新优惠券状态 -> 提示用户领取成功
这不是单个按钮组件能完整解决的问题。它需要事件系统、数据源系统、表达式引擎和运行时调度。
所以营销低代码平台不能只有组件物料,还必须有一套轻量的 Workflow 能力。
事件系统是低代码的行为层
组件负责展示,事件负责行为。
按钮组件可以声明自己支持 onClick:
export const ButtonMeta = { type: "Button", events: ["onClick"]};
页面 DSL 决定点击后做什么:
{ "id": "button_1", "type": "Button", "props": { "text": "立即领取" }, "events": { "onClick": [ { "type": "track", "event": "coupon_click" }, { "type": "request", "dataSource": "coupon.receive" }, { "type": "toast", "message": "领取成功" } ] }}
这里的按钮组件不需要知道什么是优惠券,也不需要知道埋点怎么上报。它只在用户点击时触发 onClick。运行时根据 DSL 执行动作链。
这种设计让组件保持通用,让业务行为留在配置里。
事件动作需要标准化
事件动作不能随便写字符串。要让平台可维护,动作类型必须标准化。
常见动作可以包括:
| 动作 | 说明 |
|---|---|
track | 上报埋点 |
openUrl | 打开链接 |
request | 调用数据源 |
setState | 设置页面变量 |
showToast | 展示提示 |
openModal | 打开弹窗 |
submitForm | 提交表单 |
callBridge | 调用客户端能力 |
triggerEvent | 触发自定义事件 |
运行时实现一个 action dispatcher:
async function runActions(actions, context) { for (const action of actions) { await runAction(action, context); }}async function runAction(action, context) { switch (action.type) { case "track": return tracking.report(action.event, resolve(action.params, context)); case "request": return dataSource.run(action.dataSource, context); case "openUrl": return navigation.open(resolve(action.url, context)); default: throw new Error(`Unknown action: ${action.type}`); }}
这样事件系统就从“组件里写逻辑”变成了“运行时解释动作”。
动作链要处理成功、失败和中断
真实营销链路不会永远成功。
领券接口可能失败,登录可能取消,表单校验可能不通过,埋点接口可能超时。事件系统必须能表达分支。
可以给动作增加错误策略:
{ "type": "request", "dataSource": "coupon.receive", "onSuccess": [ { "type": "toast", "message": "领取成功" } ], "onError": [ { "type": "toast", "message": "{{error.message}}" } ]}
动作还需要区分是否阻塞:
{ "type": "track", "event": "button_click", "blocking": false}
埋点通常不应该阻塞后续动作;登录和领券则需要阻塞,因为后续动作依赖它们的结果。
数据源系统统一管理动态数据
营销页面里常见数据源包括:
REST APIGraphQL商品系统用户画像CRM优惠券系统表单数据客户端 Bridge 返回数据
如果每个组件都自己发请求,会出现几个问题:
- 请求无法统一缓存。
- 首屏请求数量不可控。
- 错误处理不一致。
- 组件之间无法共享同一份数据。
- 发布前无法校验接口配置。
更好的方式是在页面 DSL 里定义数据源:
{ "dataSources": { "productList": { "type": "api", "method": "GET", "url": "/api/products", "params": { "campaignId": "{{page.query.campaignId}}" }, "priority": "visible", "cacheKey": "productList:{{page.query.campaignId}}" } }}
组件只消费结果:
{ "type": "ProductList", "props": { "items": "{{dataSources.productList.data}}" }}
运行时负责加载、缓存、错误处理和刷新。
数据源要有优先级
不是所有请求都应该阻塞首屏。
可以把数据源按优先级分成三类:
critical:首屏必须,渲染前加载visible:组件进入视口前加载idle:首屏后空闲加载
例如:
{ "dataSources": { "heroInfo": { "type": "api", "url": "/api/hero", "priority": "critical" }, "recommendProducts": { "type": "api", "url": "/api/recommend", "priority": "visible" }, "extraStats": { "type": "api", "url": "/api/stats", "priority": "idle" } }}
Renderer 可以先加载 critical 数据,渲染首屏,再逐步加载其他数据。
这对营销页性能非常关键。否则一个非首屏组件接口慢,也可能拖住整个页面。
表达式引擎连接数据和组件
数据源只是把数据放进上下文,表达式负责把上下文映射到组件属性。
例如:
{ "props": { "title": "{{user.name}} 的专属福利", "visible": "{{user.isLogin && coupon.available}}", "priceLabel": "{{product.price > 100 ? '高客单' : '普通'}}" }}
表达式引擎至少要支持:
- 路径读取。
- 布尔判断。
- 简单比较。
- 三元表达式。
- 内置格式化函数。
- 安全兜底值。
但表达式不能无限放开。它应该运行在受控上下文里:
只能访问平台提供的变量只能调用白名单函数不能访问 window/document不能发起任意网络请求不能执行无限循环
表达式越自由,平台越难保证安全和稳定。
组件通信应该通过事件和状态完成
组件之间不要互相调用。
例如车型筛选组件影响车型列表,不应该让筛选组件直接拿到列表组件实例,然后调用 reload。更好的方式是:
筛选组件触发 carFilter.change运行时更新页面状态车型列表监听状态变化或事件车型列表重新请求数据
DSL 可以表达绑定关系:
{ "bindings": [ { "source": "filter_1.onChange", "target": "carList_1.reload", "params": { "seriesId": "{{event.seriesId}}" } } ]}
运行时负责调度。这样组件之间没有直接依赖,页面配置决定联动关系。
Workflow 要防止循环和风暴
事件系统一旦支持配置化,就必须考虑失控。
典型问题:
A 组件变化触发 BB 组件变化触发 CC 又触发 A
或者:
一个筛选变化触发 20 个组件同时请求接口
所以 Workflow Runtime 需要几类保护:
- 每次用户操作生成 transaction id。
- 同一个 transaction 内记录调用链。
- 检测明显循环。
- 高频事件做 debounce。
- 请求类 action 做并发控制。
- 非关键 action 可降级或延迟。
例如:
function runWorkflow(event, context) { const transaction = createTransaction(event); return scheduler.run(event.actions, { ...context, transaction });}
如果一个 transaction 中同一个组件动作被重复触发,就可以跳过或报错。
小结
低代码营销平台不能只停留在组件搭建。
组件解决“页面长什么样”,事件解决“用户做了什么”,数据源解决“页面的数据从哪里来”,表达式解决“数据如何绑定到组件”,Workflow 解决“动作如何编排和执行”。
这些能力合起来,平台才从拖拽编辑器变成营销操作系统。
真正可维护的设计是:
组件不写死业务逻辑事件进入 DSL数据源统一管理表达式受控执行Workflow Runtime 负责调度性能和循环风险由运行时兜底
低代码平台的核心不是让页面“能搭出来”,而是让页面背后的行为也能被稳定描述、执行和治理。