Name Last Update
.husky Loading commit data...
.vscode Loading commit data...
build Loading commit data...
public Loading commit data...
src Loading commit data...
types Loading commit data...
.env Loading commit data...
.env.development Loading commit data...
.env.production Loading commit data...
.eslintignore Loading commit data...
.eslintrc Loading commit data...
.gitignore Loading commit data...
.stylelintrc.json Loading commit data...
README.md Loading commit data...
index.html Loading commit data...
package.json Loading commit data...
pnpm-lock.yaml Loading commit data...
tsconfig.json Loading commit data...
tsconfig.node.json Loading commit data...
vite.config.ts Loading commit data...
windi.config.ts Loading commit data...

版本:v1.4.0_release

准备

目录文件及重要文件说明

目录文件

/src/main/webapp/js
├── api                                                                       api请求管理
├── config                                                                项目配置 oss 配置等
├── const                                                                 项目中的常量
├── diagramly
│   ├── sidebar
│   │   └── thingskit                                         项目新增的元件库
├── grapheditor                                                       图形编辑器有关文件
├── plugin                                                                项目中使用的插件
│   ├── ace
│   ├── axios
│   ├── crypto-js
│   ├── echarts
│   ├── layui
│   │   ├── css
│   │   │   └── modules
│   │   │       ├── laydate
│   │   │       │   └── default
│   │   │       └── layer
│   │   │           └── default
│   │   └── font
│   └── videos

重要文件说明

元件库开发

image-20230106142352191

文件位置
.
├── src
│   └── main
│       └── webapp
│           └── js
|              └── diagramly
│                  └── Devel.js                                       加载元件库文件
│                  └── sidebar
|                                               └── Sidebar.js                        各图形库加载图形
|                       └── thingskit                         自定义元件库文件
|                                                  └── Sidebar-Engine.js  元件库文件
├── etc
    └── build
       └── build.xml                                          打包自定义元件库
源码介绍
创建元件库

创建需要自定义新增的元件库 !!! 自定义属性 用于确定组件类型件组件和数据绑定面板关联

// Sidebar.Engine.js
// src/main/webapp/js/diagramly/sidebar/thingskit/Sidebar-Engine.js
(function () {
    // Adds Atlassian shapes
    // conduit 管道
    Sidebar.prototype.addConduitPalette = function () {
    // 组件归类 决定数据面板中组件有哪些操作面板
        const { COMPONENT_TYPE } = this.enumCellBasicAttribute
        const { DEFAULT } = this.enumComponentType

    // 图形库信息
        const gn = 'mxgraph.engine';// 图形库id 后续注册时需要使用
        const dt = 'engine'; //图 形库id 后续注册时需要使用
        const label = '发动机'

        const width = 66;
        const height = 74;
        const staticPath = `${Proxy_Prefix}/img/lib/thingskit/`
        const prefix = 'image;image=img/lib/thingskit/'
        const defaultStyle = ';imageAspect=0;'
        this.setCurrentSearchEntryLibrary(dt);

        // !!!自定义属性 当前设置该组件库为默认类型
        const cellAttribute = {
            [COMPONENT_TYPE]: DEFAULT
        }

        const graphPathLib = [
            { name: '3-D 发动机.svg', path: 'engine/3-D 发动机.svg' },
        ]

        const lib = graphPathLib.map(item => {
            item.staticPath = staticPath + item.path
            return item
        })

        const fns = graphPathLib.map(item => {
            return this.addEntry(this.getTagsForStencil(gn, item.name, dt).join(' '), mxUtils.bind(this, function () {
                const cell = new mxCell('', new mxGeometry(0, 0, width, height), `${prefix}${item.path}${defaultStyle}`);
                cell.setVertex(true)
                this.setCellAttributes(cell, cellAttribute)
                return this.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, item.name);
            }));
        })

        this.setVariableImageLib(dt, label, lib)

        this.addPaletteFunctions(dt, label, false, fns);

        this.setCurrentSearchEntryLibrary();
    };
})();
  1. 将元件库导入
// src/main/webapp/js/diagramly/Devel.js
// Devel.js
// 将Sidebar-Engine.js引入
mxscript(drawDevUrl + 'js/diagramly/sidebar/thingskit/Sidebar-Engine.js');
  1. 将元件库加入到需要打包的文件队列中
 <!-- build.xml -->
 <!-- /etc/build/build.xml -->
 <!-- 将Sidebar-Engine.js添加至需要打包的文件中去 -->
<jscomp compilationLevel="simple" debug="false" forceRecompile="true" output="${basedir}/sidebar.min.js">
  ...
    <file name="./thingskit/Sidebar-Engine.js" />
</jscomp>
  1. 将元件库注册到侧边栏
// Sidebar.js
// src/main/webapp/js/diagramly/sidebar/Sidebar.js
// 将图形库id加入Siderbar中
Sidebar.prototype.configuration = [
  ...
  { id: 'engine' } // 图形库id
]
// 添加到更多图形中去
Sidebar.prototype.init = function () {
  ...
    var thingskitEntries = [
      // 注册至到更多图形中
            { title: "发动机", id: 'engine', image: IMAGE_PATH + '/thingskit/发动机.png' }
        ];
  }
// 添加到侧边栏中 
Sidebar.prototype.initPalettes = function () { 
    ...
  // 发动机
  this.addEnginePalette();
}

数据绑定面板开发

image-20230106151624286

image-20230106151717295

文件位置
/src/main/webapp/js
├── grapheditor
│   ├── Format.js
源码介绍

控制元件拥有哪些数据绑定面板,通过 this.setComponentPermission 方法设置各个类型的元件有哪些数据绑定面板。

// Sidebar.js
// src/main/webapp/js/diagramly/sidebar/Sidebar.js
    Sidebar.prototype.init = function () {
    const { LINE_CHART_EXPAND, BAR_CHART_EXPAND, DASHBOARD_CHART_EXPAND, DYNAMIC_EFFECT, DATA_SOURCE, VAR_IMAGE, INTERACTION, VIDEO: VIDEO_PANEL, SWITCH_STATE_SETTING, ONLY_SINGLE_EVENT, RUNNING_AND_STOP } = this.enumPermissionPanel
    const { LINE, LINE_CHART, REAL_TIME, TITLE, VARIABLE, DEFAULT, BAR_CHART, VIDEO, SWITCH, PARAMS_SETTING_BUTTON, DASHBOARD_CHART, IMAGE } = this.enumComponentType

    // 水流类型元件拥有水流效果面板与数据东西面板
        this.setComponentPermission(LINE, [RUNNING_AND_STOP, DYNAMIC_EFFECT])
    // 默认类型的元件拥有数据动效面板
        this.setComponentPermission(DEFAULT, [DYNAMIC_EFFECT])
        this.setComponentPermission(REAL_TIME, [DYNAMIC_EFFECT])
    // 标题类型的元件拥有数据交互面板与动态数据面板
        this.setComponentPermission(TITLE, [INTERACTION, DYNAMIC_EFFECT])
        this.setComponentPermission(VAR_IMAGE, [INTERACTION, VAR_IMAGE])
        this.setComponentPermission(VARIABLE, [DATA_SOURCE, INTERACTION, DYNAMIC_EFFECT])
        this.setComponentPermission(BAR_CHART, [DATA_SOURCE, BAR_CHART_EXPAND])
        this.setComponentPermission(LINE_CHART, [DATA_SOURCE, LINE_CHART_EXPAND])
        this.setComponentPermission(DASHBOARD_CHART, [DATA_SOURCE, DASHBOARD_CHART_EXPAND])
        this.setComponentPermission(VIDEO, [VIDEO_PANEL])
        this.setComponentPermission(SWITCH, [DATA_SOURCE, SWITCH_STATE_SETTING])
        this.setComponentPermission(PARAMS_SETTING_BUTTON, [DATA_SOURCE, ONLY_SINGLE_EVENT])
        this.setComponentPermission(IMAGE, [DATA_SOURCE])
  }

Format.js 根据元件拥有的权限渲染数据绑定面板,如上图开关元件与变量元件数据绑定面板的差异化渲染。

// 数据绑定面板的开发源码均在此方法中
// 开发使用layui与jquery库
// src/main/webapp/js/grapheditor/Format.js
DataFormatPanel.prototype.addDataFont = function () {

  // 该方法用于根据元件类型渲染不同的面板
  async function initNode() {
        const basicAttr = sidebarInstance.enumCellBasicAttribute
        const permissionKey = sidebarInstance.enumPermissionPanel
        const { LINE_CHART, BAR_CHART, DASHBOARD_CHART } = Sidebar.prototype.enumComponentType

        const renderMapping = {
            // 元件拥有数据源权限
            [permissionKey.DATA_SOURCE]: createDataSourcePanel,
            // 元件拥有图表数据绑定面板
            [permissionKey.LINE_CHART_EXPAND]: createChartBindPanel.bind(this, LINE_CHART),
            [permissionKey.BAR_CHART_EXPAND]: createChartBindPanel.bind(this, BAR_CHART),
            // 元件拥有数据源拓展面板
            [permissionKey.DASHBOARD_CHART_EXPAND]: createChartBindPanel.bind(this, DASHBOARD_CHART),
            // 元件拥有数据交互面板
            [permissionKey.INTERACTION]: createInteractionPanel,
            // 元件拥有数据动效面板
            [permissionKey.DYNAMIC_EFFECT]: createDynamicEffectPanel,
            // 元件库拥有变量图片选项
            [permissionKey.VAR_IMAGE]: createVarImagePanel,
            // 元件库拥有视频配置面板
            [permissionKey.VIDEO]: createVideoBindPanel,
            // 元件库拥有开关状态切换面板
            [permissionKey.SWITCH_STATE_SETTING]: createSwitchStateSettingPanel,
        }

        // 根据元件的权限渲染数据绑定面板
        function permissionRender() {
            try {
                const cell = vertices[0]
                const permission = graph.getAttributeForCell(cell, basicAttr.COMPONENT_TYPE)
                const needDisplayPanel = sidebarInstance.getComponentPermission(permission)
                for (const key of needDisplayPanel) {
                    renderMapping[key]()
                }
                if (needDisplayPanel.length) createSubmitPanel()
                UseLayUi.nextTick(() => form.render())
            } catch (e) {
                throw Error('component permission setting has some problem, please check your component permission bind on "Sidebar.prototype.init" method setComponentPermission')
            }
        }
    }

    // 该方法用于创建提交按钮以及根据元件类型做提交参数转换处理
   function createSubmitPanel() {
     ...
      const value = getValueOnSubmit(field)
   }

  // 根据元件类型做上传参数转换
  function getValueOnSubmit(field) {
    const basicAttr = sidebarInstance.enumCellBasicAttribute
    const componentType = sidebarInstance.enumComponentType
    ...
    const renderMapping = {
            [componentType.BAR_CHART]: getChartSubmitValue, 
            [componentType.DEFAULT]: getSubmitValue,
            [componentType.VIDEO]: getVideoSubmitValue,
            [componentType.SWITCH]: getSwitchSubmitValue, 
        }

    ...

    // 处理方法
    function getChartSubmitValue() {}
    function getSubmitValue() {}
    function getVideoSubmitValue() {}
    function getSwitchSubmitValue() {}

}

WebSocket接收数据,前端根据页面组件做发布订阅处理。

// src/main/webapp/js/grapheditor/Format.js

// 事件中心 发布订阅
class EventCenter {
    ...
}
// socket 相关
class Ws {
  ....
}

// 调度中心
class DispatchCenter {
   // ...
    // 初始化方法
    async init(editorUi, currentPage) {
      // 创建事件中心
      this.createEventBus()
      // 获取页面所有节点
      this.saveContentInfo(editorUi, currentPage)
      // 建立socket连接
      this.connectSocket()
      // 获取页面中所有绑定数据源的组件记录
      await this.getContentDataNode()
      // 实例化各个处理模块 各个处理模块
      this.dataSourceHandlerInstance = new HandleDataSource(this)
      this.dataInteractionInstance = new HandleDataInteraction(this)
      this.dynamicEffectInstance = new HandleDynamicEffect(this)
      // 更新队列
      this.updateQueueInstance = new UpdateQueue(this)
      this.sendSubscribeMessage()
    }
  // 创建订阅消息模型 将 消息分类汇总过滤,重复数据源只订阅一条消息
  generateSubscribeMessage() {
    //  ...
  }

  // 根据socket接受消息 调用不同的处理逻辑
  socketOnMessage() {
    // ...
    // 查找当前订阅id中,绑定的相同数据源
    const subList = this.subscribeIdMapping.get(subscriptionId)
        subList.forEach(item => {
          // ...
          // 根据绑定的数据源判断调用的处理逻辑 例如调用 HandleDataSource 实例中的updateCommonDataSource 方法
          this.dataSourceHandlerInstance.updateCommonDataSource(message, item)
        }
  }

}



// 处理数据源
class HandleDataSource {
  ...
  // 根据元件类型分发处理逻辑
  updateCommonDataSource(message, record) {
    const { nodeId, attr } = record
    const node = this.getNodeByCmdId(nodeId)
    node && this.updatePage(() => {
      const { data } = message
      const type = this.getComponentType(node) 
            // ...
      // 元件类型为图片时
      if (type === this.componentType.IMAGE) {
        this.handleImageComponent(message, record)
        return
      } 
    }, node)
    // ...
  }
  // 图片处理逻辑
  handleImageComponent(message, record) {
    const { data = {} } = message
    const { nodeId, attr } = record
    const node = this.getNodeByCmdId(nodeId)
    const [[_timespan, receiveValue] = []] = data[attr] || []
    this.updatePage(() => {
      // 更新节点中的图片
      node.setAttribute('label', `<img class="basic-component__image" alt="图片" src="${receiveValue}" />`)
    }, node)
  }
}
// 处理数据交互
class HandleDataInteraction {
  ...
}
// 处理数据动效
class HandleDynamicEffect {
  ...
}
// 建立更新队列
class UpdateQueue {
  ...
}

const.js

文件位置
/src/main/webapp/js
├── const
│   ├── const.js                          // 定义项目中的常量
│   ├── persistentStorage.js  // 加解密storage,与后台管理系统一致
源码介绍
  1. const.js 常量名
GLOBAL_TOKEN 访问令牌:
    用于请求中携带
    来源于后台管理系统中存储在storage中的值,storage中的值涉及在生产环境和开发环境中是否加解密问题,加解密方式应与后台管理系统中     一致。

GLOBAL_PLATFORM_INFO 平台信息
    用于项目中需要使用平台信息的场景,例如首屏加载时的loading,显示不同平台的平台名。

GLOBAL_WS_URL socket地址
    用于项目中socket连接

hasSavePermission 检查是否权限
    用于检查组态中时候拥有写入的权限
  1. persistentStorage.js 加解密storage相关

配置文件

OSS打包配置

文件位置
/src/main/webapp/js
├── config
│   ├── config.js
源码介绍
/**
 * @description 加载OSS文件时使用的oss文件路径
 */
const OSS_Prefix = 'https://oss.xxx.com/'
/**
 * @description 是否使用OSS文件 开启时生产环境中使用oss服务的文件将从oss服务器中加载。
 */
const Enable_OSS = false

/**
 * @description 代理配置项
 */
const Proxy_Prefix = window.location.pathname.startsWith('/') ? window.location.pathname.replace(/\/$/, '') : window.location.pathname
// index.html
// 例如在index.html中使用Enable_OSS判断是否开启OSS模式,开启则通过 OSS_Prefix 配置的oss服务器地址中加载文件
const appMinSrc = Enable_OSS ? `${OSS_Prefix}app.min.js?v=${releaseVersion}` : `js/app.min.js?v=${releaseVersion}`

项目打包配置

gulpfile.js执行流程

项目基于draw.io二次开发,使用ant进行打包,打包时不能编译es6+语法,如async,await, class等语法,使用新语法时会导致打包失败,因此先使用glup先编译,在通过ant打包。

// 1.打包前先复制需要编译的文件源码
function copyFile() {}
// 2.编译文件
function complieFormat() {}
// 3.引入的文件增加版本号(用于打包部署后服务器上文件不更新)
function generatoreVersion(){}
// 4.构建war包
async function buildWar() {}
// 5.复制需要上传到oss服务器中的文件
function copyFileUsageOssServer() {}
// 6.还原文件
function reductionFile() {}
// 7.清除复制的文件
function clean() {}

开发环境搭建及源码运行

打包部署

准备工作

  1. JDK安装与环境变量配置(项目使用JDK11) 安装文档
  2. Apache Ant安装与环境变量配置(项目使用Ant version 1.10.12 版本) 安装文档 ,安装Apache Ant 1 确保将JAVA_HOME环境变量设置到安装JDK的文件夹。 2 下载的二进制文件从 http%3A%2F%2Fant.apache.org 3,创建一个名为ANT_HOME,一个新的环境变量指向Ant的安装文件夹,在 c%3Aapache-ant-1.8.2-bin 文件夹。 5 附加的路径Apache Ant批处理文件添加到PATH环境变量中。 在我们的例子是 c%3Aapache-ant-1.8.2-binbin文件夹。))
  3. Node.js安装(项目使用v16.15.0版本)

运行打包

# 安装依赖
npm install

# 运行打包 运行后开始打包 预计耗时1min
npm build

打包成功后控制台输出结果与包存放位置,oss文件存放需要上传至oss服务器的js文件。

image-20230106143620675

image-20230106143929180

参考文档

FAQ

  1. 打包过程中发生失败时,可能回导致需要gulp编译的文件被覆盖。应检查需要编译的文件是否已经被gulp编译。

左侧文件则被gulp编译,失败时则到dist目录中找到未被编译的文件或者通过git进行回滚,在解决打包失败的原因后,再次打包。

image-20230106145422197

image-20230106145519954

  1. 打包后index.html会发生变更,引入文件的版本号修改,导致index.html被git追踪,可以通过将index.html加入git忽略文件中去。

image-20230106144551477

  1. 组态启动后的端口因与后台管理端的端口保持一致。