Фронтенд технология развивается день ото дня, а компонентизация становится все более и более зрелой.В качестве фронтенда наша ежедневная работа состоит в том, чтобы складывать страницы с компонентами.Есть ли способ реализовать наши страницы и функции через привязку компонент + скрипт как CocosCreator?Сегодня мы внедряем инструмент повышения производительности
可视化拖拽页面编辑器, Позвольте продукту и пользовательскому интерфейсу отредактировать страницу, перетащив ее, чтобы создать нужную страницу.
Техническая структура использует Vue3 + Typescript + ElementPlus.
Соответствующий код коммита будет размещен под каждой главой, чтобы всем было удобно сравнивать и изучать
окончательный эффект
Функция реализации:
- Структура главной страницы: список необязательных компонентов слева, холст промежуточного контейнера и свойства, определяемые компонентами редактирования, справа
- Перетащите компоненты из меню в контейнер;
- одиночный выбор, множественный выбор;
- Компоненты в контейнере можно перетаскивать и перемещать;
- Перетащите компонент, чтобы отрегулировать ширину и высоту;
- Компонент перетаскивает обводку, чтобы отобразить вспомогательную линию;
- Кнопки и команды панели действий
- отменить повторить;
- Импорт и экспорт;
- верх, низ;
- удалить, очистить;
- значение привязки компонента;
- Настройте поведение компонента с помощью слотов с заданной областью действия на основе его идентификатора.
адрес предварительного просмотра
1. Построение проекта и макет страницы
Сгенерировать проект через vue-cli
vue create visual-editor-vue
- Выберите ручную настройку
Выберите конфигурацию следующим образом:
- Выберите версию vue3.x
- На этом шаге выберите y, используйте jsx для написания компонентов, вам нужно добавить соответствующий плагин babel
Далее, давайте реализуем базовую раскладку слева-центр-справа.
- Список компонентов размещен в левой строке меню
- Посередине находится холст и панель инструментов, используемые для редактирования страницы предварительного просмотра.
- Правая сторона — это свойства компонента, отображаемые после выбора компонента.
Первая часть кода:базовая компоновка
Во-вторых, проектирование структуры данных и реализация двусторонней привязки.
проектирование структуры данных
- Определите структуру данных следующим образом
- container представляет контейнер холста
- блоки представляют собой компоненты, помещенные в контейнер
- Каждый блок представляет компонент, включая положение типа, ширину и высоту, выбранное состояние и другую информацию о компоненте.
- Холст принимает абсолютное позиционирование, а элементы внутри определяются сверху и слева.
{
"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
}
]
}
Реализация двусторонней привязки данных
- Компонент написан на синтаксисе jsx в vue3, и ему необходимо реализовать механизм двусторонней привязки данных, для обработки двусторонней привязки данных используется useModel.
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);
}
},
};
}
использование модели использования
// 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>
);
},
});
Вторая часть кода
3. Блочный рендеринг
- Новый компонент блока визуального редактора
- блок для представления элемента компонента, отображаемого на холсте
- блок отображается с текстом первым
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>
);
},
});
- Передайте определенные данные в редактор с помощью v-model
Файл App.vue
<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
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Объект для отображения, контейнер для описания размера холста, блок для описания каждого компонента на холсте