Простая разработка блок-схемы на основе vue

внешний интерфейс SVG Vue.js Vuex

Серьезное промедление, с одной стороны этот проектный модуль чисто личное развлечение. С другой стороны, в блок-схему вовлечено довольно много вещей, и на этот раз я представлю только несколько простых частей. После столь долгого промедления теперь я, наконец, должен стиснуть зубы и написать «псевдоучебник» на основе блок-схемы vue + svg. В первый раз, когда вы уродливы, пожалуйста, слегка распылите.

Введение модуля

адрес проекта

图片预览
Предварительный просмотр изображения

В целях изучения vue, а не совместимости, этот проект рассматривает только современные браузеры (Google), пожалуйста, простите меня за некоторые проблемы с совместимостью.

Разработка этого модуля связана с простыми требованиями к блок-схемам (чистая реализация пользовательского интерфейса, отсутствие бизнес-логики на данный момент), и структура каталогов, сгенерированная vue-cli, здесь повторяться не будет (см.этоили погуглите сами).

Реально используемый в проекте стек технологий: SVG + vue + vuex

Функции:

  • Масштаб холста
  • Узлы (старт, база, суд и т.д.) добавлять, удалять
  • Связь между узлами (линия/полилиния)
  • добавление текста
  • Импорт графики SVG извне
  • Отменить и повторить

Масштаб холста

Учитывая, что макет должен быть последовательным после масштабирования холста, здесь измененоtransform: scale(); transform-origin: ;Для этого узел позиционируется относительно родительского слоя.

TODO: оптимальное решение для масштабирования SVG?

Связанный с узлом

Вот моя простая идея:

Поскольку здесь нет бизнес-логики, я упростил блок-схему до开始 基础 判断3 базовых компонента (на основе SVG).

как:

<template>
    <!-- 开始 -->
    <ellipse v-bind="style"></ellipse>
    <!-- 基础 -->
    <rect v-bind="style"></rect>
    <!-- 判断 -->
    <path v-bind="style"></path>
</template>

Давайте поговорим об оценке этого компонента (сложные формы, которые могут появиться на более позднем этапе, реализуются с помощью путей), и соответствующие формы обычно напрямую экспортируются программным обеспечением ИИ.

Панель инструментов слева и та же графика на холсте происходят из одного и того же компонента, поэтому есть два стиля, а именноdefaultStyleиdrawStyle. Ранее считалось, что если графика блок-схемы сложна и изменчива, то нет необходимости искусственно задавать этот режим для каждого компонента. Опять же, есть аналогичные проблемы с импортом SVG. Потому что, если размер графики не определен, в дополнение к поддержке размера модификации графики, в противном случае на холсте будет отображаться графика разных размеров. (Жаль, что в этой области не сделано прорыва, но это будет направление будущего совершенствования.)

Первым решением было масштабирование, т. е. однородное заставление графики на панели инструментов формировать пропорциональное соотношение масштабирования с графикой, перетаскиваемой на холст. Однако этот метод приведет к тому, что ход будет масштабироваться из года в год, а это не то, что нам нужно.

Поэтому в настоящее время используется метод записи на кристалл.

Примечание: в SVGellipseПозиционирование относится к центральной точке, а прямоугольное позиционирование — к верхнему левому углу.

TODO Есть ли способ установить исходную точку позиционирования каждого компонента в качестве центральной точки компонента.

Рендеринг узла

С точки зрения рендеринга узлов, поскольку графика раньше использовалась как компоненты,component + isспособ рендеринга графики. В то же время он также визуализируется на основе данных, то есть данные определяют представление.

 <component v-for="(item,index) in nodeData" :is="item.type" :id="item.id" v-node inDraw></component>

Когда перетаскивание узлов включает зеркальное отображение узлов:

<component :is="selNodeId" :transform="selNodeInfo.transform" v-if="isDragging" inDraw></component>

Код через поезд

новый узел

drag dropформа. Преимущество использования этого метода заключается в том, что нет необходимости моделировать события перетаскивания. То есть зеркалирование или что-то не нужно делать самому. (Перетаскивание узлов в холсте использует собственную симуляцию)

Код через поезд

Операции над узлами выполняются в виде директив (прямое манипулирование DOM). Это заставляет меня задаться вопросом, подходит ли этот тип проекта для использования структуры класса vue.С точки зрения эффективности разработки vue по-прежнему является первым выбором, но с точки зрения производительности, поскольку нет глубоких исследований, я не имеют права говорить.

Моделирование сцены TODO, предположим, нам нужно переместить узлы на холсте, получить узлы через el директив, а затем передатьel.onmousemoveЧтобы изменить соответствующий перевод в данных, чтобы изменить положение. Изменение данных для управления представлением здесь является нашим обычным способом, но я не могу понять, чтоel.onmousemoveЗдесь отражено изменение производительности двусторонней привязки данных, реализованной данными.

Я предполагаю, что улучшение производительности, вызванное diff, ценно только тогда, когда задействовано несколько зависимостей. Например, у меня есть список listData, который существует в данных, а затем я связываю listData в нескольких видимых местах. В настоящее время манипулирование listData лучше, чем непосредственное манипулирование DOM.

После прочтения введения связанного vitrualDOM, через diff можно манипулировать только измененным DOM.

Получить размер SVG

Получить использование размера узлаgetBoundingClientRect, в то же время из-за ранее выполненной функции масштабирования при получении размера узла необходимо разделить его на коэффициент масштабирования, чтобы получить правильное значение.

let obj = el.getElementsByTagName('g')[0]
let w = obj.getBoundingClientRect().width / _this.drawStyle.zoomRate
let h = obj.getBoundingClientRect().height / _this.drawStyle.zoomRate
let wh = {
    width: w,
    height: h
}

Код через поезд

Сводка по работе узла

Поскольку отображение узлов основано на NodeData, добавления и удаления на самом деле являются добавлениями и удалениями в NodeData.

основной код

Связанные с подключением

Соединение на самом деле используется только для svglineиpolyline, которые здесь похожи на узлы, все существуют в виде компонентов и используют lineData для управления представлением соединения. Следовательно, добавление или удаление конечного соединения также является операцией над данными.

Отображение точек подключения

Во-первых, это положение точки ссылки (зеленое положение дальней точки). Предыдущая блок-схема, основанная на jquery, использовала макет div, но теперь использовать svg сложнее. Поскольку svg не может использовать положение, его нельзя позиционировать на основе текущий элемент. Используется метод почвы, то есть положение 4-х точек получается динамически с использованием размера графики + заполнения. В течение этого периода, поскольку между 4 узлами соединения и узлами графа есть промежутки, когда наведение мыши не находится на графе или узле, событие не может быть инициировано. Вот моделирование области для решения. Исходя из личного опыта, эта часть кода полностью императивна. не распылять

Код через поезд
Код через поезд 2

обработка соединения

Существует два случая соединения между узлами: (конкретные детали от mousedown до mouseup здесь не описываются, вы можете увидетьздесь)

На самом деле, многие люди говорят, что алгоритмы могут решить много ненужного кода. К сожалению, я не уловил истинного значения этого, например, графических компонентов раньше и разных строк, которые следуют. На самом деле его можно получить по определенному алгоритму. Я буду говорить здесь только о самом глупом методе.Когда я вырасту, чтобы говорить с алгоритмами, я вернусь и позабочусь об этой статье.

  • линия прямая линия

Прямая линия — это не что иное, как две координаты точки, которые отображаются линией в svg. В настоящее время это зависит от требований проекта.Мы предполагаем, что самый простой случай - это начальная или конечная точка наиболее связанных четырех точек соединения, упомянутых выше.
Ниже приведены координаты положения 4 точек на графике расчета.


computeLine(direction, obj) { // low不止一点点
    let { top, left, width, height } = obj
    let w = width / 2
    let h = height / 2
    switch (direction) {
    case 't':
        top = top - h
        break
    case 'b':
        top = top + h
        break
    case 'l':
        left = left - w
        break
    case 'r':
        left = left + w
        break
    default:
        break
    }
    return { top, left }
}
  • ломаная линия

Случаев рассмотрения полилинии относительно больше, поскольку здесь используется полилиния, ее точки задаются так.points="125,96 183.5,96 183.5,399 242,399"

В это время символы обычно преобразуются в массивы или объекты, с которыми проще работать. Начальная и конечная точки, участвующие в ломаной линии, совпадают с точками описанной выше прямой линии. Разница заключается в положении средней линии. Если не учитывать сложную ситуацию,

Вообще можно разделить на два типа, вверх и вниз, влево и вправо. Нужную полилинию можно получить, получив положение середины начальной и конечной точек для определения средней линии. Код такой: (Все по-простому и грубо.)

computePolyLine(start, end, direction) {
    let startPoint = {
    x: +(start.split(',')[0]),
    y: +(start.split(',')[1])
    }
    let endPoint = {
    x: +(end.split(',')[0]),
    y: +(end.split(',')[1])
    }
    let m1, m2
    switch (direction) {
    case 't':
    case 'b':
        let mY = startPoint.y + (endPoint.y - startPoint.y) / 2
        m1 = {
        x: startPoint.x,
        y: mY
        }
        m2 = {
        x: endPoint.x,
        y: mY
        }
        break
    case 'l':
    case 'r':
        let mX = startPoint.x + (endPoint.x - startPoint.x) / 2
        m1 = {
        x: mX,
        y: startPoint.y
        }
        m2 = {
        x: mX,
        y: endPoint.y
        }
        break
    default:
        break
    }
    return `${startPoint.x},${startPoint.y} ${m1.x},${m1.y} ${m2.x},${m2.y} ${endPoint.x},${endPoint.y}`
}

Сводка по подключению

Узлы и соединения похожи в рендеринге и обработке операций.Есть два места, которые не уверены, является ли это наилучшей практикой.В одном нужно использовать компонент + is для рендеринга компонентов, а в другом-использовать директивы для управления DOM. Форма расчета соединения также немного проще, что требует некоторого времени для роста. Это немного натянуто, поэтому вот краткое резюме.Независимо от того, какой метод соединения используется, все, что нам нужно сделать, это правильно получить положение соответствующей точки, а затем изменить данные для управления представлением. Тем не менее, это также скачок в том, чтобы уметь обобщать алгоритмы в различных сложных ситуациях.

Текстовое добавление узлов и соединений

Принцип добавления текста для узлов и соединений одинаков, вот настройкаcontenteditableКогда contenteditable имеет значение true, структура html автоматически добавляет текстовые узлы и становится редактируемой. Для получения более подробной информации, пожалуйста, обратитесь к Чжан Синьсюйэто

Кстатиpointer-eventsЭто свойство css используется в этом модуле в двух местах. Один из них — добавить этот фрагмент текста, а также раздел панели инструментов заголовка.

CSS-свойстваpointer-eventsПозволяет автору контролировать, когда определенные графические элементы становятся целью событий мыши. Если этот атрибут не указан, содержимое SVG ведет себя как visiblePainted.

В дополнение к указанию, что элемент не должен быть целью событий мыши, значение none также указывает, что событие мыши проходит через элемент и направляется на элемент, расположенный ниже элемента.

Подробнее оpointer-events

Чжан Синьсюй
MDN

Хотя реализована функция редактирования текста TODO, в этой части много багов и она не идеальна.

Импорт SVG извне

Здесь также используется функция Drop HTML5, а для отображения используются изображения svg. Реализация перетаскивания относительно проста:

dropHandle (e) {
    let reader = new FileReader()
    let file = e.dataTransfer.files[0]
    reader.onload = (e) => {
        this.userImages.push(e.target.result)
    }
    reader.readAsDataURL(file)
},
dragoverHandle () {
},
dragstart (imgSrc) {
    event.dataTransfer.setData('URL', imgSrc)
}

Здесь следует отметить, что@drop.stop.prevent="dropHandle" @dragover.stop.prevent="dragoverHandle"Чтобы предотвратить появление всплывающих окон и предотвратить поведение браузера по умолчанию.

Еще одна вещь, которую следует отметить, это то, чтоdataTransfer.getData()Проблема в том, что данные не могут быть получены в dragover, dragenter и dragleave

Согласно стандарту W3C, хранилище данных перетаскивания имеет три режима: режим чтения/записи, режим только для чтения и защищенный режим.деталь

Read/write mode
Режим чтения/записи, используемый в событии dragstart для добавления новых данных в хранилище данных перетаскивания.

Read-only mode
Режим только для чтения, используемый в событии перетаскивания, может считывать перетаскиваемые данные, но не может добавлять новые данные.

Protected mode
Защищенный режим, используемый во всех остальных событиях, список данных можно перечислить, но сами данные недоступны и нельзя добавить новые данные.

глубоко

Отменить и повторить

Эта функция по сути не доработана, потому что vuex генерирует моментальные снимки состояния ленивым способом, что не рекомендуется для производственных сред.

Основной принцип состоит в том, чтобы инициировать обратный вызов через vuex, отправляющий более высокий уровень (мутацию). Используйте это для записи снимков состояния

Код через поезд

Суммировать

Этот проект относится к vue+vuex начального уровня, но в нем не говорится о том, как использовать vue или vuex, потому что это на самом деле очень четко указано в официальных документах. Этот проект также просто использует общие методы, такие как пользовательские инструкции vue и MiXin. Например, компоненты функции рендеринга vue, которые не рассматриваются в этой статье, вот краткое описание опыта использования Компонент рендеринга больше подходит для сильно настраиваемых компонентов (логика изменения более сложная). Поскольку некоторые простые компоненты на самом деле больше подходят для использования в виде tempalte, хотя использование Render может повысить определенную производительность (уменьшив шаг от tempalte до рендеринга), многие существующие компоненты, такие как синхронизация, недоступны в компонентах рендеринга (необходимо реализовать это сами). При использовании vuex необходимо обратить внимание на проблему ссылочного адреса объекта. То есть, чтобы избежать потенциального влияния между данными. (Хотя сам vuex также избегает этой проблемы) Вы можете узнатьimmutable.

В этом руководстве в основном описывается, как реализовать простую блок-схему на основе Vue.Больше размышлений о том, какой проект больше подходит для использования этой структуры шаблона MVVM и как использовать значение VitrualDOM. На самом деле пункты предыдущих глав можно использовать для более глубокого обсуждения многих технических вопросов, и в будущем появится возможность углубиться в них.