Визуальный редактор страниц с перетаскиванием 1

Vue.js
Визуальный редактор страниц с перетаскиванием 1

Фронтенд технология развивается день ото дня, а компонентизация становится все более и более зрелой.В качестве фронтенда наша ежедневная работа состоит в том, чтобы складывать страницы с компонентами.Есть ли способ реализовать наши страницы и функции через привязку компонент + скрипт как 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Объект для отображения, контейнер для описания размера холста, блок для описания каждого компонента на холсте

Третья часть кода

Полный код GitHub

Следующий раздел: меню левого компонента, рендеринг перетаскивания компонентов, а также выбор и перемещение компонентов.