diff --git a/docs/src/.vuepress/config.ts b/docs/src/.vuepress/config.ts
index c22f38d9..4f0c7d0e 100644
--- a/docs/src/.vuepress/config.ts
+++ b/docs/src/.vuepress/config.ts
@@ -121,6 +121,7 @@ const sidebar = {
children: [
'/tutorial/hello-world',
'/tutorial/runtime',
+ '/tutorial/render',
]
},
]
diff --git a/docs/src/api/editor/editor.md b/docs/src/api/editor/editor.md
index 4a14da2f..1e9e80e6 100644
--- a/docs/src/api/editor/editor.md
+++ b/docs/src/api/editor/editor.md
@@ -80,6 +80,13 @@ import { FolderOpened, SwitchButton, Tickets } from '@element-plus/icons';
::: tip
icon使用的是[element-plus icon](https://element-plus.org/zh-CN/component/icon.html)
+
+也可直接使用url,例如
+```js
+{
+ icon: 'https://vfiles.gtimg.cn/vupload/20220614/9cc3091655207317835.png'
+}
+```
:::
::: warning
@@ -119,6 +126,17 @@ import ModListPanel from '../components/sidebars/ModListPanel.vue';
}
```
+::: tip
+icon使用的是[element-plus icon](https://element-plus.org/zh-CN/component/icon.html)
+
+也可直接使用url,例如
+```js
+{
+ icon: 'https://vfiles.gtimg.cn/vupload/20220614/9cc3091655207317835.png'
+}
+```
+:::
+
### menu
- **类型:** [MenuBarData](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/type.ts)
@@ -177,6 +195,17 @@ import { ArrowLeft, Coin } from '@element-plus/icons';
}
```
+::: tip
+icon使用的是[element-plus icon](https://element-plus.org/zh-CN/component/icon.html)
+
+也可直接使用url,例如
+```js
+{
+ icon: 'https://vfiles.gtimg.cn/vupload/20220614/9cc3091655207317835.png'
+}
+```
+:::
+
### render
- **类型:** Function
diff --git a/docs/src/guide/advanced/coupling.md b/docs/src/guide/advanced/coupling.md
index 11c406bc..4fad1de5 100644
--- a/docs/src/guide/advanced/coupling.md
+++ b/docs/src/guide/advanced/coupling.md
@@ -82,40 +82,26 @@ export default {
-
```
-::: tip
-在用 vue 实现的 组件中,我们通过 inject 方式来提供核心 app 和高阶组件 hoc。调用联动事件方法时,tmagic-editor是通过组件的 ref,并直接调用当前组件的方法。
-:::
-
#### react 版本实现
在 react 的实现中,由于tmagic-editor提供的 @tmagic/ui-react 版本是用 hook 实现的。所以组件开发我们也相应的需要使用 hook 方式。
@@ -125,10 +111,8 @@ import React from 'react';
import { useApp } from '@tmagic/ui-react';
function Test({ config }) {
- // react 和 vue 实现不同,我们通过 useApp 这个 hook 来提供 app, ref 等核心内容
- // 其中 ref 需要绑定到你的组件上作为 ref。因为一些公共事件会需要使用到你的组件 dom
- // 同时这个 ref 也会在tmagic-editor的高级函数钩子中,将你的组件 dom 作为参数提供给自定义钩子
- const { app, ref } = useApp({
+ // react 和 vue 实现不同,我们通过 useApp 这个 hook 来提供 app 等核心内容
+ const { app } = useApp({
config,
// 此处实现事件动作
// 通过向 useApp 这个 hook 提供 methods 方法
@@ -147,7 +131,6 @@ function Test({ config }) {
return (
{
const root = window.document.createElement('div');
@@ -268,10 +270,10 @@ const render = async ({ renderer }: StageCore) => {
}
`;
- renderer.iframe.contentDocument.head.appendChild(style);
+ renderer.iframe?.contentDocument?.head.appendChild(style);
- renderer.iframe.contentWindow.magic?.onPageElUpdate(root);
- renderer.iframe.contentWindow.magic?.onRuntimeReady({});
+ renderer.contentWindow?.magic?.onPageElUpdate(root);
+ renderer.contentWindow?.magic?.onRuntimeReady({});
});
return root;
diff --git a/docs/src/tutorial/render.md b/docs/src/tutorial/render.md
new file mode 100644
index 00000000..01731572
--- /dev/null
+++ b/docs/src/tutorial/render.md
@@ -0,0 +1,282 @@
+# 3.[DSL](../guide/conception.md#dsl) 解析渲染
+
+tmagic 提供了 vue3/vue2/react 三个版本的解析渲染组件,可以直接使用
+
+[@tmagic/ui](https://www.npmjs.com/package/@tmagic/ui)
+
+[@tmagic/ui-vue2](https://www.npmjs.com/package/@tmagic/ui-vue2)
+
+[@tmagic/ui-react](https://www.npmjs.com/package/@tmagic/ui-react)
+
+接下来是已vue3为基础,来讲述如何实现一个[@tmagic/ui](https://www.npmjs.com/package/@tmagic/ui)
+
+## 准备工作
+
+### 创建项目
+
+将[上一教程](./runtime.md)中的[editor-runtime](https://github.com/jia000/tmagic-tutorial/tree/master/course2/editor-runtime)和[hello-editor](https://github.com/jia000/tmagic-tutorial/tree/master/course2/hellow-editor)复制过来
+
+## 基础概念
+
+### 节点(Node)
+
+每一个组件最终都是由一个节点来描述,每个节点至少拥有id,type两个属性
+
+id: 节点的唯一标识,不可重复
+
+type: 节点的类型,有业务自行定义
+
+### 容器(Container)
+
+容器也是节点的一种,容器可以包含多个节点并且是保存在items属性下
+
+items: 容器下包含的节点组成的数组,items中不能有page,app
+
+### 页面(Page)
+
+页面是容器的一种,type固定为page,items中不能有page
+
+### 根(Root)
+
+根节点也是一个容器,type固定为app,items只能是page
+
+## 实现
+
+创建hello-ui目录
+
+```
+.
+└─editor-runtime
+└─hello-editor
+└─hello-ui
+```
+
+### 渲染节点
+
+在hello-ui下创建 Component.vue 文件
+
+由于节点的type是由业务自行定义的,所以需要使用动态组件渲染,在vue下可以使用[component](https://cn.vuejs.org/v2/api/#component)组件来实现
+
+[component](https://cn.vuejs.org/v2/api/#component) 是通过is参数来决定哪个组件被渲染,所以将type与组件做绑定
+
+例如有组件 HelloWorld,可以将组件全局注册
+
+```js
+app.component('hello-world', HelloWorld);
+```
+
+然后将'hello-world'作为type,那么is="hello-world"就会渲染 HelloWorld 组件
+
+为了让组件渲染出来的dom能被编辑器识别到,还需要将节点的id作为dom的id
+
+```vue
+
+
+
+
+
+```
+
+接下来就需要解析节点的样式,在tmagic/editor中默认会将样式配置保存到节点的style属性中,如果自行定义到了其他属性,则已实际为准
+
+解析style需要注意几个地方
+
+1. 数字
+
+css中的数值有些是需要单位的,例如px,有些是不需要的,例如opacity
+
+在tmagic/editor中,默认都是不带单位的,所以需要将需要单位的地方补齐单位
+
+这里做补齐px处理,如果需要做屏幕大小适应, 可以使用rem或者vw,这个可以根据自身需求处理。
+
+2. url
+
+css中的[url](https://developer.mozilla.org/zh-CN/docs/Web/CSS/url)需要是用url(),所以当值为url时,需要转为url(xxx)
+
+3. transform
+
+[transform](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform)属性可以指定为关键字值none 或一个或多个transform-function值。
+
+```ts
+const fillBackgroundImage = (value: string) => {
+ if (value && !/^url/.test(value) && !/^linear-gradient/.test(value)) {
+ return `url(${value})`;
+ }
+ return value;
+};
+
+const style = computed(() => {
+ if (!props.config.style) {
+ return {};
+ }
+
+ const results: Record
= {};
+
+ const whiteList = ['zIndex', 'opacity', 'fontWeight'];
+ Object.entries(props.config.style).forEach(([key, value]) => {
+ if (key === 'backgroundImage') {
+ value && (results[key] = fillBackgroundImage(value));
+ } else if (key === 'transform' && typeof value !== 'string') {
+ results[key] = Object.entries(value as Record)
+ .map(([transformKey, transformValue]) => {
+ let defaultValue = 0;
+ if (transformKey === 'scale') {
+ defaultValue = 1;
+ }
+ return `${transformKey}(${transformValue || defaultValue})`;
+ })
+ .join(' ');
+ } else if (!whiteList.includes(key) && value && /^[-]?[0-9]*[.]?[0-9]*$/.test(value)) {
+ results[key] = `${value}px`;
+ } else {
+ results[key] = value;
+ }
+ });
+
+ return results;
+});
+```
+
+### 渲染容器
+
+容器与普通节点的区别,就是需要多一个items的解析
+
+新增Container.vue文件
+
+```vue
+
+
+
+
+
+
+
+```
+
+### 渲染页面
+
+页面就是容器,之所以单独存在,是页面会自己的方法,例如reload等
+
+Page.vue文件
+
+```vue
+
+
+
+
+
+```
+
+## 在runtime中使用 hello-ui
+
+删除editor-runtime/src/ui-page.vue
+
+将App.vue中的ui-page改成hello-ui中的Page
+
+```vue
+
+
+
+
+
+```
+
+在editor-runtime main.ts中注册HelloWorld
+
+```ts
+import { createApp } from 'vue';
+
+import type { Magic } from '@tmagic/stage';
+
+// eslint-disable-next-line
+import { HelloWorld } from 'hello-ui';
+
+import App from './App.vue';
+
+declare global {
+ interface Window {
+ magic?: Magic;
+ }
+}
+
+const app = createApp(App);
+
+app.component('hello-world', HelloWorld);
+
+app.mount('#app');
+
+```
+
+[源码](https://github.com/jia000/tmagic-tutorial/tree/master/course3)
diff --git a/docs/src/tutorial/runtime.md b/docs/src/tutorial/runtime.md
index da1d3455..dc6fba22 100644
--- a/docs/src/tutorial/runtime.md
+++ b/docs/src/tutorial/runtime.md
@@ -19,6 +19,12 @@ cd editor-runtime
删除src/components/HelloWorld.vue
+按钮需要用的ts types依赖
+
+```bash
+npm install --save @tmagic/schema @tmagic/stage
+```
+
## 实现runtime
将hello-editor中的render函数实现移植到runtime项目中
@@ -145,40 +151,58 @@ devServer: {
在App.vue中通过监听message,来准备获取magic注入时机,然后调用magic.onRuntimeReady,示例代码如下
+> 这里可能会出现editor抛出message的时候,runtime还没有执行到监听message的情况
+> 编辑器只在iframe onload事件中抛出message
+> 如果出现runtime中接收不到message的情况,可以尝试在onMounted的时候调用magic.onRuntimeReady
+
```ts
-const root = ref();
+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();
window.addEventListener('message', ({ data }) => {
if (!data.tmagicRuntimeReady) {
return;
}
- (window as any).magic?.onRuntimeReady({
+ window.magic?.onRuntimeReady({
/** 当编辑器的dsl对象变化时会调用 */
- updateRootConfig(config: any) {
+ updateRootConfig(config: MApp) {
root.value = config;
},
/** 当编辑器的切换页面时会调用 */
- updatePageId(id: string) {
- page.value = root.value?.items?.find((item: any) => item.id === id);
+ updatePageId(id: Id) {
+ page.value = root.value?.items?.find((item) => item.id === id);
},
/** 新增组件时调用 */
- add({ config }: any) {
+ add({ config }: UpdateData) {
const parent = config.type === 'page' ? root.value : page.value;
parent.items?.push(config);
},
/** 更新组件时调用 */
- update({ config }: any) {
- const index = page.value.items?.findIndex((child: any) => child.id === config.id);
+ 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 }: any) {
- const index = page.value.items?.findIndex((child: any) => child.id === id);
+ remove({ id }: RemoveData) {
+ const index = page.value.items?.findIndex((child: MNode) => child.id === id);
page.value.items.splice(index, 1);
},
});
@@ -194,11 +218,11 @@ window.addEventListener('message', ({ data }) => {
watch(page, async () => {
// page配置变化后,需要等dom更新
await nextTick();
- (window as any).magic.onPageElUpdate(pageComp.value?.$el);
+ window?.magic.onPageElUpdate(pageComp.value?.$el);
});
```
-以上就是一个简单runtime实现,以及与编辑的交互,这是一个不完善的实现,但是其中已经几乎覆盖所有需要关心的内容
+以上就是一个简单runtime实现,以及与编辑的交互,这是一个不完善的实现(会发现组件再画布中无法自由拖动,是因为没有完整的解析style),但是其中已经几乎覆盖所有需要关心的内容
当前教程中实现了一个简单的page,tmagic提供了一个比较完善的实现,将在下一节介绍