之前写过一篇使用yarn实现的https://blog.csdn.net/qq_36437991/article/details/133644558,后面的两个没有写,这次准备重新实现
环境
pnpm 8.15.1
node 18.19.0
创建vue项目
pnpm create vite
cd hello-world
pnpm install
执行pnpm dev
启动项目
安装依赖包
pnpm add @tmagic/editor @tmagic/form @tmagic/stage @tmagic/design @tmagic/element-plus-adapter element-plus
修改代码
注册组件
修改main.js
import 'element-plus/dist/index.css';
import '@tmagic/editor/dist/style.css';
import '@tmagic/form/dist/style.css';
import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import TMagicDesign from '@tmagic/design';
import TMagicEditor from '@tmagic/editor';
import TMagicElementPlusAdapter from '@tmagic/element-plus-adapter';
import TMagicForm from '@tmagic/form';
import App from './App.vue';
createApp(App)
.use(ElementPlus)
.use(TMagicDesign, TMagicElementPlusAdapter)
.use(TMagicEditor)
.use(TMagicForm)
.mount('#app');
渲染编辑器
修改app.vue
<template>
<m-editor
v-model="value"
:render="render"
:component-group-list="componentGroupList"
></m-editor>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const value = ref();
const componentGroupList = ref([]);
const render = () => window.document.createElement('div');
</script>
<style>
html,
body,
#app,
.m-editor {
height: 100vh;
}
body {
margin: 0;
}
</style>
删除components文件夹,pnpm dev
启动项目报错
修改vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis',
},
},
},
})
重新启动项目不在报错
到这里一个空白的编辑器就运行起来了
添加组件列表
修改componentGroupList
const componentGroupList = ref([
{
title: '组件列表',
items: [
{
icon: 'https://vfiles.gtimg.cn/vupload/20220614/9cc3091655207317835.png',
text: 'HelloWorld',
type: 'hello-world',
},
],
},
]);
到这里,左侧会出现一个叫HelloWorld的爱心图标,点击它会发现没有反应,打开控制台会发现有error;这是因为还没有创建页面
点击中间的新增页面也会发现没有反应,这是因为没有编辑器初始值,只需要给value赋上初始值就可以了
设置编辑器初始值
修改value
const value = ref({
type: 'app',
// 必须加上ID,这个id可能是数据库生成的key,也可以是生成的uuid
id: 1,
items: [],
});
加上初始值后,点击新增页面就可以渲染出一个画布了,但是点击添加HelloWorld组件依然没有反应
这是因为这时的编辑器并能理解HelloWorld是什么,需要在render函数中处理
渲染
修改render
const render = () => {
const root = window.document.createElement('div');
const page = value.value.items[0];
if (!page) return root;
root.id = `${page.value.id}`;
createApp(
{
template: '<p v-for="node in config.items" :key="node.id" :id="node.id">hello world</p>',
props: ['config'],
},
{
config: page,
},
).mount(root);
return root;
};
这里用到了动态编译Vue模板,所以需要在vue.config.js中添加vue alias,因为用的vite,所以需要修改vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve:{
alias: {
// 在 Vite 中,你不需要使用 `$` 来指定默认导出
// 直接使用 'vue' 作为别名即可
vue: 'vue/dist/vue.esm-bundler.js'
}
},
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis',
},
},
},
})
render函数中获取page是通过value.value.items[0],这样只是表示第一个页面,如果页面有多个页面就会有问题 可以通过editorService.get(‘page’)获取到当前选中的页面
import { ref,createApp,computed } from 'vue';
import { editorService } from '@tmagic/editor';
const page = computed(() => editorService.get('page'));
到这已经能渲染出HelloWorld组件了,但是会发现无法选中,因为这时并不知道画布中的Dom已经发生变化,所以需要通知编辑器,需要调用
renderer.iframe.contentWindow.magic?.onRuntimeReady({});
renderer.iframe.contentWindow.magic?.onPageElUpdate(root);
最终完整的render函数实现
import type StageCore from '@tmagic/stage';
const render = async ({ renderer }: StageCore) => {
const root = window.document.createElement('div');
if (!page.value) return root;
const { width = 375, height = 1700 } = page.value.style || {};
root.id = `${page.value.id}`;
root.style.cssText = `
width: ${width}px;
height: ${height}px;
`;
createApp(
{
template: '<div v-for="node in config.items" :key="node.id" :id="node.id">hello world</div>',
props: ['config'],
},
{
config: page.value,
},
).mount(root);
renderer.on('onload', () => {
const style = window.document.createElement('style');
// 隐藏滚动条,重置默认样式
style.innerHTML = `
body {
overflow: auto;
}
html,body {
height: 100%; margin: 0;padding: 0;
}
html::-webkit-scrollbar {
width: 0 !important;
display: none;
}
`;
renderer.iframe?.contentDocument?.head.appendChild(style);
renderer.contentWindow?.magic?.onPageElUpdate(root);
renderer.contentWindow?.magic?.onRuntimeReady({});
});
return root;
};
以上就是一个简单的搭建编辑器的示例,安装上面的步骤完成后会发现,可以添加组件,也可选中组件,但是无法拖动,配置属性中的样式也无法生效,这是因为上述的render函数并不完整,没有处理dsl中style,下一节将详细介绍runtime的搭建,将不再使用render函数的方式而是使用runtimeUrl。
并不是render函数不好,但是从设计上将,render函数还是让渲染逻辑落到了编辑器中,@tmagic/editor的设计是希望做到渲染跟编辑器解耦