ThingsKit开发指南-前段组态开发指南(二次开发指南)

组态界面介绍

💡提示

组态开发的内容包括下面界面中的元件库开发、画布中的节点开发以及元件的数据绑定面板开发。

元件库面板

ThingsKit开发指南-前段组态开发指南(二次开发指南)

画布

ThingsKit开发指南-前段组态开发指南(二次开发指南)

元件数据绑定面板

ThingsKit开发指南-前段组态开发指南(二次开发指南)

开发步骤

1/3: 元件库面板开发

一、进入项目根目录下的public/webapp/js/diagramly/sidebar/thingskit目录

二、创建Sidebar-ExampleComponent.js文件

(function () {
  Sidebar.prototype.addThingsKitExampleComponentsPalette = function () {
    this.addExampleComponentsPalette()
  }

  const { COMPONENT_KEY, CATEGORY } = window.CellAttributeKeyEnum

  // 组件key 不能重复
  const dt = 'exampleComponents'

  Sidebar.prototype.addExampleComponentsPalette = function () {

    function createUserObject(attribute) {
      attribute = Object.assign({ [COMPONENT_KEY]: '', [CATEGORY]: '' }, attribute || {})
      var xmlElement = mxUtils.createXmlDocument().createElement('UserObject');

      Object.keys(attribute).forEach(key => {
        xmlElement.setAttribute(key, attribute[key]);
      })
      return xmlElement
    }

    this.setCurrentSearchEntryLibrary(dt);

    this.addPaletteFunctions(
      // 面板key
      dt,
      // 组件类别面板名称
      '示例组件',
      // 是否默认展开
      true,
      [
        // Example 示例组件
        this.createVertexTemplateEntry(
          // 初始化样式
          "text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;",
          // 初始化宽度
          100,
          // 初始化高度
          40,
          // 初始化内容
          createUserObject({ label: 'Example', [COMPONENT_KEY]: 'ExampleComponent', [CATEGORY]: 'Example' }),
          // 标题,设置值为组件库面板中的显示标题
          "Example Component",
          // 是否显示label
          null,
          // 是否显示标题
          !0,
          // 标签tags
          "Example example"
        ),
      ]
    );

    this.setCurrentSearchEntryLibrary();
  };

})();

💡提示

  1. dt 为元件库的key 不能重复。
  2. 设置初始化内容时通过 createUserObject 方法创建初始化内容时需要设置label(组件渲染内容)COMPONENT_KEY(组件key)CATEGORY(组件类别key)三个属性。

三、进入public/webapp/js/diagramly/Devel.js文件

// 示例组件
mxscript(drawDevUrl + 'js/diagramly/sidebar/thingskit/Sidebar-ExampleComponent.js');

💡提示

在文件的最后引入Sidebar-ExampleComponent.js文件

四、进入public/webapp/js/diagramly/sidebar/Sidebar.js文件。

// 将面板key设置到默认入口中
Sidebar.prototype.defaultEntries = 'exampleComponents;basicComponents;...'

// 增加新增的面板配置
Sidebar.prototype.configuration = [
  { id: 'exampleComponents' },
  // ... other code
]

// 调用增加的组件面板
Sidebar.prototype.initPalettes = function () {
  // ... other code
  // 示例组件
	this.addThingsKitExampleComponentsPalette();
  // 基础元件
	this.addThingsKitBasicComponentsPalette();
  // ... other code
}

💡提示

  1. 找到 Sidebar.prototype.initPalettes 方法,在此方法的中调用addThingsKitExampleComponentsPalette 方法。

五、新增成功后效果图

ThingsKit开发指南-前段组态开发指南(二次开发指南)

2/3:组件内容

一、使用原始逻辑绘制内容

(function () {
  Sidebar.prototype.addThingsKitExampleComponentsPalette = function () {
    this.addExampleComponentsPalette()
  }

  const { COMPONENT_KEY, CATEGORY } = window.CellAttributeKeyEnum

  // 组件key 不能重复
  const dt = 'exampleComponents'

  Sidebar.prototype.addExampleComponentsPalette = function () {

    function createUserObject(attribute) {
      attribute = Object.assign({ [COMPONENT_KEY]: '', [CATEGORY]: '' }, attribute || {})
      var xmlElement = mxUtils.createXmlDocument().createElement('UserObject');

      Object.keys(attribute).forEach(key => {
        xmlElement.setAttribute(key, attribute[key]);
      })
      return xmlElement
    }

    this.setCurrentSearchEntryLibrary(dt);

    this.addPaletteFunctions(
      // 面板key
      dt,
      // 组件类别面板名称
      '示例组件',
      // 是否默认展开
      true,
      [
        // Example 示例组件
        this.createVertexTemplateEntry(
          // 初始化样式 html=1使用html语法进行渲染
          "text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;",
          // 初始化宽度
          200,
          // 初始化高度
          80,
          // 设置渲染内容
          createUserObject({
            label: `
        <table border="1" style="border-collapse: collapse;">
          <tr style="background-color: #a7c942; color: #fff;">
            <th>Title 1</th>
            <th>Title 2</th>
            <th>Title 3</th>
          </tr>
          <tr>
            <td>Value 1</td>
            <td>Value 2</td>
            <td>Value 3</td>
          </tr>
        </table>`.replace(/\n/g, ''), [COMPONENT_KEY]: 'ExampleComponent', [CATEGORY]: 'Example'
          }),
          // 标题,设置值为组件库面板中的显示标题
          "Example Component",
          // 是否显示label
          true,
          // 是否显示标题
          !0,
          // 标签tags
          "Example example"
        ),
      ]
    );

    this.setCurrentSearchEntryLibrary();
  };

})();

💡提示

改变 createUserObject 中的label属性,使用html语法绘制组件内容。

效果图

ThingsKit开发指南-前段组态开发指南(二次开发指南)

二、使用vue绘制组件内容

(一) 修改Sidebar-ExampleComponent.js文件

(function () {
  Sidebar.prototype.addThingsKitExampleComponentsPalette = function () {
    this.addExampleComponentsPalette()
  }

  const { COMPONENT_KEY, CATEGORY } = window.CellAttributeKeyEnum

  // 组件key 不能重复
  const dt = 'exampleComponents'

  Sidebar.prototype.addExampleComponentsPalette = function () {

    function createUserObject(attribute) {
      attribute = Object.assign({ [COMPONENT_KEY]: '', [CATEGORY]: '' }, attribute || {})
      var xmlElement = mxUtils.createXmlDocument().createElement('UserObject');

      Object.keys(attribute).forEach(key => {
        xmlElement.setAttribute(key, attribute[key]);
      })
      return xmlElement
    }

    this.setCurrentSearchEntryLibrary(dt);

    this.addPaletteFunctions(
      // 面板key
      dt,
      // 组件类别面板名称
      '示例组件',
      // 是否默认展开
      true,
      [
        // Example 示例组件
        this.createVertexTemplateEntry(
          // 初始化样式 设置图形处理逻辑
          "shape=mxgraph.thingskit/exampleComponent;text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;",
          // 初始化宽度
          200,
          // 初始化高度
          80,
          // 初始化内容
          createUserObject({ label: ``, [COMPONENT_KEY]: 'ExampleComponent', [CATEGORY]: 'Example' }),
          // 标题,设置值为组件库面板中的显示标题
          "Example Component",
          // 是否显示label
          true,
          // 是否显示标题
          !0,
          // 标签tags
          "Example example"
        ),
      ]
    );

    this.setCurrentSearchEntryLibrary();
  };

})();

💡提示

  1. 设置样式,追加 shape=mxgraph.thingskit/exampleComponents 属性。
  2. 设置 createUserObject 中的 label 属性为,不为空时将出现内容重叠的现象

(二) 创建 mxExampleComponent.js 文件

// 创建目录
// public/webapp/shapes/thingskit/mxExampleComponent.js
function mxExampleComponent(bounds, fill, stroke, strokewidth) {
  mxShape.call(this);
  this.bounds = bounds;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = (strokewidth != null) ? strokewidth : 1;
}

mxUtils.extend(mxExampleComponent, mxShape)

mxExampleComponent.prototype.customProperties = [
  
]

// 与Sidebar-ExampleComponent.js中的style设置的key相同
mxExampleComponent.prototype.shapeKey = 'mxgraph.thingskit/exampleComponent'

mxExampleComponent.prototype.paintVertexShape = function (c, x, y, w, h) {
  this.paintBackground(c, x, y, w, h)
  this.foreground(c, x, y, w, h)
}

mxExampleComponent.prototype.paintBackground = function (svgCanvas, x, y, w, h) {
  svgCanvas.rect(x, y, w, h);
  svgCanvas.fillAndStroke()
}

mxExampleComponent.prototype.foreground = function (svgCanvas, x, y, w, h) {
  var cell = this.state.cell

  // status值为tooltip edit tooltip
  var status = getCellStatus(this.state.view.graph)
  if (status === 'sidebar') {
    // 设置图片
    svgCanvas.image(...this.getThumbSize(x, y, w, h), IMAGE_PATH + '/thingskit/xxx.svg')
  } else {
    createComponent(this, svgCanvas, x, y, w, h)
    setContainerSize(this, svgCanvas, x, y, w, h)
  }
}

mxExampleComponent.prototype.configureCanvas = function (a, b, c, d) {
  mxShape.prototype.configureCanvas.apply(this, arguments)
  a.setFontColor(this.color);
  a.setFontFamily(this.family);
  a.setFontSize(this.size);
  a.setFontStyle(this.fontStyle)
}

mxExampleComponent.prototype.updateMargin = function () {
  this.margin = mxUtils.getAlignmentAsPoint(this.align, this.valign)
}

mxExampleComponent.prototype.apply = function (a) {
  mxText.prototype.apply.apply(this, arguments);
  this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_OPACITY, 100)
}

mxExampleComponent.prototype.getThumbSize = function (x, y, w, h) {
  const offset = Math.min(x, y)
  const size = Math.max(w, h)
  return [offset, offset, size, size]
}

mxCellRenderer.registerShape(mxExampleComponent.prototype.shapeKey, mxExampleComponent)

💡提示

  1. 创建 mxExampleComponent.js 进行重写渲染逻辑。

(三) 在mxStencilRegistry.libraries中注册组件

// public/webapp/js/diagramly/Editor.js

// ... other code 
// 图片组件
mxStencilRegistry.libraries['thingskit/imageComponent'] = [SHAPES_PATH + '/thingskit/mxImageComponent.js']
// 示例组件
mxStencilRegistry.libraries['thingskit/exampleComponent'] = [SHAPES_PATH + '/thingskit/mxExampleComponent.js']

💡提示

  1. 需要在 Editor.js 中注册 mxExampleComponent.js
  2. libraries 的key值需要一致。

(四) 创建vue组件

  1. 进入 src/core/Library/packages 目录。
  2. 创建 Example 类别目录。
  3. 创建 ExampleComponent 目录。
  4. ExampleComponent 目录下创建 config.ts 、config.vue 、 index.ts 、index.vue 四个文件。
1. 目录结构图
packages
└── Example
    └── ExampleComponent
        ├── config.ts
        ├── config.vue
        ├── index.ts
        └── index.vue
2. config.ts
import { cloneDeep } from 'lodash-es'
import { ExampleComponentConfig } from '.'
import { ModeEnum } from '@/enums/modeEnum'
import type { ConfigPresetOptionsType, CreateComponentParamsType, CreateComponentType } from '@/core/Library/types'

export const options: ConfigPresetOptionsType = {

}

export default class Config implements CreateComponentType {
  public key: string = ExampleComponentConfig.key

  public presetOption = cloneDeep(options)

  public mode = ModeEnum.EDIT

  public category = ExampleComponentConfig.category

  constructor(params: CreateComponentParamsType = {} as CreateComponentParamsType) {
    type Keys = keyof CreateComponentParamsType
    const keys = Object.keys(params) as Keys[]

    for (const key of keys)
      ((this as CreateComponentParamsType)[key] as CreateComponentParamsType[typeof key]) = params[key]
  }
}
3. config.vue
<script setup lang="ts">
import type { ConfigComponentProps } from '@/core/Library/types'
const props = defineProps<ConfigComponentProps>()
</script>

<template>
  <main>
    示例组件数据绑定面板
  </main>
</template>
4. index.ts
import { PackageCategoryEnum, PackageCategoryNameEnum } from '@/core/Library/enum'
import { useComponentKeys } from '@/core/Library/hook/useComponentKeys'
import type { ConfigType } from '@/core/Library/types'

const componentKeys = useComponentKeys('ExampleComponent')
export const ExampleComponentConfig: ConfigType = {
  ...componentKeys,
  category: PackageCategoryEnum.EXAMPLE,
  categoryName: PackageCategoryNameEnum.EXAMPLE,
}
5. index.vue
<script setup lang="ts">
import type { RenderComponentProps } from '@/core/Library/types'

const props = defineProps<RenderComponentProps>()
</script>

<template>
  <main>
    示例组件内容
  </main>
</template>

💡提示

  1. config.ts 配置组件信息,配置信息可以在 数据绑定面板组件 config.vue 与渲染组件 index.vue 中的props中获取。
  2. index.ts 声明组件基本信息。
  3. index.vue 内容渲染组件。
  4. config.vue 数据绑定面板组件。
  5. 创建的目录名与文件名需要与 Sidebar-ExampleComponent.jscreateUserObjectCATEGORY 组件类别属性与 COMPONENT_KEY 组件key属性值保持一致。
6. 效果图
ThingsKit开发指南-前段组态开发指南(二次开发指南)

3/3: Websocket数据绑定

文件说明

processor socket处理器

// src/core/websocket/processor/index.ts

export class LightboxModeWebsocketService extends TelemetryWebsockerService {
  dataSource: NodeDataType[] // 数据源

  nodeUtils: NodeUtils // 节点工具

  messageHandler: MessageHandler // 消息处理者

  eventHandler: EventHandler // 事件处理者

  constructor(public App: DrawApp) {
    
  }
  
  getNodeDataSubscribers(nodeData: NodeDataType) {
    // 处理单个节点绑定的数据源
    // 将数据源转换为消息订阅者
  }


  processOnMessage(message: WebsocketDataMsg): void {
    // socket 接收消息分发消息
  }

  unsubscribeCurrentPageData() {
    // 取消订阅
  }

  onSelectPageChange() {
    // 切换分页处理
  }
}

💡提示

getNodeDataSubscribers 在此方法中将数据源转换为消息订阅者。

eventHandler 事件处理器

// src/core/websocket/processor/eventHandler.ts
export class EventHandler {
  addDbClickEventListener() {
  	// 添加双击事件
  }

  addClickEventListener() {
  	// 添加单击事件
  }

  addMouseUpOrDownEventListener() {
  	// 添加鼠标按下与抬起事件
  }

  dispathEvent(eventName: EventTypeEnum, event: MxEvent) {
  	//	分发事件
  }
}

messageHandler 消息处理器

// src/core/websocket/processor/messageHandler.ts
export class MessageHandler {
	// 分发消息
  distribute(message: SubscriptionUpdateMsg) {

  }
	// 更新数据源
  updateDataSource(commandSource: CommandSource, message: SubscriptionUpdateMsg) {

  }
  
	// 默认处理数据源
  defaultHandler(commandSource: CommandSource, message: SubscriptionUpdateMsg) {

  }

  // 更新数据动效
  updateDataDynamicEffect(commandSource: CommandSource, message: SubscriptionUpdateMsg) {

  }
}

💡提示

updateDataSource 方法中将调用需要更新的节点组件的更新方法( onMessage ) 方法,若组件没有 onMessage 更新方法,将调用 defaultHandler 方法进行默认处理。

dataDynamicEffectHandler 数据动效处理器

💡提示

DataDynamicEffectHandler 中声明不同的数据动效处理逻辑,将在消息处理器 messageHandler updateDataDynamicEffect 方法进行分发处理。

组件订阅消息

<script setup lang="ts">
  import { useOnMessage } from '@/core/Library/hook/useOnMessage'
  import type { RenderComponentExposeType, RenderComponentProps } from '@/core/Library/types'

  const props = defineProps<RenderComponentProps>()

    const { onMessage } = useOnMessage({
      onReceiveActMessage(commandSource, message) {
        // 关于数据动效的消息
      },
      onReceiveDataSourceMessage(commandSource, message) {
        // 关于数据源中的消息
      },
      onReceiveEventMessage(commandSource, message) {
        // 关于事件中的消息
      },
    })

  defineExpose<RenderComponentExposeType>({
    onMessage,
  })
</script>

<template>
  <main>
    示例组件内容
  </main>
</template>

💡提示

  1. 在渲染组件中定义抛出 onMessage 方法。
  2. onReceiveDataSourceMessage 定义关于接收到数据源的socket消息。
  3. onReceiveEventMessage 定义关于接收到事件的socket消息。
  4. onReceiveActMessage 定义关于接收到数据动效的socket消息。
  5. 如果组件未定义抛出 onMessage 消息将按照默认的处理逻辑更新视图。

参考文档

https://jgraph.github.io/mxgraph/docs/js-api/files/util/mxConstants-js.html#mxConstants.STYLE_ABSOLUTE_ARCSIZE

https://www.drawio.com/doc/faq

https://juejin.cn/post/7065578041140838436

https://juejin.cn/post/7017686432009420808#heading-8

https://bibichuan.gitee.io/posts/3337d458.html

https://fancyerii.github.io/2019/03/26/mxgraph