Самый подробный учебный материал по bpmn.js во всей сети - пользовательский рендерер

внешний интерфейс
Самый подробный учебный материал по bpmn.js во всей сети - пользовательский рендерер

предисловие

В: Что такое bpmn.js?

"

bpmn.jsЭто набор инструментов рендеринга BPMN2.0 и средство веб-моделирования, которое позволяет выполнять функцию рисования блок-схем во внешнем интерфейсе.

В: Почему я пишу эту серию учебников? 🤔️

"

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

Поскольку это серия статей, она может часто обновляться.Пожалуйста, простите меня, если вы случайно смахнули его, и это не то, что вам нужно😊.

Не просите похвалы👍 не просите сердца❤️ Я просто надеюсь, что смогу вам немного помочь.

Статьи о пользовательском рендерере

Следуя предыдущей главе, мы уже знаем, как настроить панель инструментов (Палитру) слева, а те, кто не понимает, могут перемещаться:"Самый подробный учебный материал по bpmn.js во всей сети - пользовательская палитра".

Но в то же время мы также знаем, что только меняетсяPaletteнедостаточно, потому что нарисованная фигура еще «голая»:

В этой главе давайте рассмотрим, как настроить графику на холсте, то есть добиться настройкиRendererфункция.

Читая, вы узнаете:

Изменить на основе средства визуализации по умолчанию

и обычайPaletteТочно так же давайте рассмотрим простейшую модификацию исходного элемента.

Предварительная подготовка

продолжимLinDaiDai/bpmn-vue-customразработка кейс-проекта.

существуетcomponentsсоздать новую папкуcustom-renderer.vueфайл и настройте маршрут «пользовательский рендерер».

существуетcomponents/customсоздать новую папкуCustomRenderer.jsфайл для настройкиrenderer.

существуетcomponentsсоздать новую папкуutilsОдновременно создайте новую папкуutil.jsфайл, используемый для размещения некоторых общедоступных методов и конфигурации.

записыватьCustomRenderer.vueкод

из-заbpmn.jsИзмените существующие элементы, поэтому сначала мы можем сначалаBaseRendererЭтот класс импортируется, а затем пусть наш пользовательскийrendererунаследовать это:

import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer' // 引入默认的renderer
const HIGH_PRIORITY = 1500 // 最高优先级
export default class CustomRenderer extends BaseRenderer { // 继承BaseRenderer
    constructor(eventBus, bpmnRenderer) {
        super(eventBus, HIGH_PRIORITY)
        this.bpmnRenderer = bpmnRenderer
    }

    canRender(element) {
        // ignore labels
        return !element.labelTarget
    }

    drawShape(parentNode, element) { // 核心函数就是绘制shape
        const shape = this.bpmnRenderer.drawShape(parentNode, element)
        return shape
    }

    getShapePath(shape) {
        return this.bpmnRenderer.getShapePath(shape)
    }
}

CustomRenderer.$inject = ['eventBus', 'bpmnRenderer']

Приведенный выше код 👆 очень прост, я думаю, его сможет понять каждый.

Примечание. Здесь есть небольшая яма, на которую следует обратить внимание, т.HIGH_PRIORITYЕго нельзя удалить, иначе вы обнаружите, что он не будет выполнять следующиеdrawShpeфункция

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

Да, недостаточно просто выполнить вышеперечисленные действия, главное, как написатьdrawShapeСюда.

записыватьdrawShapeкод

Сначала мы можем создатьutils/util.jsНапишите этот код под файлом:

// util.js
const customElements = ['bpmn:Task']

export { customElements }

То есть создатьcustomElementsМассив , а затем экспортированный, почему в массиве только один элементbpmn:Task?🤔️

Это потому, что в предыдущем случае я создалlindaidai-taskТипbpmn:TaskТип.

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

Вы даже можете сделать некоторую настройку:

const customElements = ['bpmn:Task'] // 自定义元素的类型
const customConfig = { // 自定义元素的配置(后面会用到)
    'bpmn:Task': {
        'url': 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png',
        'attr': { x: 0, y: 0, width: 48, height: 48 }
    }
}

export { customElements, customConfig }

впусти насCustomRenderer.jsиспользуйте и напишите его в:

import { customElements, customConfig } from '../utils/util'

...
    drawShape(parentNode, element) {
      const type = element.type // 获取到类型
      if (customElements.includes(type)) { // or customConfig[type]
        const { url, attr } = customConfig[type]
        const customIcon = svgCreate('image', { // 在这里创建了一个image
          ...attr,
          href: url
        })
        element['width'] = attr.width // 这里我是取了巧, 直接修改了元素的宽高
        element['height'] = attr.height
        svgAppend(parentNode, customIcon)
        return customIcon
      }
      const shape = this.bpmnRenderer.drawShape(parentNode, element)
      return shape
    }
...

Как видите, способ заставить страницу отображать нужный эффект — использоватьsvgCreateметод созданияimageи добавлен к родительскому узлу.

экспортировать и использоватьCustomRenderer

такая же настройкаrendererНеобходимо экспортировать, чтобы использовать, модифицироватьcustom/index.jsдокумент:

import CustomPalette from './CustomPalette'
import CustomRenderer from './CustomRenderer'

export default {
    __init__: ['customPalette', 'customRenderer'],
    customPalette: ['type', CustomPalette],
    customRenderer: ['type', CustomRenderer]
}

Уведомление:__init__именование атрибутов вcustomRendererВсе они имеют фиксированное написание и не могут быть изменены, иначе не будет никакого эффекта.

если ты читал это раньшеcustom-palette.vueЕсли это так, вы знаете, что можете применить его прямо на странице:

<!--custom-renderer.vue-->
<script>
...
import customModule from './custom'
...
this.bpmnModeler = new BpmnModeler({
...
    additionalModules: [
        // 左边工具栏以及节点
        propertiesProviderModule,
        // 自定义的节点
        customModule
    ]
})

Примечание: В случае с проектом для удобства демонстрации вcustom-paletteПредставлен вImportJS/onlyRenderer.js, а рассмотренный выше случай основан на введенииcustom/index.jsДля того, чтобы объяснить, это надо самому понять, как различать.

На данный момент вы можете увидеть эффект при открытии страницы, типbpmn:TaskУзел отображается как пользовательский «золотой строительный блок» 😝:bpmnCustom9.png

Полностью настраиваемый рендерер

полностью настраиваемыйRendererозначает, что первоначальное использованиеnew BpmnModelerСпособ создания холста изменился на использованиеnew CustomModelerсоздавать.

Эта часть находится в"Самый подробный учебный материал по bpmn.js во всей сети - пользовательская палитра"Это объясняется очень подробно, поэтому я не буду вдаваться в подробности.

Также вcustomModeler/customсоздать папку вcustomRender.jsфайл, а затем напишите следующий код:

/* eslint-disable no-unused-vars */
import inherits from 'inherits'

import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer'

import {
    append as svgAppend,
    create as svgCreate
} from 'tiny-svg'

import { customElements, customConfig } from '../../utils/util'
/**
 * A renderer that knows how to render custom elements.
 */
export default function CustomRenderer(eventBus, styles) {
    BaseRenderer.call(this, eventBus, 2000)

    var computeStyle = styles.computeStyle

    this.drawCustomElements = function(parentNode, element) {
        console.log(element)
        const type = element.type // 获取到类型
        if (customElements.includes(type)) { // or customConfig[type]
            const { url, attr } = customConfig[type]
            const customIcon = svgCreate('image', {
                ...attr,
                href: url
            })
            element['width'] = attr.width // 这里我是取了巧, 直接修改了元素的宽高
            element['height'] = attr.height
            svgAppend(parentNode, customIcon)
            return customIcon
        }
        const shape = this.bpmnRenderer.drawShape(parentNode, element)
        return shape
    }
}

inherits(CustomRenderer, BaseRenderer)

CustomRenderer.$inject = ['eventBus', 'styles']

CustomRenderer.prototype.canRender = function(element) {
    // ignore labels
    return !element.labelTarget;
}

CustomRenderer.prototype.drawShape = function(p, element) {
    return this.drawCustomElements(p, element)
}

CustomRenderer.prototype.getShapePath = function(shape) {
    console.log(shape)
}

Изменять непосредственно в цепочке прототиповdrawShapeметода достаточно.

тогда не забудьтеcustomModeler/custom/index.jsчтобы экспортировать его.

Тег label настраивается под элементом

Из-за вопроса, поднятого небольшим партнером в области комментариев: какlabelПользовательский ярлык ниже элемента?

Итак, Лин был ошеломлен, и я вернулся и потратил некоторое время на его изучение.labelЭтикетка.

первыйlabelЭтикетка на самом делеxmlИмя на каждой этикетке вnameсвойства, как показано ниже:

начальный узел иlindaidai-taskв целомnameсвойства, но вbpmn:StartEventможет этоlabelотображается, потому что естьbpmndi:BPMNLabelТег.

В результате график выглядит так:

bpmn11.png
bpmn11.png

Итак, как мы поместимlabelпокажи это?

Сначала положимShapeРаспечатайте и посмотрите:bpmn12.png

можно найти вbusinessObjectесть одинnameАтрибуты...

В этом случае мы, безусловно, можемdrawShapeполучить этоnameсвойства, которые можно использовать позжеsvgCreateМетод добавляет метку текстового типа к родительскому узлу.

// CustomRenderer.js

import { hasLabelElements } from '../../utils/util'

drawShape(parentNode, element) {
    const type = element.type // 获取到类型
    if (customElements.includes(type)) { // or customConfig[type]
        const { url, attr } = customConfig[type]
        const customIcon = svgCreate('image', {
            ...attr,
            href: url
        })
        element['width'] = attr.width // 这里我是取了巧, 直接修改了元素的宽高
        element['height'] = attr.height
        svgAppend(parentNode, customIcon)
        // 判断是否有name属性来决定是否要渲染出label
        if (!hasLabelElements.includes(type) && element.businessObject.name) {
            const text = svgCreate('text', {
                x: attr.x,
                y: attr.y + attr.height + 20, // y取的是父元素的y+height+20
                "font-size": "14",
                "fill": "#000"
            })
            text.innerHTML = element.businessObject.name
            svgAppend(parentNode, text)
            console.log(text)
        }
        return customIcon
    }
    const shape = this.bpmnRenderer.drawShape(parentNode, element)
    return shape
}

потому что некоторые элементы имеютlabelатрибуты, такие какbpmn:StartEvent, так что нет необходимости повторно рендерить, поэтому я используюutil.jsдобавил одинhasLabelElementsмножество:

// utils/util.js
const hasLabelElements = ['bpmn:StartEvent', 'bpmn:EndEvent'] // 一开始就有label标签的元素类型

Я хотел пройти мимоelement.labels.length<=0чтобы отфильтровать началоlabelЭлемент метки, но обнаружил, что он недоступен на этапе рендерингаlabels, поэтому длина всегда будет0, просто определитеhasLabelElementsПриходите судить 😓...

Эффект от открытия страницы следующий:

bpmn13.png
bpmn13.png

Похоже, это сработало! Молодец! 😄

но когда я дважды щелкаю, хочу перейти к редактированиюlabelПри записи появляется такой эффект:

Он создал новое поле ввода прямо поверх моего исходного графика...

Эх😅... На самом деле, я не придумал никакого хорошего способа решить эту проблему. Здесь я предлагаю решение, которое кажется возможным:При двойном щелчке по элементуtextбыть удалены или егоinnerHTMLУстановить как''.

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

Если нет, вы можете изменить его глобальноdjs-direct-editing-parentДля стиля этого класса также можно прикрыть текст ниже... Конечно, я чувствую, что это не очень хороший способ. существуетapp.cssнаписать в:

.djs-direct-editing-parent {
    top: 130px!important;
    width: 60px!important;
}

Суммировать

Вышеупомянутый подход в основном используется дляsvgCreateсоздаватьtextэлемент добавлен вparentNodeв, на самом делеbpmn.jsиспользовал многоting-svgЯ никогда раньше не сталкивался с такими вещами, а потом я также узнал, ища информацию.svgCreateиспользование...

Волна научно-популярного это хорошо, ха-ха 😄:Основы SVG

послесловие

В приведенных выше 👆 случаях используется один и тот же проект🦐

Git-адрес кейса проекта:LinDaiDai/bpmn-vue-customЕсли вам это нравится, пожалуйста, дайтеStar🌟 Да, спасибо😊

Полный каталог серии можно найти здесь:"Самый подробный учебник по bpmn.js во всей сети"

Рекомендации по сериалу:

"Самый подробный учебник по bpmn.js во всей сети - основы"

"Самый подробный учебный материал по bpmn.js во всей сети - http запрос"

"Самый подробный учебный материал по bpmn.js во всей сети - мероприятия"

"Самый подробный учебник по bpmn.js во всей сети - contextPad"

"Самый подробный учебник по bpmn.js во всей сети - редактирование и удаление узлов"