管控原生融合平台工程配置环境应用接入规范

1. 引言

1.1 目的

本规范对应用接入工程配置环境提出了规范性要求。其输入是 XMagital V1.0的需求规格要求及相关子系统架构设计。该规范对平台开发应用或第三方应用的制作提出了要求,同时也是工程配置环境及要接入工程配置环境的应用的集成相关功能的设计依据

1.2 预期读者

本文档的预期读者包括:XMagital的管理人员、设计人员、开发人员、测试人员,在 管控原生融合平台基础上进行应用软件产品开发的设计人员、开发人员、测试人员、工程实施人员和维护人员。

1.3 术语与缩略语

1.3.1 术语

本文使用的专业术语、自定义的词语、容易产生理解偏差的词语以及缩略语。

术语解释
平台一个公共的基础,在此基础上可以开发不同产品。平台是产品线开发的基础,它为衍生一个产品提供可共用和可重用的特性、设计元素(组件、代码功能)以及相关流程和工具。本文中所提及的平台即管控原生融合平台。 [GB/T37413-2019 数字化车间术语和定义]
应用Application,为应用软件(application software)的简称,指专为特定工程或特定生产场景编写的程序包,或本身也具有一定的通用性,但是通过XMagital应用开发框架编写的、并可以随时从系统摘除的软件包或模块(如签名软件,称量软件,或WMS接口程序等等)。
环境环境即应用运行时依赖的环境。例如,Java应用依赖JRE环境、NodeJs应用依赖NodeJs环境等。
部署工具部署工具提供服务器节点的管理、现场应用及行业包管理、应用部署能力,方便工程施工人员快速安装系统。
HSM-ENG管控原生融合平台中的工程配置环境,可以集中进行工程项目的组态配置。

2. HSM-ENG接入规范概述

2.1 HSM-ENG简介

HSM-ENG是一个可以进行工程配置的集中的配置环境,可以接入工程组态配置类的应用工具,其核心用户是工程项目的实施工程师。

HSM-ENG的核心价值是提高用户体验及工作效率:集中的工程配置环境为用户提供了一个集成的平台,可以在同一个环境中访问多个应用程序,使用户更快地完成任务和工作,从而避免了不必要的切换和登录过程,提高了用户体验及工作效率。

降低实施及管理成本:集中的工程配置环境可以集成多个组态配置类应用程序,使得用户可以更容易地进行项目实施及系统管理,从而降低了实施及管理的成本。

总之,工程配置环境的目的是为了提供一个易用、高效的平台,使用户能够更好地完成工作任务,并为工程项目实施及系统管理提供更好的工作方式。

2.2 HSM-ENG核心能力

ENG工程配置环境提供一套集成配置框架和一组内置工具,主要面向工程实施人员。

集成配置框架由内置环境和接入规范构成,为工具或应用接入ENG提供统一方法和规则,并提供接入后的集成管理方案。支持符合规范的工具或应用接入框架(包括平台工具以及基于平台开发的行业工具),从而构建一个可扩展、可裁剪、高集成度的工程配置环境,支撑多产品形态、多应用场景需求。

内置工具提供系统级通用功能,包括:工程项目管理、数据发布、角色管理、多语言等。ENG对内置工具、接入工具进行集成管理,提供工程配置/组态能力,提高工程项目实施质量和工作效率。

2.2.1 工程管理

提供工程项目的管理功能。主要包括:

  • 项目创建:创建新项目。

  • 项目打开:打开已有项目。

  • 项目导出/导入:项目数据导出,以及导入。

2.2.2 顶部标题栏

标题栏左侧:产品图标+产品名称+面包屑导航。

档题栏右侧:通用系统级操作,例如:全局搜索、帮助、全屏、登录用户等。

2.2.3 顶部工具栏

提供通用工具按钮

  • 通用工具按钮包括:剪切、复制、粘贴、保存、撤销、重做、打印等;

  • 接入HSM-ENG的工具或应用可使用通用工具按钮,挂接实际的操作完成特定功能(前述操作的实现由工具或应用提供);

  • 通用工具按钮可跨工具或应用使用,比如:将一个工具中的配置内容复制到另一个工具使用。

提供自定义工具按钮

  • 提供自定义工具按钮,按钮图标、文本、Tip提示信息可自定义;

  • 接入HSM-ENG的工具或应用可使用自定义工具接钮,挂接实际的操作完成特定功能(前述操作的实现由工具或应用提供);

注:上述通用工具按钮、自定义工具按的作范围均是主编辑区域。

2.2.4 工程项目树

承载接入应用/工具的功能和配置/组态内容。

工程项目树内容层次组织构成:

  • 一级:接入的工具或应用名称,由工具或应用提供,在appmanifest.ini中配置,安装部署后由安装部署工具注册到HSM-ENG;

  • 二级:分两种情况。一种是子工具或功能,二是工具组态结果;

  • 三级及三级以下:同二级;

  • 原则上层级数不超过6级。

初始化安装环境后,在安装部署页面点击一键部署,部署成功后配置的应用信息在工程树种展示。

2.2.5 右侧边栏

右侧边栏可放置属性栏、资源栏等,可在一个分栏内使用多标签页展示不同内容。

  • 右侧边栏整体可收起/展开。

属性栏:

属性栏可挂接编辑界面,实现数据编辑功能。

  • 属性栏实质是可编辑的表单界面,可实现编辑功能;

  • 属性栏内容较多时,允许出现滚动条或按属性分类折叠/展开;

  • 属性栏内可使用多个横向标签页。

资源栏:

资源栏存放配置/组态过程中需要使用的资源。

  • 支持接入HSM-ENG的工具或应用使用资源栏挂接其资源选择界面;

  • 资源栏内容较多时,允许出现滚动条或按属性分类折叠/展开;

  • 资源栏内可使用多个横向标签页。

2.2.6 底部信息栏

集成显示HSM-ENG配置/组态过程中产生产实时信息、提示信息、调试信息、警告信息等。提供接口,供接入HSM-ENG的工具或应用输出信息。

  • 支持消息级别,不同级别显示样式差异化;

  • 支持链接定位到应用内部功能或对象(需应用提供支持);

2.2.7 多语言

提供英文/中文切换功能,界面文本跟随语言切换。语言切换时向接入HSM-ENG的工具或应用发送消息,通知其进行相应的语言切换操作。

2.2.8 应用集成

应用的界面集成方式:

  • 独立浏览器页面式(第三方应用推荐)。

应用和资源配置:

  • 以iframe的方式集成显示

2.2.9 数据发布

主要为接入工程配置环境的各个应用和工具,提供稳定的环境间的连接和可靠的数据传输通道,在此基础上以工程项目的维度,对指定应用的指定范围的数据发起数据发布的动作,发布的内容、范围受工具的控制。为提高发布效率,支持多节点的并行发布。并且为接入工程配置环境中的应用和工具提供数据发布的标准规范,符合该规范的应用和工具均可使用数据发布功能,如应用可按规范实现数据准备、发布、回退等功能。

2.2.10 系统设置

2.2.10.1 登录设置

主要对登录页的欢迎文字、登录logo、登录框标题、背景图片等进行设置

图片 28

2.2.10.2 浏览器标签页设置

主要对浏览器标签页的logo和文字进行设置

图片 29

2.2.10.3 产品设置

主要对页面左上角的产品logo和产品名称进行设置

图片 31

3 HSM-ENG接入规范定义

3.1 运行原理

图片 1

3.2 接入流程

工具应用界面的开发遵循XMagital通用前端开发规范即可。工具应用页面的接入点和通信方式需要HSM-ENG通信规范。

应用接入流程图

3.2.1 接入点

  • 必选:左侧项目树、身份认证、数据发布、通信组件EventBus、项目导入导出及升级、新建/打开/删除项目、多语言、全局搜索。

  • 可选:顶部工具栏、右侧边栏、底部信息栏和底部状态栏。

详细接入案例参考4.3章节

3.2.2 通信方式

3.2.2.1 前端
3.2.2.1.1 安装通信工具

子应用集成过程中大多数行为都涉及到与框架的通信,所以安装并使用通信工具是一个非常基础并且重要的工作。框架提供了通信工具包:event-bus,已发布在 npm 私服中,子应用可以通过 npm 安装该依赖直接使用。Event-bus 中已经封装了部分通信常用的方法,大大降低了自定义通信带来的额外工作量和不方便维持的额外约定。

3.2.2.1.2 约定通信事件

工具应用与HSM-ENG框架前端使用EventBus进行通信。

详情参见3.3.2章节

子应用在初次加载的时候可以通过通信工具向框架请求一些基础的系统信息

示例如下

// 基座 bus.ts
// 基座注册一个 user-info 的 api ,子应用可以通过 bus.getData('user-info') 来随时获取 return 的数据
// 存储eng所有信息
export const USER_INFO = 'user-info';

// eng的sessionStorage 存储的用户 token 的 key
export const ENG_USER_TOKEN = 'eng-user-token';

// eng的用户信息
export const ENG_USER_INFO = 'eng-user-info';

// 存储打开的项目信息
export const ENG_PPROJECT_INFO = 'eng-project-info';

// 注册 user-info 接口,供子应用调用

bus.registryApi(USER_INFO, async () => {
  return {
    token: getToken(), // 打开的用户token
    userInfo: sessionStorage.getItem(ENG_USER_INFO), // 打开的用户信息
    engProjectInfo: JSON.stringify(projectInfo.value), // 打开的项目信息
    newCreateProject: newCreateProject.value, // 是否为新打开的项目
    lang: JSON.stringify(engLang.value), // 国际化
  };
});


3.2.2.1.2.1 token信息

登录时接口返回,在eng接口以及子应用接口中使用

3.2.2.1.2.2 用户信息

获取到的用户信息主要有:ID、登录名和姓名

3.2.2.1.2.3 项目信息

打开的项目信息包含:项目ID,项目名称、项目版本、项目描述等

3.2.2.1.2.4 多语言国际化

主题信息通过拿到的数据中的 res.data.setting.lang字段值。随后监听设置变化事件,当返回的值中有 lang 字段时,需要更新语言。

3.2.2.2 后端
3.2.2.2.1 http webhook

webhook是在特定情况下触发的一种api(回调),用于在项目发生相关事件时通知外部服务器。这些回调由工具应用自己定义、维护、管理,就好像允许别人挂载一条网线到你的Web网站或者应用程序的钩子上,来实时地收到你的推送信息。

3.2.2.2.2 Api

Api为通用的graphql/rest风格的接口。由框架提供,各个工具应用按需使用。

3.3 基于 XMagital 构建的新应用接入规范

XMagital 构建的新应用要集成到HSM-ENG需要注意以下几个方面:

  1. 需要将项目树的结构及行为配置在appmanifest.init文件execute.extend的engMenu字段;
  2. 需要在服务器中按照包名规范将各应用前端包按照应用以特定的包名放到服务器前端路径。

3.3.1 UI/UE 设计规范

为了保证接入HSM-ENG的应用的 UI/UE的一致性,应用需遵循 《智能软件平台软件UI与UE规范》

3.3.2 通信组件 EventBus

工具应用与框架之间使用EventBus进行通信。

常用事件:

事件描述示例
on监听全局事件 包括emit和post的事件bus.on(‘theme-change’, res => { })
off关闭on监听事件bus.off(‘theme-change’)
post跨应用事件发送bus.post(data, “app-vite3”)
emit应用内部事件发送bus.emit(‘change’, data)
clearAll清除所有on中监听事件bus.clearAll()

常用方法:

方法名描述示例
getData获取框架数据获取用户相关信息: bus.getData(“user-info”).then(res => {})
getDataSelf获取另一个 iframe 的数据。需要配置workerPathHMI子应用的 iframe A中: bus.registryApi(‘active-node’, () => ‘textNode’) HMI子应用的  iframeB中: bus.getDataSelf(“active-node”).then(res => {})

自定义事件:

事件类型描述示例
route-change子应用需要跳转路由时通知框架bus.post({   type: ‘menu-change’,   data: {     path: ‘xxx’ } })
setting-change用户设置修改,包含用户信息、主题、语言设置信息等bus.on(‘setting-change’, (res) => {})
user-info获取用户信息,包含token、用户名、主题、语言设置等bus.getData(“user-info”).then(res => {})

详细使用参考API手册

3.3.3 项目树

按照 XMagital UI与UE规范的一致性,各个工具应用的项目树要加载并展示。这就要求工具应用需要提供项目树相关的配置信息

安装部署工具读取项目树配置,并将工具应用的初始树节点追加到工程项目树。

初始化安装环境后,在安装部署页面点击一键部署,部署成功后配置的应用信息在工程树种展示。

项目树是否可打开,是否支持快捷菜单等,由工具应用提供配置。

项目树交互方式:

  • 单击项目树节点,执行项目树的active字段配置

  • 如果工具应用配置了快捷菜单,鼠标移到项目树菜单时会出现快捷菜单选项 ,点击快捷菜单选项,执行快捷菜单操作

图片 34

3.3.3.1 应用配置

工具应用项目树配置信息如下:

字段描述类型是否必填默认值备注
items子项列表对象列表null子项与父项属性一致
id编号字符串唯一
name名称字符串用于显示
app_id应用编号字符串
icon图标字符串用于显示
active菜单项选中时的动作对象active菜单项选中时的动作
active.kind动作类别字符串active.kind动作类别
active.data动作附加信息字符串/对象
expand菜单项展开操作AJAX 对象null为 null 时不显示展开图标(三角)
expand.ajax菜单项展开时的 AJAX 操作AJAX 对象null为 null 时自动使用默认动作
enabled是否可用布尔TRUE不可用时,鼠标显示为阻止样式
order菜单项在本级的显示顺序整数1
actions菜单项右侧的操作按钮集合对象列表
actions.icon按钮动作图标名字符串
actions.tooltip按钮tooltip字符串
actions.kind按钮动作类别字符串open:通过指定 URL 打开标签页 ajax:发送 AJAX 请求 trigger:发送 EventBus 事件 component:插入处理组件
actions.confirmYes、No确认提示信息字符串null
actions.open点击按钮打开标签页处理对象
actions.open.url打开 URL字符串
actions.open.event打开 URL 后附属的 EventBus 事件信息对象null
actions.ajax按钮 AJAX 请求AJAX对象
actions.trigger点击按钮发送 EventBus 事件对象
actions.trigger.event事件信息对象/字符串
actions.component点击按钮追加处理组件对象
actions.component.type组件类型字符串text: 文本框 custom: 自定义组件
actions.component.payload处理组件附加信息对象
actions.component.payload.icon处理组件图标名字符串
actions.component.payload.validations处理组件验证规则对象
actions.component.payload.data处理组件数据对象对象
actions.component.payload.data.text组件待编辑文本AJAX对象/ 字符串
actions.component.payload.data.value组件转化为菜单节点时的附加值对象/字符串null
actions.component.payload.save数据保存请求AJAX对象
templates子菜单数据模板对象该模板是主菜单的属性,有子菜单的工具应用在INI文件中需提供此属性。(具体请参考样例JSON文件)
templates.2.A.3.子菜单数据模板子项(对应level和类型)对象除 level_types、class 和 templates 之外的所有菜单项目
AJAX 对象
ajax.url请求 URL字符串
ajax.method请求方法字符串GET、POST、PUT、DELETEGET
ajax.params请求参数对象{}
ajax.timeout请求超时时间整数10
3.3.3.2 快捷菜单

项目树如果有快捷菜单,当鼠标移动到工程树对应的菜单时,会出现快捷图标,点击快捷图标执行快捷配置的操作。

项目树中工具应用提供所有操作(增删查改)均由ENG统一处理,点击保存时框架调用应用提供的API接口,并通知应用数据更新。

3.3.3.3 集成方式
3.3.3.3.1 提供应用配置信息

配置信息参考 3.3.3.1应用配置

3.3.3.3.2 eventBus通信
  1. 左侧项目树

打开或者创建项目后,根据项目信息展示主页面的项目树 iframe

// appName为应用独一无二的编码,需要拼到地址后面,path时左侧树iframe的地址,name为应用的备注说明

{
  appName: 'app-hmi',
  path: 'http://172.21.201.13:8800/2d/displaysTree?appName=app-hmi',
  name: 'app-hmi',
}

项目树的新增,编辑跟删除需要应用自身控制显示与隐藏

// 点击项目树的节点,需要需要新增或者切换页面,需要应用用eventBus给主框架发送数据,如果需要跟主页面的iframe通信,可以自己增加通信内容

bus.post({
  type:'menu-change',
  appName:'app-hmi',
  appCode:'app-hmi',
  path:''// 主页面iframe的地址
  title:'主页面多标签的名称',
  code:'树上独一无二的编码标识,主要用于多标签页的index', // 最好在最前面加上appName,比如hmi-XXX
})
  1. 左侧项目树的删除

删除需要弹框确认,所以需要通知框架显示删除弹框,并在用户确认后删除项目树节点

// 应用通知框架弹出删除弹框

bus.post({
  type:'project-tree-delete',
  ...data, // data为需要传回去的数据,给框架什么数据确认删除后原样给回去,appName为必传项
})

// 删除成功后框架需要告知应用用户点击了确认删除按钮,第二个参数为应用appName

bus.post({
  type:\`project-tree-${appName}-delete\`,
  ...data, // data为需要传回去的数据,给框架什么数据确认删除后原样给回去
},\`${appName\`)
  1. 左侧树的选中

树的选中状态需要所有应用自查,如果选中非自己项目树的字典,自己应用树需要取消选中状态

// 应用通知框架选中树的数据
bus.post({
  type: 'project-tree-check',
  ...data, // data为需要传给框架的数据
});

// 框架通知应用选中树的数据,第二个参数为global即传送给所有应用
bus.post(
  {
    type: 'project-tree-check-success',
    ...data, // data为需要传回去的数据,给框架什么数据原样给回去
  },
  'global',
);

3.3.4 顶部工具栏

UI 示意图:

图片 1

  • 框架提供通用工具栏组件,应用在项目中按需引入

  • 默认工具图标:剪切、复制、粘贴、保存、撤销、重做、打印

  • 框架提供基础UI图标和按钮点击事件

  • 框架工具栏提供 write 和 read 方法,用来修改和读取框架中的模拟剪切板

  • 对象的剪切、复制、粘贴,应用内部处理

  • 撤销、重做、打印以及应用扩展按钮图标功能需要应用在接收到事件后自行处理,其中打印功能可以在监听到 action 事件为print时调用系统打印接口 window.print() 来实现默认的打印

  • 另外工具栏提供了一个默认插槽,子应用可根据实际情况进行填充

3.3.4.1 集成方式
3.3.4.1.1 工具栏组件安装
组件库使用实例
@hsmos/operate-barimport OperateBar from ‘@hsmos/operate-bar’

组件常用属性:

属性描述默认值
tools自定义按钮[{ icon: ‘mdiCopy’, // 按钮图标 title: ‘复制’, // tooltip提示文字 action: ‘copy’, // 按钮事件 visible: true,  // 显示隐藏 disabled: false // 按钮灰化 },…]

详细使用参考API参考手册

3.3.4.1.2 EventBus通信

顶部工具栏由框架提供组件,应用工具按需使用。可通过EventBus监听按钮点击事件做相应事件的处理。

3.3.5 全局搜索

UI 示意图:

图片 1

针对接入到ENG项目的工具应用,框架提供基于关键字全局搜索功能。

流程说明:

搜索框输入内容后回车,调用HSM-ENG框架的后台搜索服务接口,搜索服务通过各工具应用,各工具应用将搜索结果返回给HSM-ENG框架服务端,框架将各个工具应用的结果整合,并将最终展示结果返给框架前端,框架前端自动展开底部信息栏,并切换到查找选项卡,在查找选项卡内容区域以列表的形式展示查找结果。

查找结果包含可点击的链接,点击链接可快速定位到工具应用的功能页面。

搜索结果:

  • 框架通知应用,应用将搜索结果返回
3.3.5.1 集成方式
3.3.5.1.1 iframe

工具应用提供搜索结果展示的URL地址,框架在数据库中维护,通过iframe的方式集成到搜索结果展示区。

3.3.6 右侧边栏

图片 144       图片 143

由于工具应用各自资源展示内容不定,HSM-ENG提供资源栏多Tab页选项卡组件,应用按需使用。组件布局方式:

  1. 单选项卡显示资源栏或属性栏

  2. 多选项卡,可配置多个资源选项卡和属性选项卡

  3. 上下结构布局,可配置多个资源选项卡和属性选项卡

3.3.6.1 集成方式
3.3.6.1.1 安装
pnpm install @hsmos/components

右侧边栏由框架提供组件,应用工具按需使用。详细使用案例参考 4.3.5章节

3.3.7 底部信息栏

信息栏示例为:图标(新增信息类型图标) 2023-08-29 10:09:55.115-工程师站(新增)-发布代理(修改为应用名称显示)-通知应用<图形运行服务>完成。

UI示意图如下:

图片 10

框架提供统一的底部信息栏,工具应用提供信息内容项以及内容链接信息等,框架监听工具应用事件并进行展示

  • 异常:应用内部异常信息展示

  • 输出:应用内部日志打印展示

  • 查找:全局搜索结果展示

3.3.7.1 输出/异常

异常信息和普通的输出日志由工具应用通过 EventBus 的 post 方法,发送 log-change 事件,即可将输出或者异常信息展示在信息反馈栏中。

事件类型描述应用发送示例
log-change应用内日志推送 type为log类型bus.post({  type: ‘log-change’,  data: {  level 1,  type: ‘error’,// 区分普通日志或者异常信息 content: ‘error info’, time: ‘2023-05-11 10:00:00’ } })
应用异常信息推送 type为error

异常信息和普通的输出日志由工具应用通过 EventBus 的 post 方法,发送 empty-log-item 事件,即可将输出或者异常信息全部清空(在新打开项目或创建项目时清空日志,工具暂无此使用场景)。

事件类型描述应用发送示例
empty-log-item无需参数bus.post({  type: ‘empty-log-item’, })

支持消息级别显示,不同级别显示样式差异化,消息级别:错误、警告、提示。

  • 错误色:危险色系;

    • 警告色:警告色系;
    • 提示色:信息色系;

消息内容可包含链接,用于消息源定位,链接需为配置的菜单中的一项,点击链接定位到应用内页面。

注:信息栏展示的是工具应用推送给HSM-ENG框架的实时操作日志,非服务端日志

3.3.7.1.1 集成方式

3.3.7.1.1.1 EventBus通信

输出日志/异常信息由应用工具前端通过EventBus推送到框架进行展示。

1. 修改tab页的当前项

// 打开项目或者创建项目时清空所有值

bus.on('empty-log-item', () => {
  logData.value = []
  errorData.value = []
})

// 修改tab的active

export enum PanelName {
  Error = 'error',
  Log = 'log',
  Find = 'find',
}

bus.on<{ type: PanelName.Error | PanelName.Find | PanelName.Log }>(
  'eng-tab-active',

  (res) => {
    if (res?.data) {
      const type = res?.data?.type
      if (
        type &&
        [PanelName.Error, PanelName.Find, PanelName.Log].includes(type)
      ) {
        activePanel.value = type
      }
    }
  }
)

2. 信息栏的显示与隐藏

// 控制下面信息框的伸缩

bus.on <
  { flag: boolean } >
  ('info-collapse',
  (res) => {
    if (res?.data) {
      const { flag } = res.data;
      isCollapse.value = flag;

      if (isCollapse.value) {
        setContentHeight(0);
      } else {
        setContentHeight(contentHeight);
      }
    }
  });

3. 输出和异常推送消息

需要高亮的需要使用@{}包裹,例如:‘搜索到了@{10}条数据’,10 为高亮


# event-bus 事件

export enum EventBusTypes {
  SETTING_CHANGE 'setting-change',
  LOG_CHANGE 'log-change',
  FIND 'find',
  RouteChange 'route-change',
}
# 推送的数据结构,data下的数据

export interface LogData {
  type: PanelName
  level: number // 1为错误信息,2为成功信息,3为提示信息
  content: string // 显示的内容
  serverName?: string // 服务器名称
  from?: string // 应用的名称,不传显示eventbus的appName
  time: string | number
  menu?: string // 点击可以跳转的菜单路径
  isShow?: boolean // 是否显示面板
}
# 增加输出和异常日志,发送的输出type为log,异常类型为error

bus.on<LogData | LogData[]>(EventBusTypes.LOG_CHANGE, (res) => {
  if (res?.data) {
    let type: PanelName = PanelName.Log
    if (Array.isArray(res.data)) {

      if (res.data.length) {
        type = res.data[0].type
        res.data.forEach((v) => {

          if (v.type === 'error') {
            errorData.value.push({
              type: EventBusTypes.LOG_CHANGE,
              from: res.from,
              data: v,
            })
          } else if (v.type === 'log') {
            logData.value.push({
              type: EventBusTypes.LOG_CHANGE,
              from: res.from,
              data: v,
            })
          }
        })

        if (res.data[0].isShow) {
          toggleLogPanel(true)
        }
      }
    } else {
      type = res.data.type

      if (type) {
        if (type === 'error') {
          errorData.value.push(res as TransferData<LogData>)
        } else if (type === 'log') {
          logData.value.push(res as TransferData<LogData>)
        }
      }
    }
    updateScroll(type as unknown as PanelName)
  }
})

3.3.7.2 查找

框架全局搜索的结果展示在底部信息栏的查找页签。

UI示意图:

图片 1

3.3.8 状态栏

3.3.8.1 内容

显示三种类型,且图标只支持成功、失败和信息类型,不支持应用自定义图标:

1、数量提示类,如:图标+错误数量、警告数量、运行数量等(状态栏左侧) 2、操作提示类,如:图标+正在进行的操作、操作成功/失败状态(状态栏中侧) 3、对象提示类,如:图标+对象名称、对象基本属性(状态栏右侧)

状态栏同一类型会覆盖提示,且只会显示一个工具的信息,每次发送的消息为数组,可以带一种或者多种(最多三种)类型,显示在不同位置。

底部信息栏显示文字最好不要太多,超出长度会显示…且以tip的方式查看

应用示例:

1.数据组态:

数量提示:编译的错误数量、警告数量

操作提示:编译的进度(数字百分比显示)、编译完成

操作提示:复制/粘贴操作的状态

2.图形组态:

对象提示:图符:风扇

对象提示: 图纸:一车间冷却系统

3.信息系统集成

数量提示:正在运行作业数量、异常作业数量

3.3.8.2 集成方式
export const FOOTERTYPE = {
  NUMBER: 'number', // 数量提示类
  OPERATION: 'operation', // 操作提示类
  TARGET: 'target', // 对象提示类,避免object特殊关键字
};

export const FOOTERICONTYPE = {
  SUCCESS: 'success', // 成功图标
  FAIL: 'fail', // 失败图标
  INFO: 'info', // 信息图标
};

状态栏的数据结构如下:

footerData = [{
  type: 'number|operation|target'// 数量提示类| 操作提示类|对象提示类
  messageData :[{
    type: 'success|fail|info'// 成功图标 | 失败图标 | 信息图标
    content:'变异数量|成功数量'// 具体的信息等
  }]
}]

// 显示状态栏的信息

bus.on<FooterItem[]>(ENG_STATE_SHOW, (res) => {
  if (res?.data) {
    footerData.value = res.data || []
  }
})

3.3.9 多语言

框架提供统一多语言切换入口,工具应用监听框架多语言事件并进行响应。

子应用在首次加载的时候应该通过 event-bus 获取用户当前设置的语言信息。框架提供统一多语言切换入口,框架切换了语言时,会通过 EventBus 的 post 方法向所有已加载的应用发送 setting-change 事件,当子应用监听到 setting-change 事件时,获取其中的 lang 参数来设置语言状态。

事件类型描述子应用监听示例
setting-change个人偏好设置包含语言设置,子应用需要监听该事件bus.on(‘setting-change’, res => {   const lang = res.data.lang })
3.3.9.1 集成方式
3.3.9.1.1 EventBus通信

接入案例参见 4.3.7多语言章节

3.3.10 前端存储

子应用如果与框架部署在同源环境中,原则上不允许操作localStorage / sessionStorage / cookie ,不能做清空缓存的动作。如果确需使用,需要以自己的 appName 作为前缀,例如:hmi-activeTab,避免使用时产生键值冲突。框架缓存的键一般以双中划线开头,例如:—token.

如果确认不在同源环境中无此约束。

框架已使用的键:

存储位置键名说明
localStorage—tokenEng token
—langPortal 语言
—themePortal 主题ID
—lastActiveTimePortal 最后操作时间
—isLockPortal 是否锁屏状态标识

3.3.11 数据发布

本章节接入规范,旨在说明集成于ENG工程配置环境中的工具,如需使用ENG提供的数据发布工具的能力,需要遵循一定的规范要求,以此为基础即可使用数据发布工具完成数据发布。

图片 1

3.3.11.1 调用工具接口
  • 获取数据选择的接口,入参为当前数据发布的工程标识

  • 数据准备的接口,入参为数据选择的结果

3.3.11.2 通知节点应用接口
  • 接收数据发布的接口

  • 接收数据回滚的接口

3.3.12 导入导出

3.3.12.1 导入

将已有项目通过文件导入的方式导到HSM-ENG工程。文件包括项目的基础信息、所包含的工具信息;导入后将信息加载到HSM-ENG工程。

导入流程图

3.3.12.2 导出

将当前项目的工具信息最终以文件的形式导出

  • 导出时,可以选择要导出的数据范围(全部数据、配置数据、业务数据)

  • 支持导出进度展示 (需要工具应用支持)

  • 支持导出取消操作(需要工具应用支持)

当执行导出时,由框架通知各个工具应用,各应用内部自行打包压缩,并将打包结果返回给框架,最终由框架对打包后的结果再次压缩,生成最终的zip包。

导出流程图

3.3.12.3 集成方式
3.3.12.3.1 http webhook通信

工具应用提供http webhook接口,由框架调用

3.3.13 URL规划

URL规划原则:

  1. 名称简短: 因涉及前端的URL可能会出现在浏览器地址栏中,便于分享.

  2. 尽少占用: 框架或平台尽量占用少的一级URL路径资源.

  3. 范围: 只对一级路径进行规范化,二级及以下路径原则上不限制形式.

3.3.13.1 前端

各应用前端应使用子路径的方式打包,如修改代码的baseUrl,各应用的子路径如下:

名称前端URL备注
ENG框架/
设备模型/model
HMI/hmi
安装部署工具/hsm-install
数据发布工具/data-publish
信息系统集成/hsm-io-it
3.3.13.2 后端

与中间件集成各应用方式一致,后端URL规划如下:

名称后端URL备注
ENG框架/graphql
设备模型/api/model
HMI/api/hmi
安装部署工具/api/deploy
数据发布工具/api/publish

3.4 已有应用接入规范

由于老应用没有遵循HSM-ENG应用接入规范开发,在不改造现有项目的场景下是不能接入HSM-ENG框架的。

3.4.1 接入点

3.4.1.1 身份认证和授权
  • 从HSM-ENG的eventBus注册信息中获得token,并在身份认证中使用
3.4.1.2 页面
  • 页面使用iframe的方式集成

3.5 接入应用的身份认证和授权

应用内部使用SSO身份验证。当从ENG打开应用界面时,会携带当前登录用户的TOKEN信息,应用根据TOKEN验证用户身份,从而实现应用免登录。

验证方式:

  • 通过私钥解密 token, token 解析正确并且未过期即视为有效token。

  • 如果应用的服务端判定token无效,则响应给应用的前端,由应用前端通知框架,跳转登录页面。

3.6 iframe 安全限制

iframe 在部署时需要按照内容安全策略(CSP)和 X-Frame-Options 的配置策略来确定是否可以被框架正确加载。

3.7 eng框架与应用通信

1. 框架注册信息,应用在加载完毕后获得框架给应用的信息

// 基座 bus.ts
// 基座注册一个 user-info 的 api ,子应用可以通过 bus.getData('eng-all-info') 来随时获取 return 的数据
// 存储eng所有信息

export const USER_INFO = 'user-info';

// eng的sessionStorage 存储的用户 token 的 key
export const ENG_USER_TOKEN = 'eng-user-token';

// eng的用户信息
export const ENG_USER_INFO = 'eng-user-info';

// 存储打开的项目信息
export const ENG_PPROJECT_INFO = 'eng-project-info';

// 注册 user-info 接口,供子应用调用
bus.registryApi(USER_INFO, async () => {
  return {
    token: getToken(), // 打开的用户token
    userInfo: sessionStorage.getItem(ENG_USER_INFO), // 打开的用户信息
    engProjectInfo: sessionStorage.getItem(ENG_PPROJECT_INFO), // 打开的项目信息
  };
});

// 子应用中调用
interface UserInfo {
  token: string
}

bus.getData<UserInfo>('user-info').then(res => {
  console.log(res) // res 是符合 ApiData 结构的数据
})

2. 应用与应用通信

// 给另一个应用发送消息

bus.registryApi('project-tree-param', async () => {
  return currentNode;
});

// 接受工程树传入的节点数据,此处'project-tree-param'可以自己定义
bus.getDataSelf('project-tree-param').then((resany) => {
  console.log(res)
});

3. 应用与框架通信

左侧项目树的地址后面加?appName=app-db,appName 是必须有的,此 appName 为应用独一无二的编码,且为左侧树iframe地址后面拼接的

应用给框架发送信息,type是必须的

bus.post({
  type:'menu-change',
  ...data
})

框架接收消息

bus.on<Type>('menu-change',(res) => {
  console.log(res)
}) 

4. 信息弹框

// 主框架

bus.on<{ type: 'success' | 'warning' | 'info' | 'error'; message: string }>(
  'message',
  (res) => {
    if (res?.data) {
      const { typemessage } = res.data
      ElMessage({
        type,
        message,
      })
    }
  }
)

需要的是type跟message,跟elmessage保持一致
type只支持'success' | 'warning' | 'info' | 'error
// 打开项目或者创建项目时清空所有值

bus.on('empty-log-item', () => {
  logData.value = []
  errorData.value = []
})
// 修改tab的active

export enum PanelName {
  Error 'error',
  Log 'log',
  Find 'find',
}

bus.on<{ type: PanelName.Error | PanelName.Find | PanelName.Log }>(
  'eng-tab-active',

  (res) => {
    if (res?.data) {
      const type = res?.data?.type
      if (
        type &&
        [PanelName.Error, PanelName.Find, PanelName.Log].includes(type)
      ) {
        activePanel.value type
      }
    }
  }
)

5. 与scada交互

// 支持弹框信息

bus.on<{ type: 'success' | 'warning' | 'info' | 'error'; message: string }>(
  'scada-message',

  (res) => {
    if (res?.data) {
      const { typemessage } = res.data
      Message({
        type,
        message,
      })
    }
  }
)

bus.on<{ submit: boolean }>('scada-cancel-dialog', (res) => {
  emit('update:modelValue'false)
  if (res?.data && res?.data?.submit) {
    bus.post(
      {
        type: 'scada-data',
        data: res?.data,
      },
      'scada'
    )
  }
})

6. 关闭页签

关闭页签的判断逻辑如下:

  1. 增加页签的字段 isAsk 为 true,即应用的页签是否需要判断页签有修改,需要用户确认是否强制关闭,不传即可随时关闭

  2. eng 接收的 data 中含有 allowDelete,即关闭的页签有修改,然后 eng 框架弹框询问应用是否强制关闭,取消则不做操作

  3. 如果打开页签中有 isAsk 为 true 字段,必须接受 eng 框架发送的下面事件,然后告诉 eng 框架关闭是否可以关闭


# 删除当前tab页
# export const DELETE_CURRENT_TAB = 'delete-current-tab'
# 删除当前tab页成功
# export const DELETE_CURRENT_TAB_SUCCESS = 'delete-current-tab-success'
# 删除其他tab页
# export const DELETE_OTHER_TAB = 'delete-other-tab'
# 删除其他tab页成功
# export const DELETE_OTHER_TAB_SUCCESS = 'delete-other-tab-success'
# 删除所有tab页
# export const DELETE_ALL_TAB = 'delete-all-tab'
# 删除所有tab页成功
# export const DELETE_ALL_TAB_SUCCESS = 'delete-other-tab-success'
# 关闭当前页签
# 接收关闭当前页面返回的数据
# 询问当前页签是否有修改。isAsk为menu-change事件的新增字段

EventBus.post(
  {
    type: DELETE_CURRENT_TAB,
  },
  view.appCode
)

EventBus.on<{ currentTag: MenuData; allowDelete: boolean }>(
  DELETE_CURRENT_TAB_SUCCESS,
  (res) => {
    if (res?.data) {
      const { allowDelete, currentTag } = res.data

      if (allowDelete) {
        deleteCurrentTab(currentTag)
      } else {
        MessageBox.confirm('页签有修改,确定要关闭当前页签吗?''提示'{
          type: 'warning',
          confirmButtonText: '确定',
          cancelButtonText: '取消',
        }).then(() => {
          deleteCurrentTab(currentTag)
        })
      }
    }
  }
)

# 关闭其他页签
# 接收关闭其他页面返回的数据
# 询问其他页签是否有修改。isAsk为menu-change事件的新增字段

EventBus.post(
  {
    type: DELETE_OTHER_TAB,
    data: visitedViewArr,
  },
  'global'
)

EventBus.on<{ currentTag: MenuData; allowDelete: boolean }>(
  DELETE_OTHER_TAB_SUCCESS,

  (res) => {
    if (allowOtherDelete.value) return
    if (res?.data) {
      const { allowDelete } = res.data
      if (allowDelete) {
        allowOtherDelete.value false
        tagsViewStore.delOthersViews(selectedTag.value as MenuData)
        tagsViewStore.setActiveTab(selectedTag.value as MenuData)
      } else {
        MessageBox.confirm('其他页签有修改,确定要关闭其他页签吗?''提示'{
          type: 'warning',
          confirmButtonText: '确定',
          cancelButtonText: '取消',
        })
          .then(() => {
            allowOtherDelete.value false
            tagsViewStore.delOthersViews(selectedTag.value as MenuData)
            tagsViewStore.setActiveTab(selectedTag.value as MenuData)
          })
          .catch(() => {
            allowOtherDelete.value false
          })
      }
    }
  }
)

# 关闭所有页签

# 接收关闭当前页面返回的数据

// 询问所有页签是否有修改。isAsk为menu-change事件的新增字段
      EventBus.post(
        {
          type: DELETE_ALL_TAB,
          data: [...visitedViews.value],
        },

        'global'
      )

EventBus.on<{ currentTag: MenuData; allowDelete: boolean }>(
  DELETE_ALL_TAB_SUCCESS,

  (res) => {
    if (allowALlDelete.value) return
    if (res?.data) {
      const { allowDelete } = res.data

      if (allowDelete) {
        allowALlDelete.value false
        tagsViewStore.delAllViews()
      } else {
        MessageBox.confirm('其他页签有修改,确定要关闭其他页签吗?''提示'{
          type: 'warning',
          confirmButtonText: '确定',
          cancelButtonText: '取消',
        })
          .then(() => {
            allowALlDelete.value false
            tagsViewStore.delAllViews()
          })
          .catch(() => {
            allowALlDelete.value false
          })
      }
    }
  }
)

7. 页签与左边树的选中状态

强烈注意:这个事件名跟树的选中时一样的,需要注意关闭所有页签时不传 data,需要应用判断 data 是否为空,并且取消所有树的选中状态

// 项目树的选中后告诉应用

export const PROJECT_TREE_CHECK_SUCCESS = 'project-tree-check-success'

# 页签选中状态切换时项目树选中需要切换到下一个页签,data为menu-change事件发送的数据

EventBus.post(
  {
    type: PROJECT_TREE_CHECK_SUCCESS,
    data,
  },
  'global'
)

# 关闭当前页签时项目树选中需要切换到下一个页签,data为menu-change事件发送的数据

EventBus.post(
  {
    type: PROJECT_TREE_CHECK_SUCCESS,
    data,
  },
  'global'
)

# 关闭所有页签时项目树选中需要切换到下一个页签,此时data为空,,需要应用判断data是否为空,并且取消所有树的选中状态

EventBus.post(
  {
    type: PROJECT_TREE_CHECK_SUCCESS,
  },
  'global'
)

3.8 接入注意事项

  1. 打开项目或者创建项目后显示的各应用项目树的地址在数据库中配置,其中iframe地址后面拼写?appName=app-db,app-db为应用名,数据库中存储,各应用应该将appName存储,并在项目树上点击打开右侧页面时使用eventBus发送menu-change事件,新增或者切换到该应用页面上,menu-change事件的数据中需要含有appCode,appName,path,title,code等,code需要保持为独一无二的值,在多标签页切换时防止重复,path为右侧iframe主页面的路径,同样需要拼写?appName=app-db。

  2. 左侧树 iframe 的组件为邮件的 tree.vue 组件,需要增加 id 为 resizer,组件里面有,这个在服务器中同源会自动调整高度,

  3. 每次加载项目树时出现白色闪烁,项目树加载后消失,在这里eng框架做了处理,左侧树的高度,开始设置为0,在服务器同源加载后显示自适应高度,如果在本地联调,需要将高度放开,否则不显示左侧树。

  4. 下面是应用解决留白部分代码

body {
      opacity: 0;
      animation: page-fade-in 1s forwards;
}
@keyframes page-fade-in  {
      0%  {
            opacity: 0;
  }
      100%  {
            opacity: 1;
  }
}
  1. 树的选中状态的切换,点击其他应用时,自身应用树需要取消选中状态,详见邮件集成文档树的选中状态。

  2. eventbus 的 worker 路径为/worker.js,在 public 文件夹下,如果在服务器中,请打包时将路径修改为/model/worker.js,model 为设备模型服务器文件名,各应用修改为自身服务器上的文件名称。

  3. 如果需要菜单与展示页面的iframe通信需要在初始化时配置workerPath,在public中创建workerjs,这里会通过shared worker通信,只要配置这个worker,就可以在两个应用间通过getDataSelf & registryApi / emit & on

  4. 页面iframe加载完毕时内容并未加载完毕,因此通过框架监听iframe的load事件抛出执行时机不准确,尝试后可以在页面的mount中抛出emit事件,在菜单添加on事件监听实现想要的效果

  5. iframe直接销毁不会执行vue生命周期函数,可以使用监听unload事件的方式

  6. Workerjs的路径如果为/部署后按目前的部署方式会使用eng的worker,建议添加应用前缀使用私有,比如/model/worker.js

4 HSM-ENG接入规范示例

4.1 应用的界面布局

HSM-ENG的界面包含头部左侧菜单栏、内容区、底部信息栏。

子应用在集成到 HSM-ENG的时候,内容显示在框架的主区域(工具栏+操作区+属性栏+右侧按钮区)

图片 8

4.2 应用开发流程

应用开发流程图

4.3 基于 XMagital 构建的新应用接入示例

4.3.1 UI/UE设计原则

工具应用的UI/UE需遵循 《智能软件平台HSM-OS V1.0软件UI与UE规范》。

4.3.2 框架与应用

1. 左侧树组件

如附件所示,el-tree和event-bus组件结合使用,

  • el-tree组件负责展示工程树及其操作;
  • event-bus组件负责与框架的通信,如获取项目信息/用户信息,在主区域打开应用功能页面等.

树节点删除确认对话框由框架提供,应用根据需要调用

2. URL规划

  • 前端: 各应用前端应使用子路径的方式打包,如修改代码的baseUrl,各应用的子路径如下:
名称前端URL备注
ENG框架/
设备模型/model
HMI/hmi
安装部署工具/deploy
数据发布工具/publish
  • 后端: 与中间件集成各应用方式一致,后端URL规划如下:
名称后端URL备注
ENG框架/graphql
设备模型/api/model
HMI/api/hmi
安装部署工具/api/deploy
数据发布工具/api/publish

集成DEMO效果,如下图所示

图片 30

3. 左侧项目树

根据打开的项目显示主页面的菜单树,菜单时由iframe构成,地址类似于http://172.21.201.15:3000/app-db#/projectTree?menuCode=app-db

menuCode为各应用自身独一无二的编码。

点击树上节点后,需要打开右侧多便签页以及显示页面时需要EventBus通讯

EventBus.post({
  type:'menu-change',
  label:'app-db'// 页签的名称
  appCode:'app-db'// 应用的menuCode,需要保持唯一,不重复
  code:''// 点击树上的code,需要保持唯一,不重复
})

4. 主页面显示

点击左侧树后通知eng打开该页面

4.3.3 Npm库配置说明

由于开发环境是内网环境,并且使用到的部分依赖是发布在npm私服的,所以使用这些依赖均需设置项目的 npm 源,设置方式为在项目根目录下创建 .npmrc 文件,之后在项目工程目录使用 npm 相关命令访问的源都是基于npm私服的。

.npmrc 文件内容:

// .npmrc

registry=http://172.21.44.57:4873/

4.3.4 通信组件 – EventBus

HSM-ENG框架与工具应用之间通过EventBus进行通信:

4.3.4.1 安装

在终端中执行 pnpm install @hsmos/event-bus

图片 4

4.3.4.2 引入

在项目 /src/utils 文件夹中创建 bus.ts 文件

import EventBus, { type BusOption } from '@hsmos/event-bus'

const option: BusOption = {
  name: getQueryString(‘appName’)
}

const bus = new EventBus(option)
export default bus

其中 BusOption 中的 name 指的是当前子应用的编码,这个是ENG的应用管理中配置的。在接入 ENG 的时候,可以从iframe的src上读取 appName 参数,来初始化 EventBus。

一个iframe地址示例:

<iframe src="https://172.21.44.120/scada/#/info?appName=app-scada"></iframe>

读取参数的工具函数存放在 /src/utils/tools.ts 中

示例内容如下

// 获取查询字符串中的参数

export function getQueryString(key: string){
  const arg: Record<string, string> = {}

    if (location.href.includes('?')){
      const qs = location.href.split('?')
      let query = qs[1]

      if (query.includes('#')){
        // 如果查询字符串后存在hash值,去掉hash值
        query = query.split('#')[0]
      }

      query.split('&').forEach((v: string=> {
        const kv = v.split('=')
        arg[kv[0]] = kv[1]
      })
    }
  return arg[key]
}

4.3.4.3 应用
4.3.4.3.1 子应用与框架的通信有三种情况:

子应用按需获取框架提供的信息

子应用通过 bus.getData() 的方式可以获取框架提供的信息,该方法返回一个 Promise,以保证不会阻塞子应用的主线程。

子应用在初始化EventBus之后的任何时机都可以执行该方法,例如框架提供了一个接口:user-info,那么我们通过该方法获取框架的用户信息。

bus.getData('user-info').then((res) => {
  console.log('获取到的用户信息: ', res);
});
4.3.4.3.2 子应用向框架发送消息

子应用可以通过 bus.post() 的方式向框架发送消息,比较常见的场景是,子应用点击跳转路由时,需要判断所处的环境,如果是在 iframe 环境,则需要向框架发送路由跳转的消息,由框架来完成路由跳转。

function toPath(path: string) {
  if (window !== parent) {
    bus.post({
      type: 'route-change',
      data: {
        path
      }
    })
  } else {
    router.push(path)
  }
}
4.3.4.3.3 子应用接收框架发送的消息

子应用需要通过 bus.on() 来监听框架发送的过来的消息。比较常见的场景是,如果框架改变了语言、主题等偏好设置,会通知子应用更新设置。

bus.on('setting-change', (res) => {
  console.log('收到设置变化消息通知: ', res);
});

4.3.5 顶部工具栏

4.3.5.1 安装
pnpm install @hsmos/operate-bar
4.3.5.2 使用示例
<script setup lang="ts">

import OperateBar from '@hsmos/operate-bar'

function handleAction(action: string) {
  console.log(action)
  if(action === ‘print’) {
    // 默认打印
    window.print()
  }
}
</script>
<template>
  <operate-bar @action="handleAction"/>
</template>

这是一个最简单的使用示例,默认会渲染6个工具:剪切、复制、粘贴、撤销、重做、打印。

如果需要配置显示哪些按钮或者调整按钮顺序,或者调整鼠标悬浮按钮时显示的文字,都可以通过 tools 的配置来实现。tools 接收一个数组,数组元素需要满足如下的结构。数组的顺序表示按钮渲染的顺序,数组的个数表示按钮的个数。

示例

<script setup lang="ts">
import OperateBar from '@hsmos/components/operate-bar'

const tools = [
  {
    icon: mdiRefresh,
    title: '刷新',
    action: 'refresh',
    visible: true,
    disable: false,
  }
]

function handleAction(action: string){
  console.log('用户触发了行为: ', action)
}
</script>

<template>
  <operate-bar :tools="tools" @action="handleAction"/>
</template>

4.3.6 右侧边栏

右侧边栏是框架提供的一个UI组件,可通过填充不同的插槽内容来实现多种布局方式。

4.3.6.1 安装
pnpm install @hsmos/components
4.3.6.2 使用示例

例如,常规的布局方式为一个多选项卡中包含多个资源栏和属性栏

示例如下

<script setup lang="ts">
import SideBar, { SideBarPane } from '@hsmos/components/side-bar'
function handleCollapse(isCollapse: boolean){
  console.log('用户点击了折叠按钮: ', isCollapse)
}
</script>
<template>
  <side-bar  @collapse="handleCollapse">
<side-bar-pane label="模型" name="resource-model">
资源栏内容
</side-bar-pane>

<side-bar-pane label="属性" name="property">
属性栏内容
</side-ba -pane>

<side-bar-pane label="算法" name="resource-algorithm">
资源栏内容
</side-bar-pane>
</side-bar>
</template>

当使用 bottom 插槽的时候,可以渲染为上下结构2个多标签页:

<script setup lang="ts">
  import SideBar, { SideBarPane } from '@hsmos/components/side-bar';

  function handleCollapse(isCollapse: boolean) {
    console.log('用户点击了折叠按钮: ', isCollapse);
  }
</script>

<template>
    <side-bar @collapse="handleCollapse">
    <side-bar-pane label="模型" name="resource-model">
      资源栏内容
    </side-bar-pane>
    <side-bar-pane label="算法" name="resource-algorithm">
      资源栏内容
    </side-bar-pane>
    <!--下半部分的插槽-->>
    <template #bottom>
      <side-bar-pane label="属性" name="property"> 属性栏内容 </side-bar-pane>
    </template>
  </side-bar>
</template>

4.3.7 底部信息栏

信息反馈栏主要展示子应用在运行过程中抛出的异常和日志。

4.3.7.1 使用示例

抛出异常:

interface ErrorLog {
  type: 'error' | 'log'
  level: number
  content: string
  time: string | number
  menu?: string
}

const errorData: TransferData<ErrorLog= {
  type: 'log-change',
  data: {
    type: 'error',
    level: 1,
    content: '这是子应用发送的异常',
    time: '2023-05-11 18:26:45',
    menu: '/instance?id=5'
  }
}

bus.post(errorData)

抛出日志:

const logData: TransferData<ErrorLog= {
  type: 'log-change',
  data: {
    type: log,
    level: 1,
    content: '这是子应用发送的日志',
    time: '2023-05-11 18:26:46',
    menu: '/instance?id=5'
  }
}

bus.post(logData)

4.3.8 多语言

通过请求静态资源站点的语言包来实现动态可配置的多语言,语言包按语言以 json 格式分类存放,请求的格式为:/langs/{lang}.json。其中每个应用的文件夹内包含一个 config.json 文件,主要记录当前文件夹有哪些语言,以及默认语言。

子应用可以获取静态资源站点提供的语言包或自己工程 public 目录下定义的语言包文件。建议语言包的存放结构为:

配置文件url: /langs/config.json

语言包文件url: /langs/{lang}.json

服务端部署结构:

 ├─ APP1
      └─/langs
          ├─ config.json
          ├─ zh-CN.json
          └─ en-US.json
  └─ APP2
      └─/langs
          ├─ config.json
          ├─ zh-CN.json
          └─ en-US.json

推荐用法:在路由拦截中判断是否已经获取了在线的语言包,如果未获取,则通过fetch 的方式获取指定应用的语言包配置文件 config.json,通过config.json 中配置的语言包集合来判断是否有框架设置的语言包,如果有则正常加载,如果没有则加载默认语言包。通过 fetch 来获取语言包 json 文件,使用 vue-i18n 的 merge API,合并 message 属性。

对于 element-plus 自带的语言选项,我们在项目打包的时候建议把 element-plus 支持的语言包全部打包进去了,只需要动态设置语言选项即可。

图片 744037166

工具应用需要监听框架应用多语言切换事件,并进行多语言的切换。

interface ISettingData {
  lang: 'zh-CN' | 'en-US'
}

bus.on<ISettingData>('setting-change', res => {
  if (res.data) {
    if (rea.data.lang) {
      // 处理语言设置逻辑
    }
  }  
})

4.4 前端存储

// 可以使用

localStorage.getItem('hmi-activeTab');
localStorage.setItem('hmi-activeTab', 'xxx');
localStorage.removeItem('hmi-activeTab');
sessionStorage.setItem('hmi-activeTab', 'xxx');
sessionStorage.removeItem('hmi-activeTab');

// 禁止使用

localStorage.clear();
localStorage.setItem('--token', 'xxx'); // 自定义登录页可以使用
localStorage.removeItem('--token');

4.5 接入应用身份认证和授权示例

通过 EventBus 的 getData 接口获取框架的 token 信息:

import bus from '@/utils/bus';
bus.getData('user-info').then((res) => {
  console.log(res.data.token);
});

子应用的后端判断 token 有效则正常响应,如果判断 token 无效,需要通知子应用的前端,子应用的前端再通知框架跳转登录页:

import bus from '@/utils/bus';

axios.get('/list').then((res) => {
  // 这部分逻辑可以写在拦截器里

  if (res.code === 401) {
    bus.post({
      type: 'logout',
    });
  }
});

4.6 HSM-ENG依赖服务

4.6.1 文件上传服务

参考DB组提供的文件上传服务

4.7 打包

参考3.3.12章节,将应用前端包以应用名称包名放到前端app目录下

图片 36

5 API参考手册

5.1 EventBus 通信组件

简介:适用于 iframe 的框架与子应用通信工具,支持跨标签页通信。

5.1.1 安装

npm install @hsmos/event-bus

5.1.2 使用说明

1. 在 utils 文件夹下创建 bus.ts 文件用于初始化 EventBus 对象

import EventBus, { BusOption } from '@hsmos/event-bus'

const option: BusOption = {
  name: 'app-main'// 当前应用的 name,这个 name 一般从 iframe 的 src 参数 appName 上获取
  workerPath: '/worker.js' , // 当需要跨 iframe 进行通信的时候配置
}

const bus = new EventBus(option)
export default bus

BusOption 的配置项 name 动态获取的方式:

function getQueryString(key: string) {
  const arg: Record<string, string> = {}
  if (location.href.includes('?')) {
    const qs = location.href.split('?')
    let query = qs[1]

    if (query.includes('#')) {
      // 如果查询字符串后存在hash值,去掉hash值
      query = query.split('#')[0]
    }

    query.split('&').forEach((v: string=> {
      const kv = v.split('=')
      arg[kv[0]] = kv[1]
    })
  }

  return arg[key]}const name = getQueryString('appName')

2. 在 main.ts 中引入

import bus from '@/utils/bus'; // 初始化 EventBus
// 注册监听事件,包括监听来自框架/子应用 post 事件和自身的 emit 事件// 可以在任意文件注册

bus.on('msg', (data) => {
  console.log('收到 msg: ', data);
});

3. 使用

// 向框架发消息,使用 post

import bus from '@/utils/bus'
import { TransferData } from 'event-bus-micro-app'

const data: TransferData = {
  type: 'msg',
  data: {
    msg: 'message'
  }
}

// 第二个参数如果有值,为框架向子应用发送消息,如果第二个参数为字符串 global,则向子应用列表中的所有子应用广播消息,支持数组参数,如果是数组可以批量通知子应用;

// 如果第二个参数为空,则是子应用向框架发送消息

bus.post(data)
bus.post(data, 'app-vite3')
bus.post(data, ['app-vite3''app-vite4'])

// 向自身发消息 —— EventBus 也支持当前应用中的发布订阅模式,类似于 mitt 或 this.$bus

bus.emit(type, data)

4. 通过 script 标签引入使用

目前还未部署在静态资源站点,所以需要通过下载压缩包,将 lib 中的 main.js 拷贝到本地项目中引入使用

<script src="./main.js"></script>

EventBus 构造函数已经挂载在全局变量中,直接实例化即可

<!doctype html>
<html lang="en">
    
  <body>
        
    <script src="./main.ts"></script>
        
    <script>
      const bus = new EventBus({
        name: 'app-name',
      });
    </script>
      
  </body>
</html>

5.1.3 更多配置

1. 子应用调用框架或自身提供的 API

API 的设计借鉴了 ajax 的思想,子应用通过 bus.getData(‘api-path’, params) 发起一个数据请求时,框架会根据 api-path 组织需要的数据并返回,这个过程是基于 Promise 的,所以子应用可以在任何时候,任何位置获取框架可提供的数据。

// 框架或子应用注册一个 api ,子应用可以获取 return 的数据

bus.registryApi('user-info', async () => {
  return {
    username: 'Job',
    age: 18,
  };
});

// 子应用调用框架提供的数据

bus.getData('user-info').then((res) => {
  console.log(res); // res 是符合 ApiData 结构的数据
});

// 子应用其他 iframe 调用子应用提供的数据,该方法需要 sharedWorker 支持,所以在初始化  event-bus 的时候需要传入 workerPath 资源路径

bus.getDataSelf('active-node').then((res) => {
  console.log(res); // res 是符合 ApiData 结构的数据
});

2. 动态更新子应用列表,会覆盖初始化的子应用列表

const list: string[] = ['app-app1''app-app2''app-app3']

bus.setChildrenApp(list)

5.1.4 使用注意事项

  1. 初始化的时候 name 不能为空,否则会报错:EventBus: The name cannot be empty during initialization;

  2. 框架向子应用发送消息时,需要确保子应用已渲染;

  3. 保留字符:

    1. global : post 的第二个参数如果为 global,则向子应用列表中的所有子应用以及父应用发送消息,所以子应用命名不可为 global ;
    2. get-data / set-data : 用于服务框架注册接口,子应用调用接口的通信服务,所以框架和子应用的通信时事件的 type 不要使用这两个保留名称;
    3. page-unload : 用于页面刷新或关闭时通知 shared-worker 清除缓存,事件的 type 不要使用该名称;
  4. post / emit 在发送消息的时候,受 postMessage限制,传递的数据不能是DOM节点,vue 的响应式对象等,如果需要传递,可以进行序列化或使用 vue 提供的 toRaw API 进行转换。

5.1.5 类型定义

类型定义

// 内置事件类型

enum EventBusType {
  GetData = 'get-data',
  SetData = 'set-data',
}

// 约定的数据传输结构

interface TransferData<T=unknown> extends Record<PropertyKey, unknown> {
  type: string
  from?: string// 不需要手动设置,发送 post 事件时会自动携带
  timestamp?: number// 不需要手动设置,通信时会自动添加,主要用来标识通信数据的唯一性
  data?: T,
}

// 子应用调用框架接口时通信传输结构

interface ApiData extends TransferData {
  type: EventBusType.GetData | EventBusType.SetData
  api?: string      // 框架提供的 api
  params?: object   // 子应用调用 api 时传的参数
  data?: unknown    // 框架返回值
}

5.1.6 接口定义

接口定义

// 监听全局事件,包括 emit 和 post 的事件

on<T = TransferData>(type: string, cb: EventCB<T>): void;

// 发送本地事件,应用内部事件

emit<T=unknown>(type: string, data: T): void;

/**
 * 发送跨应用事件,框架 -> 子应用, 子应用 -> 框架
 * @param target - global: 向所有子应用和框架广播事件
 * @param target - string - appName: 向指定的子应用发送事件
 * @param target - array - string[]: 向传入的列表中的子应用发送事件
 **/ 

post(data: TransferData, target?: 'global' | string | string[]): void;

// 关闭 on 监听的事件
off<T = unknown>(type: string, cb: EventCB<T>): void;

// 清除所有 on 中监听的事件
clearAll(): void;

// 注册 api
registryApi(api: string, cb: ApiCb): void;

// 子应用调用 api
getData<T=unknown>(api: string, params?: object): Promise<T>;

// 子应用其他iframe调用自身注册的 api,使用时需要注意 2个iframe的name 是否一致,是否在实例化 event-bus de 时候传入 workerPath 资源
getDataSelf<T=unknown>(api: string, params?: object): Promise<T>;

// 动态更新子应用列表 isMerge - 是否合并旧数据
setChildrenApp (list: string[], isMerge = false): void

5.2 全局搜索

  • 框架:根据用户关键字从索引库检索查询

  • 工具应用:调用框架提供的api,更新所有库数据

5.2.1 创建/编辑接口

接口描述:工具应用索引库数据的创建/更新

接口地址:/api/eng/search

请求方式:POST/PUT

数据类型:application/json

请求参数:

名称说明in必填类型
authentication鉴权tokenheader/queryString
projectid项目idbodyString
appid应用idbodyString
url关联资源URL地址bodyString
text资源内容bodyText

响应结构:

名称说明类型
data数据结果Object
|— id索引库数据idString
|— url关联资源URL地址String
|— ……······
message响应信息String
code响应状态String

5.2.2 删除接口

接口描述:工具应用索引库数据的删除

接口地址:/api/eng/search

请求方式:DELETE

数据类型:application/json

请求参数:

名称说明in必填类型
authentication鉴权tokenheader/queryString
projectid项目idbodyString
appid应用idbodyString
url关联资源URL地址bodyString

响应结构:

名称说明类型
data数据结果Object
|— count删除影响条数Integer
message响应信息String
code响应状态String

5.2.3 搜索接口

接口描述:索引库数据的查询

接口地址:/api/eng/search

请求方式:GET

请求参数:

名称说明in必填类型
authentication鉴权tokenheader/queryString
keys查询关键字body/urlString

响应结构:

字段名说明示例
projectId项目id
appid应用id
keys查找关键字app-vite1
text查找到的结果信息建模工具
url查找结果的链接,需要是配置的菜单中的一项/instance?id=6

5.3 项目树

5.3.1 创建/编辑接口

接口描述:工具应用索引库数据的创建/更新

接口地址:/api/eng/project-tree

请求方式:POST/PUT

数据类型:application/json

请求参数:

名称说明in必填类型
authentication鉴权tokenheader/queryString
projectid项目idbodyString
appid应用idbodyString
parentId菜单父节点idbodyString
code菜单编码bodyString
name菜单名称bodyString
url菜单访问地址bodyString
openType打开类型bodyString
······

响应结构:

名称说明类型
data数据结果Object
|— id项目树节点idString
|— code节点编码String
|— name节点名称String
|—······
message响应信息String
code响应状态String

5.3.2 删除接口

接口描述:删除项目树某个节点

接口地址:/api/eng/ project-tree

请求方式:DELETE

数据类型:application/json

请求参数:

名称说明in必填类型
authentication鉴权tokenheader/queryString
projectid项目idbodyString
appid应用idbodyString
id菜单idbodyString

响应结构:

名称说明类型
data数据结果Object
|— count删除影响条数Integer
message响应信息String
code响应状态String

5.4 顶部工具栏

5.4.1 配置项

配置项必填默认说明示例
tools默认显示:剪切 、复制、粘贴、撤销、重做、打印tools 是一个满足 `ToolsData` 数据结构的数组,当用户点击某个图标时,会以 `action` 事件抛出 `ToolsData` 上的 `action` 字符串,以便父组件知道点击了哪个按钮,做出对应的处理。[  {   title: ‘刷新’,   icon: ‘refresh’,   action: ‘refresh’, visible: true, disable: false,  } ]
bus在子应用中使用该组件时,可传入 event-bus 实例,用于在调用 read/write 方法时,与框架的 clip-board 进行通信。
clipBoard在框架中使用该组件时,可传入 clipBoard 实例,用于在调用 read/write 方法时,更新 clipBoard 实例。

5.4.2 组件方法

1、read

读取框架剪切板的数据。read 方法返回一个 Promise,使用方式:const str = await operateBarRef.value.read() 。

2、write

向框架剪切板写入数据。使用方式:operateBarRef.value.write(str)。

5.5 右侧边栏

右侧边栏是框架提供的一个UI组件,子应用按需引用,使用时填充插槽内容的区域即可。

当用户点击了右侧边栏的展开折叠按钮时,抛出一个事件:collapse ,使用方式如下:

function handleCollapse(isCollapse: boolean){
  console.log('用户点击了折叠按钮: ', isCollapse)
}

5.6 底部信息栏

当子应用在发生异常或者需要输出日志时,需要向HSM-ENG发出日志事件,日志的类型分为2种:error和log,日志事件中应该包含日志的类型、等级、时间戳、日志内容,可以选择是否包含链接信息,如果包含链接,则可以点击日志内容跳转到指定的链接,链接内容应该是配置的菜单中的一项,可以在链接中携带查询参数,具体的数据结构为:

interface LogData {
  type: 'error' | 'log'
  level: number
  content: string
  link?: string
  time: string | number
}

发送事件的动作为:

import bus, { type TransferDatafrom '@utils/bus'

const logChangeData: TransferData<LogData= {
  type: 'log-change',
  data: {
    type: 'error',
    level: 1,
    content: 'error info',
    time: Date.now(),
  }}

bus.post(logChangeData)

5.7 多语言

框架在加载的时候,应该通过bus.getData(‘user-info’) 来获取用户信息及设置信息,其中的语言设置信息将以 lang 字段返回,值为:zh-CN/en-US。

import bus from '@utils/bus';

bus.getData <
  { lang: string } >
  'user-info'.then((res) => {
    const lang = rea.data.lang;
  });

系统在运行过程中,当用户修改了偏好设置中的语言时,框架会向所有子应用发出 ‘setting-change’事件,当子应用监听到该事件变化时,应该同步更新自身的设置状态。

子应用监听事件:

import bus from '@utils/bus';

bus.on <
  { lang: string } >
  ('setting-change',
  (res) => {
    const lang = rea.data.lang;
  });

5.8 设置数据参考

5.8.1 登录页设置

// 登录页设置

const loginSettings = {
  // 背景图片服务器路径
  backgroundImg: '', // 登录框logo服务器路径
  loginLogoImg: '', // 欢迎语
  welcomeText: '欢迎进入智能工厂数字化管理系统', // 登录框标题
  loginTitleText: '登录系统',
};

5.8.2 浏览器标签页设置

// 浏览器标签页设置

const pageSettings = {
  id: 'clkazdo7g0000n30iw9x9lawy', // 浏览器标签页logo路径
  logoImg: '', // 浏览器标签页文字
  name: '',
};

5.8.3 产品设置

// 页面左上角产品logo和产品名称设置

const productionSettings = {
  id: 'clkazdo7g0000n30iw9x9lawy', // 产品logo路径
  logoImg: '', // 产品名称
  name: '智能工厂数字化管控系统',
};

5.9 数据发布/导入/导出

数据发布由框架通知应用,在配置文件中需定义应用回调地址:

如:http://[eng.api]/appName/xxx?type=create&projectId=111

其中 type 的值:

  • import:项目导入
  • export:项目导出
  • publish:数据发布