简单的低开编辑器(一):实现组件渲染
好家伙,
项目目录如下: 该项目使用Vue3, Element-plus
效果图如下:
开搞:
1.写出简单界面
App.vue
<template>
<div class="app">
<Editor v-model="state"></Editor>
</div>
</template>
<script>
import { ref, provide } from 'vue';
import data from './data.json'
import Editor from './packages/editor'
import { registerConfig as config } from './utils/editor-config'
export default {
components: {
Editor
},
setup() {
const state = ref(data);
console.log(config)
provide('config', config);
return {
state
}
}
}
</script>
<style lang="scss">
.app {
position: fixed;
top: 20px;
left: 20px;
right: 20px;
bottom: 20px;
}
</style>
editor.jsx
import { computed, defineComponent,inject } from "vue";
import './editor.scss'
import EditorBlock from "./editor-block";
export default defineComponent({
props: {
modelValue: { type: Object }
},
setup(props) {
const data = computed({
get() {
return props.modelValue
}
})
console.log(data.value)
const containerStyles = computed(() => ({
width: data.value.container.width + 'px',
height: data.value.container.height + 'px'
}))
const config= inject('config')
return () => <div>
<div class="editor-left">
{/* 根据注册列表 渲染对应的内容 */}
{config.componentList.map(component=>(
<div class="editor-left-item">
<span>{component.label}</span>
<div>{component.preview()}</div>
</div>
))}
</div>
<div class="editor-top">菜单栏</div>
<div class="editor-right">属性控制栏目</div>
<div class="editor-container">
<div class="editor-container-canvas">
{/* 产生内容 */}
<div class="editor-container-canvas__content" style={containerStyles.value}>
{
(data.value.blocks.map(block=>(
<EditorBlock block={block}></EditorBlock>
)))
}
</div>
</div>
</div>
</div>
}
})
editor.scss
.editor {
width: 100%;
height: 100%;
&-left,
&-right {
position: absolute;
width: 270px;
background: red;
top: 0;
bottom: 0;
}
&-left {
left: 0;
&-item{
width:200px;
margin:20px auto;
display: flex;
justify-content:center;
align-items: center;
background: #fff;
padding:20px;
cursor:move;
user-select: none;
min-height: 80px;
position: relative;
> span{
position: absolute;
top:0;
left:0;
background: rgb(96, 205, 224);
color:#fff;
padding:4px;
}
&::after{
content: '';
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
background: #ccc;
opacity:0.2 ;
}
}
}
&-right {
right: 0
}
&-top {
position: absolute;
right: 280px;
left: 280px;
height: 80px;
background: blue;
}
&-container {
padding: 80px 270px 0;
height: 100%;
box-sizing: border-box;
&-canvas {
overflow: scroll;
height: 100%;
&__content {
margin: 20px auto;
width: 9999px;
height: 10000px;
background: yellow;
position: relative;
}
}
}
}
.editor-block{
position: absolute;
}
2.构造假数据 实现根据位置渲染内容
我们随便写一些假数据
data.json
{
"container":{
"width":"550",
"height":"550"
},
"blocks":[
{"top":100,"left":100,"zIndex":1,"key":"text"},
{"top":200,"left":200,"zIndex":1,"key":"button"},
{"top":300,"left":300,"zIndex":1,"key":"input"}
]
}
现在我要实现的效果是,
我的容器大小能够变为以上data.json中"container"的大小,并能够将"blocks"中的三个组件渲染到容器中
创建工具类,为组件进行注册
editor-config.jsx
//列表去可以显示所有的物料
//key对应的组件映射关系
import {ElButton,ElInput} from 'element-plus'
function createEditorConfig(){
const componentList =[];
const componentMap ={}
return{
componentList,
componentMap,
register:(component)=>{
componentList.push(component);
componentMap[component.key] = component
}
}
}
export let registerConfig = createEditorConfig();
registerConfig.register({
label:'文本',
preview:()=>'预览文本',
render:()=>'渲染文本',
key:'text'
});
registerConfig.register({
label:'按钮',
preview:()=><ElButton>预览按钮</ElButton>,
render:()=><ElButton>渲染按钮</ElButton>,
key:'button'
});
registerConfig.register({
label:'输入框',
preview:()=><ElInput placeholder='预览输入框'>预览按钮</ElInput>,
render:()=><ElInput placeholder='渲染输入框'>预览按钮</ElInput>,
key:'input'
});
console.log(registerConfig)
此处preview
属性用于预览组件,render
属性用于渲染组件
在App.vue中数据传输
<Editor v-model="state"></Editor>
/.
.
.
./
import data from './data.json'
import { registerConfig as config } from './utils/editor-config'
export default {
components: {
Editor
},
setup() {
const state = ref(data);
console.log(config)
provide('config', config);
return {
state
}
}
}
再次来到editor.jsx中
props: {
modelValue: { type: Object }
},
setup(props) {
const data = computed({
get() {
return props.modelValue
}
})
console.log(data.value)
const containerStyles = computed(() => ({
width: data.value.container.width + 'px',
height: data.value.container.height + 'px'
}))
<div class="editor-container-canvas__content" style={containerStyles.value}>
通过样式绑定,完成从.json到样式的修改
3.渲染组件列表
配置组件对应的映射关系 {preview:xxx,render:xxx}后
在editor.jsx中
const config= inject('config')
return () => <div>
<div class="editor-left">
{/* 根据注册列表 渲染对应的内容 */}
{config.componentList.map(component=>(
<div class="editor-left-item">
<span>{component.label}</span>
<div>{component.preview()}</div>
</div>
))}
</div>
4.渲染组件
editor.jsx
<div class="editor-container-canvas__content" style={containerStyles.value}>
{
(data.value.blocks.map(block=>(
<EditorBlock block={block}></EditorBlock>
)))
}
</div>
editor-block.jsx
import { computed, defineComponent, inject } from "vue";
export default defineComponent({
props: {
block: { type: Object }
},
setup(props) {
const blockStyles = computed(() => ({
top: `${props.block.top}px`,
left: `${props.block.left}px`,
zIndex: `${props.block.zIndex}px`,
}))
const config = inject('config')
console.log(config)
return () => {
const component = config.componentMap[props.block.key];
const RenderComponent = component.render()
return <div class="editor-block" style={blockStyles.value}>
{RenderComponent}
</div>
}
}
})