2.Runtime
创建项目
创建editor项目
将上一教程中的hello-world复制过来,改名hello-editor
创建runtime项目
vue create editor-runtime
cd editor-runtime
删除src/components/HelloWorld.vue
按钮需要用的ts types依赖
npm install --save @tmagic/core @tmagic/stage
实现runtime
将hello-editor中的render函数实现移植到runtime项目中
新建ui-page.vue文件
<template>
<div v-if="config" :data-tmagic-id="config.id" :style="style">
<div v-for="node in config.items" :key="node.id" :data-tmagic-id="node.id">hello world</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
const props = defineProps<{
config: any;
}>();
const style = computed(() => {
const { width = 375, height = 1700 } = props.config.style || {};
return {
width: `${width}px`,
height: `${height}px`,
};
});
</script>
将以下代码覆盖到src/App.vue中
<template>
<uiPage :config="page"></uiPage>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import uiPage from './ui-page.vue';
const page = ref<any>();
</script>
启动runtime
npm run serve -- --port=8078
修改editor
删除render props,添加runtimeUrl
修改样式
<template>
<m-editor
v-model="value"
:runtime-url="runtimeUrl"
:component-group-list="componentGroupList"
></m-editor>
</template>
<script lang="ts" setup>
// imports
const value = ref({
type: 'app',
// 必须加上ID,这个id可能是数据库生成的key,也可以是生成的uuid
id: 1,
items: [],
});
const componentGroupList = ref([
// ...
]);
const runtimeUrl = 'http://localhost:8078/';
</script>
<style>
#app {
overflow: auto;
}
html,body,#app {
height: 100%; margin: 0;padding: 0;
}
#app::-webkit-scrollbar {
width: 0 !important;
display: none;
}
</style>
启动editor
cd hello-editor
npm run serve -- --port=8080
跨域问题
在editor-runtime项目下的vue.config.js中添加如下配置
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
runtime与editor通信
到这里项目就可以正常访问了,但是会发现添加组件没有反应。
这是因为在runtime中无法直接获取到editor中的dsl,所以需要通过editor注入到window的magic api来交互
如出现在runtime中出现magic为undefined, 可以尝试在App.vue中通过监听message,来准备获取magic注入时机,然后调用magic.onRuntimeReady,示例代码如下
window.addEventListener('message', ({ data }) => {
if (!data.tmagicRuntimeReady) {
return;
}
window.magic?.onRuntimeReady({
// ...
});
});
这里可能会出现editor抛出message的时候,runtime还没有执行到监听message的情况 编辑器只在iframe onload事件中抛出message 如果出现runtime中接收不到message的情况,可以尝试在onMounted的时候调用magic.onRuntimeReady
import type { Magic } from '@tmagic/stage';
declare global {
interface Window {
magic?: Magic;
}
}
import type { Id, MApp, MNode } from '@tmagic/core';
import type { RemoveData, UpdateData } from '@tmagic/stage';
const root = ref<MApp>();
window.magic?.onRuntimeReady({
/** 当编辑器的dsl对象变化时会调用 */
updateRootConfig(config: MApp) {
root.value = config;
},
/** 当编辑器的切换页面时会调用 */
updatePageId(id: Id) {
page.value = root.value?.items?.find((item) => item.id === id);
},
/** 新增组件时调用 */
add({ config }: UpdateData) {
const parent = config.type === 'page' ? root.value : page.value;
parent.items?.push(config);
},
/** 更新组件时调用 */
update({ config }: UpdateData) {
const index = page.value.items?.findIndex((child: MNode) => child.id === config.id);
page.value.items.splice(index, 1, reactive(config));
},
/** 删除组件时调用 */
remove({ id }: RemoveData) {
const index = page.value.items?.findIndex((child: MNode) => child.id === id);
page.value.items.splice(index, 1);
},
});
同步页面dom给编辑器
由于组件渲染在runtime中,对于编辑器来说是个黑盒,并不知道哪个dom节点才是页面(对于dsl的解析渲染可能是千奇百怪的),所以需要将页面的dom节点同步给编辑器
watch(page, async () => {
// page配置变化后,需要等dom更新
await nextTick();
window?.magic.onPageElUpdate(pageComp.value?.$el);
});
以上就是一个简单runtime实现,以及与编辑的交互,这是一个不完善的实现(会发现组件再画布中无法自由拖动,是因为没有完整的解析style),但是其中已经几乎覆盖所有需要关心的内容
当前教程中实现了一个简单的page,tmagic提供了一个比较完善的实现,将在下一节介绍