前端技術日益發展,組件化日益成熟,做爲一個前端,天天的工做就是用組件堆砌頁面,有沒有一種方式能夠像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來描述在畫布上的每一個組件