mirror of
				https://github.com/Tencent/tmagic-editor.git
				synced 2025-11-04 18:52:18 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			234 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
# 2.Runtime
 | 
						||
 | 
						||
## 创建项目
 | 
						||
 | 
						||
[关于@vue/cli的详细教程可以查看官方文档](https://cli.vuejs.org/zh/guide/installation.html)
 | 
						||
 | 
						||
### 创建editor项目
 | 
						||
 | 
						||
将[上一教程](./hello-world.md)中的[hello-world](https://github.com/jia000/tmagic-tutorial/tree/master/course1/hello-world)复制过来,改名hello-editor
 | 
						||
 | 
						||
 | 
						||
### 创建runtime项目
 | 
						||
 | 
						||
```bash
 | 
						||
vue create editor-runtime
 | 
						||
 | 
						||
cd editor-runtime
 | 
						||
```
 | 
						||
 | 
						||
删除src/components/HelloWorld.vue
 | 
						||
 | 
						||
按钮需要用的ts types依赖
 | 
						||
 | 
						||
```bash
 | 
						||
npm install --save @tmagic/schema @tmagic/stage
 | 
						||
```
 | 
						||
 | 
						||
## 实现runtime
 | 
						||
 | 
						||
将hello-editor中的render函数实现移植到runtime项目中
 | 
						||
 | 
						||
新建ui-page.vue文件
 | 
						||
 | 
						||
```vue
 | 
						||
<template>
 | 
						||
  <div v-if="config" :id="config.id" :style="style">
 | 
						||
    <div v-for="node in config.items" :key="node.id" :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中
 | 
						||
 | 
						||
```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
 | 
						||
 | 
						||
```bash
 | 
						||
npm run serve -- --port=8078
 | 
						||
```
 | 
						||
 | 
						||
## 修改editor
 | 
						||
 | 
						||
删除render props,添加runtimeUrl
 | 
						||
 | 
						||
修改样式
 | 
						||
 | 
						||
```vue
 | 
						||
<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',
 | 
						||
  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
 | 
						||
 | 
						||
```bash
 | 
						||
cd hello-editor
 | 
						||
 | 
						||
npm run serve -- --port=8080
 | 
						||
```
 | 
						||
 | 
						||
## 跨域问题
 | 
						||
 | 
						||
在editor-runtime项目下的vue.config.js中添加如下配置
 | 
						||
 | 
						||
```javascript
 | 
						||
devServer: {
 | 
						||
  headers: {
 | 
						||
    'Access-Control-Allow-Origin': '*',
 | 
						||
  },
 | 
						||
},
 | 
						||
```
 | 
						||
 | 
						||
## runtime与editor通信
 | 
						||
 | 
						||
到这里项目就可以正常访问了,但是会发现添加组件没有反应。
 | 
						||
 | 
						||
这是因为在runtime中无法直接获取到editor中的dsl,所以需要通过editor注入到window的magic api来交互
 | 
						||
 | 
						||
> 如出现在runtime中出现magic为undefined, 可以尝试在App.vue中通过监听message,来准备获取magic注入时机,然后调用magic.onRuntimeReady,示例代码如下
 | 
						||
```js
 | 
						||
window.addEventListener('message', ({ data }) => {
 | 
						||
  if (!data.tmagicRuntimeReady) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  window.magic?.onRuntimeReady({
 | 
						||
    // ...
 | 
						||
  });
 | 
						||
});
 | 
						||
```
 | 
						||
> 这里可能会出现editor抛出message的时候,runtime还没有执行到监听message的情况
 | 
						||
> 编辑器只在iframe onload事件中抛出message
 | 
						||
> 如果出现runtime中接收不到message的情况,可以尝试在onMounted的时候调用magic.onRuntimeReady
 | 
						||
 | 
						||
 | 
						||
```ts
 | 
						||
import type { Magic } from '@tmagic/stage';
 | 
						||
 | 
						||
declare global {
 | 
						||
  interface Window {
 | 
						||
    magic?: Magic;
 | 
						||
  }
 | 
						||
}
 | 
						||
```
 | 
						||
 | 
						||
```ts
 | 
						||
import type { RemoveData, UpdateData } from '@tmagic/stage';
 | 
						||
import type { Id, MApp, MNode } from '@tmagic/schema';
 | 
						||
 | 
						||
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节点同步给编辑器
 | 
						||
 | 
						||
```ts
 | 
						||
watch(page, async () => {
 | 
						||
  // page配置变化后,需要等dom更新
 | 
						||
  await nextTick();
 | 
						||
  window?.magic.onPageElUpdate(pageComp.value?.$el);
 | 
						||
});
 | 
						||
```
 | 
						||
 | 
						||
以上就是一个简单runtime实现,以及与编辑的交互,这是一个不完善的实现(会发现组件再画布中无法自由拖动,是因为没有完整的解析style),但是其中已经几乎覆盖所有需要关心的内容
 | 
						||
 | 
						||
当前教程中实现了一个简单的page,tmagic提供了一个比较完善的实现,将在下一节介绍
 | 
						||
 | 
						||
[源码](https://github.com/jia000/tmagic-tutorial/tree/master/course2)
 |