Skip to content

Engine 使用示例

Engine 使用示例

基础使用

最简单的使用方式,使用默认插件列表:

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
return (
<Engine
plugins={DEFAULT_PLUGIN_LIST}
schema={pageSchema}
material={materials}
assetPackagesList={assetPackagesList}
/>
);
}

自定义插件列表

只使用部分插件:

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
const { DesignerPlugin, ComponentLibPlugin, RightPanelPlugin } = plugins;
function App() {
return (
<Engine plugins={[DesignerPlugin, ComponentLibPlugin, RightPanelPlugin]} schema={pageSchema} material={materials} />
);
}

生命周期回调

使用 onReadyonMount 回调:

import { Engine, plugins, EnginContext } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const onMount = (ctx: EnginContext) => {
console.log('Engine 已挂载');
};
const onReady = async (ctx: EnginContext) => {
console.log('所有插件已加载完成');
// 可以在这里访问 pluginManager 和 engine
const pluginManager = ctx.pluginManager;
const engine = ctx.engine;
// 获取设计器插件
const designerPlugin = await pluginManager.get('Designer');
// 获取历史记录插件
const historyPlugin = await pluginManager.get('History');
};
return (
<Engine
plugins={DEFAULT_PLUGIN_LIST}
schema={pageSchema}
material={materials}
onMount={onMount}
onReady={onReady}
/>
);
}

自定义工作台

通过 workbenchConfig 配置工作台:

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
return (
<Engine
plugins={DEFAULT_PLUGIN_LIST}
schema={pageSchema}
material={materials}
workbenchConfig={{
// 初始隐藏左侧面板
hiddenLeftPanel: true,
// 初始隐藏右侧面板
hiddenRightPanel: true,
}}
/>
);
}

动态控制工作台

通过 onReady 回调动态控制工作台:

import { Engine, plugins, EnginContext } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
import { Button } from 'antd';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const onReady = (ctx: EnginContext) => {
const workbench = ctx.engine.getWorkbench();
// 自定义顶部栏
workbench?.replaceTopBarView(
<div style={{ padding: '0 20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>我的编辑器</div>
<div>
<Button onClick={() => ctx.engine.preview()}>预览</Button>
<Button onClick={() => ctx.engine.existPreview()}>退出预览</Button>
</div>
</div>
);
};
return <Engine plugins={DEFAULT_PLUGIN_LIST} schema={pageSchema} material={materials} onReady={onReady} />;
}

保存和加载页面数据

import { Engine, plugins, EnginContext } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
import { Button, message } from 'antd';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const [pageSchema, setPageSchema] = useState(() => {
// 从本地存储加载
const saved = localStorage.getItem('pageSchema');
return saved ? JSON.parse(saved) : defaultPageSchema;
});
const onReady = (ctx: EnginContext) => {
const workbench = ctx.engine.getWorkbench();
workbench?.replaceTopBarView(
<div style={{ padding: '0 20px', display: 'flex', justifyContent: 'flex-end' }}>
<Button
type="primary"
onClick={() => {
// 导出页面数据
const pageData = ctx.engine.pageModel.export();
localStorage.setItem('pageSchema', JSON.stringify(pageData));
message.success('保存成功');
}}
>
保存
</Button>
</div>
);
};
return <Engine plugins={DEFAULT_PLUGIN_LIST} schema={pageSchema} material={materials} onReady={onReady} />;
}

动态更新页面

import { Engine, plugins, EnginContext } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
import { Button } from 'antd';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const onReady = (ctx: EnginContext) => {
const workbench = ctx.engine.getWorkbench();
workbench?.replaceTopBarView(
<Button
onClick={() => {
// 从服务器获取新页面数据
fetch('/api/page')
.then((res) => res.json())
.then((newPageSchema) => {
// 更新页面
ctx.engine.updatePage(newPageSchema);
});
}}
>
刷新页面
</Button>
);
};
return <Engine plugins={DEFAULT_PLUGIN_LIST} schema={pageSchema} material={materials} onReady={onReady} />;
}

动态更新物料

import { Engine, plugins, EnginContext } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
import { Button } from 'antd';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const onReady = async (ctx: EnginContext) => {
const workbench = ctx.engine.getWorkbench();
workbench?.replaceTopBarView(
<Button
onClick={async () => {
// 从服务器获取新物料
const [newMaterials, newAssetPackages] = await Promise.all([
fetch('/api/materials').then((res) => res.json()),
fetch('/api/asset-packages').then((res) => res.json()),
]);
// 更新物料
await ctx.engine.updateMaterials(newMaterials, newAssetPackages, {
formatComponents: (components) => {
// 自定义组件格式化逻辑
return components;
},
});
}}
>
更新物料
</Button>
);
};
return (
<Engine
plugins={DEFAULT_PLUGIN_LIST}
schema={pageSchema}
material={materials}
assetPackagesList={assetPackagesList}
onReady={onReady}
/>
);
}

监听节点选中

import { Engine, plugins, EnginContext } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const onReady = (ctx: EnginContext) => {
// 监听节点选中变化
ctx.engine.emitter.on('onSelectNodeChange', ({ node }) => {
if (node) {
console.log('选中节点:', node);
console.log('节点ID:', node.id);
console.log('组件名:', node.componentName);
} else {
console.log('取消选中');
}
});
};
return <Engine plugins={DEFAULT_PLUGIN_LIST} schema={pageSchema} material={materials} onReady={onReady} />;
}

自定义渲染器 JS 地址

如果渲染器 JS 不在默认位置,可以自定义:

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
return (
<Engine
plugins={DEFAULT_PLUGIN_LIST}
schema={pageSchema}
material={materials}
// 使用 CDN 地址
renderJSUrl="https://cdn.example.com/render.umd.js"
// 或使用相对路径
// renderJSUrl="/assets/render.umd.js"
/>
);
}

配置 Monaco Editor CDN

如果使用 CDN 加载 Monaco Editor:

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
return (
<Engine
plugins={DEFAULT_PLUGIN_LIST}
schema={pageSchema}
material={materials}
monacoEditor={{
cndUrl: 'https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs',
}}
/>
);
}

自定义样式

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
import './custom.css';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
return (
<Engine
plugins={DEFAULT_PLUGIN_LIST}
schema={pageSchema}
material={materials}
className="my-custom-engine"
style={{
width: '100%',
height: '100vh',
}}
/>
);
}

使用 beforeInitRender

beforeInitRender 用于在渲染器初始化之前配置 iframe 环境。

场景 1: 注入全局变量和配置

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const beforePluginRun = ({ pluginManager }) => {
pluginManager.customPlugin('Designer', (pluginInstance) => {
pluginInstance.ctx.config.beforeInitRender = async ({ iframe }) => {
const subWin = iframe.getWindow();
if (!subWin) return;
// 必须:注入 React
(subWin as any).React = window.React;
(subWin as any).ReactDOM = window.ReactDOM;
(subWin as any).ReactDOMClient = window.ReactDOMClient;
// 注入应用配置
(subWin as any).APP_CONFIG = {
apiUrl: process.env.REACT_APP_API_URL,
theme: 'light',
language: 'zh-CN',
};
// 注入工具函数
(subWin as any).utils = {
formatDate: (date) => new Date(date).toLocaleDateString(),
formatCurrency: (amount) => `¥${amount.toFixed(2)}`,
};
};
return pluginInstance;
});
};
return (
<Engine plugins={DEFAULT_PLUGIN_LIST} schema={pageSchema} material={materials} beforePluginRun={beforePluginRun} />
);
}

场景 2: 注入第三方库

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
import axios from 'axios';
import dayjs from 'dayjs';
import _ from 'lodash';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const beforePluginRun = ({ pluginManager }) => {
pluginManager.customPlugin('Designer', (pluginInstance) => {
pluginInstance.ctx.config.beforeInitRender = async ({ iframe }) => {
const subWin = iframe.getWindow();
if (!subWin) return;
// 注入 React(必需)
(subWin as any).React = window.React;
(subWin as any).ReactDOM = window.ReactDOM;
(subWin as any).ReactDOMClient = window.ReactDOMClient;
// 注入第三方库
(subWin as any).axios = axios;
(subWin as any).dayjs = dayjs;
(subWin as any)._ = _;
console.log('第三方库注入成功');
};
return pluginInstance;
});
};
return (
<Engine plugins={DEFAULT_PLUGIN_LIST} schema={pageSchema} material={materials} beforePluginRun={beforePluginRun} />
);
}

场景 3: 设置跨窗口通信

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
import { message } from 'antd';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const beforePluginRun = ({ pluginManager }) => {
pluginManager.customPlugin('Designer', (pluginInstance) => {
pluginInstance.ctx.config.beforeInitRender = async ({ iframe }) => {
const subWin = iframe.getWindow();
if (!subWin) return;
// 注入 React
(subWin as any).React = window.React;
(subWin as any).ReactDOM = window.ReactDOM;
(subWin as any).ReactDOMClient = window.ReactDOMClient;
// 监听来自 iframe 的消息
subWin.addEventListener('message', (event) => {
if (event.data.source === 'component') {
console.log('收到组件消息:', event.data);
// 处理不同类型的消息
switch (event.data.type) {
case 'notification':
message.info(event.data.message);
break;
case 'error':
message.error(event.data.message);
break;
}
}
});
// 注入发送消息到父窗口的方法
(subWin as any).sendToEditor = (data) => {
window.postMessage(
{
source: 'iframe',
timestamp: Date.now(),
...data,
},
'*'
);
};
};
return pluginInstance;
});
};
return (
<Engine plugins={DEFAULT_PLUGIN_LIST} schema={pageSchema} material={materials} beforePluginRun={beforePluginRun} />
);
}

场景 4: 初始化主题和样式

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const beforePluginRun = ({ pluginManager }) => {
pluginManager.customPlugin('Designer', (pluginInstance) => {
pluginInstance.ctx.config.beforeInitRender = async ({ iframe }) => {
const subWin = iframe.getWindow();
const subDoc = iframe.getDocument();
if (!subWin || !subDoc) return;
// 注入 React
(subWin as any).React = window.React;
(subWin as any).ReactDOM = window.ReactDOM;
(subWin as any).ReactDOMClient = window.ReactDOMClient;
// 注入主题配置
(subWin as any).THEME = {
primaryColor: '#1890ff',
successColor: '#52c41a',
warningColor: '#faad14',
errorColor: '#ff4d4f',
fontSize: '14px',
borderRadius: '4px',
};
// 添加全局样式
const style = subDoc.createElement('style');
style.textContent = `
:root {
--primary-color: #1890ff;
--text-color: #333;
--border-color: #d9d9d9;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, sans-serif;
color: var(--text-color);
}
`;
subDoc.head.appendChild(style);
};
return pluginInstance;
});
};
return (
<Engine plugins={DEFAULT_PLUGIN_LIST} schema={pageSchema} material={materials} beforePluginRun={beforePluginRun} />
);
}

场景 5: 注入模拟数据和 API

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const beforePluginRun = ({ pluginManager }) => {
pluginManager.customPlugin('Designer', (pluginInstance) => {
pluginInstance.ctx.config.beforeInitRender = async ({ iframe }) => {
const subWin = iframe.getWindow();
if (!subWin) return;
// 注入 React
(subWin as any).React = window.React;
(subWin as any).ReactDOM = window.ReactDOM;
(subWin as any).ReactDOMClient = window.ReactDOMClient;
// 注入模拟 API
(subWin as any).mockAPI = {
// 获取用户信息
getUserInfo: async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
return {
id: 1,
name: '张三',
email: 'zhangsan@example.com',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix',
};
},
// 获取产品列表
getProducts: async () => {
await new Promise((resolve) => setTimeout(resolve, 300));
return [
{ id: 1, name: '产品 A', price: 299, stock: 100 },
{ id: 2, name: '产品 B', price: 399, stock: 50 },
{ id: 3, name: '产品 C', price: 199, stock: 200 },
];
},
// 提交订单
submitOrder: async (orderData) => {
await new Promise((resolve) => setTimeout(resolve, 800));
console.log('提交订单:', orderData);
return {
success: true,
orderId: `ORDER-${Date.now()}`,
};
},
};
// 注入模拟数据
(subWin as any).mockData = {
users: [
{ id: 1, name: '张三', role: 'admin' },
{ id: 2, name: '李四', role: 'user' },
],
categories: ['电子产品', '图书', '服装', '食品'],
};
};
return pluginInstance;
});
};
return (
<Engine plugins={DEFAULT_PLUGIN_LIST} schema={pageSchema} material={materials} beforePluginRun={beforePluginRun} />
);
}

场景 6: 开发环境调试工具

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const beforePluginRun = ({ pluginManager }) => {
pluginManager.customPlugin('Designer', (pluginInstance) => {
pluginInstance.ctx.config.beforeInitRender = async ({ iframe }) => {
const subWin = iframe.getWindow();
if (!subWin) return;
// 注入 React
(subWin as any).React = window.React;
(subWin as any).ReactDOM = window.ReactDOM;
(subWin as any).ReactDOMClient = window.ReactDOMClient;
// 只在开发环境注入调试工具
if (process.env.NODE_ENV === 'development') {
// 调试工具对象
(subWin as any).__DEBUG__ = {
// 日志工具
log: (...args) => {
console.log('[iframe]', ...args);
},
error: (...args) => {
console.error('[iframe]', ...args);
},
warn: (...args) => {
console.warn('[iframe]', ...args);
},
// 组件调试
inspectComponent: (componentName) => {
console.log(`检查组件: ${componentName}`);
// 可以添加更多检查逻辑
},
// 性能监控
performance: {
start: (label) => {
console.time(`[iframe] ${label}`);
},
end: (label) => {
console.timeEnd(`[iframe] ${label}`);
},
},
};
// 启用详细日志
(subWin as any).__VERBOSE_LOGGING__ = true;
// 监听错误
subWin.addEventListener('error', (event) => {
console.error('[iframe error]', {
message: event.error?.message,
stack: event.error?.stack,
filename: event.filename,
lineno: event.lineno,
});
});
// 监听未捕获的 Promise 错误
subWin.addEventListener('unhandledrejection', (event) => {
console.error('[iframe unhandled promise]', event.reason);
});
}
};
return pluginInstance;
});
};
return (
<Engine plugins={DEFAULT_PLUGIN_LIST} schema={pageSchema} material={materials} beforePluginRun={beforePluginRun} />
);
}

综合示例:完整的 beforeInitRender 配置

import { Engine, plugins } from '@chamn/engine';
import '@chamn/engine/dist/style.css';
import axios from 'axios';
import dayjs from 'dayjs';
const { DEFAULT_PLUGIN_LIST } = plugins;
function App() {
const beforePluginRun = ({ pluginManager }) => {
pluginManager.customPlugin('Designer', (pluginInstance) => {
pluginInstance.ctx.config.beforeInitRender = async ({ iframe }) => {
const subWin = iframe.getWindow();
const subDoc = iframe.getDocument();
if (!subWin || !subDoc) {
console.error('无法获取 iframe window 或 document');
return;
}
console.log('开始初始化 iframe 环境...');
try {
// 1. 注入 React(必需)
(subWin as any).React = window.React;
(subWin as any).ReactDOM = window.ReactDOM;
(subWin as any).ReactDOMClient = window.ReactDOMClient;
console.log('✓ React 注入成功');
// 2. 注入应用配置
(subWin as any).APP_CONFIG = {
apiUrl: process.env.REACT_APP_API_URL || 'https://api.example.com',
environment: process.env.NODE_ENV,
version: '1.0.0',
features: {
enableAnalytics: true,
enableDebug: process.env.NODE_ENV === 'development',
},
};
console.log('✓ 应用配置注入成功');
// 3. 注入第三方库
(subWin as any).axios = axios;
(subWin as any).dayjs = dayjs;
console.log('✓ 第三方库注入成功');
// 4. 设置跨窗口通信
(subWin as any).sendToEditor = (data) => {
window.postMessage(
{
source: 'iframe',
timestamp: Date.now(),
...data,
},
'*'
);
};
subWin.addEventListener('message', (event) => {
if (event.data.source === 'component') {
console.log('收到组件消息:', event.data);
}
});
console.log('✓ 跨窗口通信设置成功');
// 5. 初始化样式和主题
(subWin as any).THEME = {
primaryColor: '#1890ff',
fontSize: '14px',
};
const style = subDoc.createElement('style');
style.textContent = `
:root {
--primary-color: #1890ff;
--text-color: #333;
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto;
}
`;
subDoc.head.appendChild(style);
console.log('✓ 主题和样式初始化成功');
// 6. 开发环境配置
if (process.env.NODE_ENV === 'development') {
(subWin as any).__DEBUG__ = {
log: (...args) => console.log('[iframe]', ...args),
error: (...args) => console.error('[iframe]', ...args),
};
subWin.addEventListener('error', (event) => {
console.error('[iframe error]', event.error);
});
console.log('✓ 调试工具初始化成功');
}
console.log('iframe 环境初始化完成!');
} catch (error) {
console.error('iframe 环境初始化失败:', error);
}
};
return pluginInstance;
});
};
return (
<Engine plugins={DEFAULT_PLUGIN_LIST} schema={pageSchema} material={materials} beforePluginRun={beforePluginRun} />
);
}