前端技术日益发展,组件化日益成熟,做为一个前端,天天的工做就是用组件堆砌页面,有没有一种方式能够像CocosCreator,经过组件+脚本绑定的方式来实现咱们的页面和功能,今天咱们就来实现一个提升生产力的工具
可视化拖拽页面编辑器
, 让产品和UI经过拖拽编辑页面,生产本身想要的页面。javascript
技术框架采用Vue3 + Typescript + ElementPluscss
每一个章节下边都会贴出对应commit代码,方便你们对比学习前端
最终效果vue
实现功能:java
预览地址git
vue create visual-editor-vue
复制代码
选择配置以下:github
{
"container": {
"height": 500,
"width": 800
},
"blocks": [
{
"componentKey": "button",
"top": 102,
"left": 136,
"adjustPosition": false,
"focus": false,
"zIndex": 0,
"width": 0,
"height": 0
},
{
"componentKey": "input",
"top": 148,
"left": 358,
"adjustPosition": false,
"focus": false,
"zIndex": 0,
"width": 244,
"height": 0
}
]
}
复制代码
import { computed, defineComponent, ref, watch } from "vue";
// 用jsx封装组件的时候,实现双向数据绑定
export function useModel<T>(getter: () => T, emitter: (val: T) => void) {
const state = ref(getter()) as { value: T };
watch(getter, (val) => {
if (val !== state.value) {
state.value = val;
}
});
return {
get value() {
return state.value;
},
set value(val: T) {
if (state.value !== val) {
state.value = val;
emitter(val);
}
},
};
}
复制代码
useModel用法vue-cli
// modelValue 外部能够用v-model绑定
export const TestUseModel = defineComponent({
props: {
modelValue: { type: String },
},
emits: {
"update:modelValue": (val?: string) => true,
},
setup(props, ctx) {
const model = useModel(
() => props.modelValue,
(val) => ctx.emit("update:modelValue", val)
);
return () => (
<div> 自定义输入框 <input type="text" v-model={model.value} /> </div>
);
},
});
复制代码
import { computed, defineComponent, PropType } from "vue";
import { VisualEditorBlockData } from "./visual-editor.utils";
export const VisualEditorBlock = defineComponent({
props: {
block: {
type: Object as PropType<VisualEditorBlockData>,
},
},
setup(props) {
const styles = computed(() => ({
top: `${props.block?.top}px`,
left: `${props.block?.left}px`,
}));
return () => (
<div class="visual-editor-block" style={styles.value}> 这是一条block </div>
);
},
});
复制代码
App.vue文件json
<template>
<div class="app"> <visual-editor v-model="editorData" /> </div>
</template>
<script lang="ts"> import { defineComponent } from "vue"; import { VisualEditor } from "../src/packages/visual-editor"; export default defineComponent({ name: "App", components: { VisualEditor }, data() { return { editorData: { container: { height: 500, width: 800, }, blocks: [ { top: 100, left: 100 }, { top: 200, left: 200 }, ], }, }; }, }); </script>
复制代码
visual-editor.tsx文件babel
import { computed, defineComponent, PropType } from "vue";
import { useModel } from "./utils/useModel";
import { VisualEditorBlock } from "./visual-editor-block";
import "./visual-editor.scss";
import { VisualEditorModelValue } from "./visual-editor.utils";
export const VisualEditor = defineComponent({
props: {
modelValue: {
type: Object as PropType<VisualEditorModelValue>,
},
},
emits: {
"update:modelValue": (val?: VisualEditorModelValue) => true,
},
setup(props, ctx) {
const dataModel = useModel(
() => props.modelValue,
(val) => ctx.emit("update:modelValue", val)
);
const containerStyles = computed(() => ({
width: `${props.modelValue?.container.width}px`,
height: `${props.modelValue?.container.height}px`,
}));
return () => (
<div class="visual-editor"> <div class="menu">menu</div> <div class="head">head</div> <div class="operator">operator</div> <div class="body"> <div class="content"> <div class="container" style={containerStyles.value}> {(dataModel.value?.blocks || []).map((block, index: number) => ( <VisualEditorBlock block={block} key={index} /> ))} </div> </div> </div> </div>
);
},
});
复制代码
editorData
对象,来进行展现,container来描述画布的大小,block来描述在画布上的每一个组件