跳到主要内容

开发UI扩展

要使用UI扩展,您首先需要开发并构建一个从外界可以访问的网站(称为扩展站)。您可以使用任何技术开发、维护和部署此网站,只需要满足您的用户可以通过直接公网访问即可。

上下文参数

OpenSCOW会在访问扩展页和调用某些配置接口时,将以下参数作为查询字符串(querystring)加入访问的URL,UI扩展可以通过这些参数获取当前OpenSCOW系统的信息。具体哪些页面/API会传递这些参数会在具体章节里提到。

参数取值解释
scowDark"true" | "false"当前OpenSCOW是否以黑暗主题显示
scowUserTokenstring | undefined当前OpenSCOW的登录用户的token。可通过OpenSCOW认证系统接口查询登录用户
scowLangIdstring当前OpenSCOW显示所使用的语言ID

扩展页

UI扩展的功能应实现为标准的网页。当访问OpenSCOW的扩展路径时,OpenSCOW将会在外层显示OpenSCOW的基础导航结构,并在页面主要部分使用一个<iframe>组件将扩展页的内容显示出来。上下文参数中的参数也将会传递给<iframe>

若只设置了一个UI扩展,当用户访问OpenSCOW部署路径的/extensions/*的路径时,<iframe>将会显示UI扩展/extensions/*下的内容。

若设置了多个UI扩展,当用户访问OpenSCOW部署路径的/extensions/{name}/*的路径时,<iframe>将会显示{name}部分对应的UI扩展的/extensions/*下的内容。

例如,假设OpenSCOW部署于https://myscow.com/scow,您的扩展站1部署于https://myscowext1.com/ext1,扩展站2部署于https://myscowext2.com/ext2

  • 若用户在配置中使用单个UI扩展配置语法时,当用户访问https://myscow/scow/extensions/parent/child?test=123时,OpenSCOW将会显示一个iframe,其URL为https://myscowext1.com/ext1/extensions/parent/child?test=123&scowDark={当前OpenSCOW是否以黑暗模式显示}&scowUserToken={用户token}&scowLangId={当前OpenSCOW显示语言ID}
  • 若用户在配置中使用多个UI扩展配置语法,但是只配置了扩展站1时,起名称为extname1,当用户访问https://myscow/scow/extensions/extname1/parent/child?test=123时,OpenSCOW将会显示一个iframe,其URL为https://myscowext1.com/ext1/extensions/parent/child?test=123&scowDark={当前OpenSCOW是否以黑暗模式显示}&scowUserToken={用户token}&scowLangId={当前OpenSCOW显示语言ID}
  • 若用户在配置中使用多个UI扩展配置语法,配置了扩展站1和2,名称分别为extname1extname2,当用户访问https://myscow/scow/extensions/extname1/parent/child?test=123时,OpenSCOW将会显示一个iframe,其URL为https://myscowext1.com/ext1/extensions/parent/child?test=123&scowDark={当前OpenSCOW是否以黑暗模式显示}&scowUserToken={用户token}&scowLangId={当前OpenSCOW显示语言ID}

配置接口

除此之外,UI扩展站需要实现以下的配置接口。OpenSCOW会在需要的使用调用以下接口获取响应配置。所有配置接口以/api开头。

获取清单:GET /api/manifests

获取UI扩展配置清单。OpenSCOW通过此接口获取您的UI扩展的一些配置参数。

对于此接口,您需要返回如下类型的JSON内容:

JSON属性路径类型是否必须解释
portal对象关于门户系统的配置
portal.rewriteNavigations布尔值是否重写门户系统的导航项。默认为false
portal.navbarLinks布尔值或者对象是否在门户系统中增加导航栏右侧的链接。默认为false
portal.navbarLinks.enabled布尔值是否在管理系统中增加导航栏右侧的链接。默认为false
portal.navbarLinks.autoRefresh对象是否定时自动刷新导航栏右侧的链接。不设置为不定时刷新。
portal.navbarLinks.autoRefresh.enabled布尔值是否定时自动刷新导航栏右侧的链接。默认为false
portal.navbarLinks.autoRefresh.intervalMs数字定时刷新导航栏右侧的链接的间隔,单位ms

将路径中的portal改为mis即设置管理系统的配置,门户系统和管理系统的配置完全一致。

例如,您可以返回如下类型的JSON,表示在门户系统中重写导航项并增加导航栏右侧的链接,在管理系统中不重写管理系统的导航项,增加导航栏右侧的链接,并且开启自动刷新,每5s自动刷新一次。

{
"portal": {
"rewriteNavigations": true,
"navbarLinks": true
},
"mis": {
"rewriteNavigations": false,
"navbarLinks": {
"enabled": true,
"autoRefresh": {
"enabled": true,
"intervalMs": 5000,
}
}
}
}

重写导航项:POST /api/{portal,mis}/rewriteNavigations

重写门户(portal)或者管理系统(mis)的导航项。若您在GET /api/manifests中返回的{portal/mis}.rewriteNavigationstrue,则必须实现对应的接口。

OpenSCOW将会在body中传入默认情况下OpenSCOW将会显示的导航项。下表为传入的JSON参数的属性。除此表之外,上下文参数同样也会被作为查询字符串传入。

JSON属性路径类型是否必须解释
navs对象数组导航项
navs[].path字符串此导航项的路径。此路径不包括base path。若当前浏览器的pathname以此开头,则此导航项将会高亮
navs[].clickToPath字符串点击此导航项将会导航至的路径。如果不填,则使用path属性
navs[].text字符串导航项的文本
navs[].openInNewPage布尔值此导航项的页面是否在新窗口中打开
navs[].hideIfNotActive布尔值如果设置为true,那么这个导航项以及它的子项只会当当前path处于这个导航项的path下才会出现。
navs[].children对象数组,类型与navs数组的每一项相同此导航项的子项。

您需要返回以下类型的JSON,表示重写后的导航项。您可以重写系统默认导航项的属性。

JSON属性路径类型是否必须解释
navs对象数组导航项
navs[].path字符串此导航项的路径,请参考表格下关于返回的路径的的说明
navs[].clickToPath字符串点击此导航项将会导航至的路径,规则同上。如果不填,则使用path属性
navs[].text字符串导航项的文本
navs[].icon对象导航项的图标信息。如果不填,将显示默认导航项中具有相同的path的导航项的图标。如果不存在具有相同path的导航项,将显示Ant Design IconLinkOutlined
navs[].icon.src图标URL导航项的图标地址。必须是完整的、可公开访问的URL
navs[].icon.alt文本导航项的图标alt属性。可不填
navs[].openInNewPage布尔值此导航项的页面是否在新窗口中打开,默认false
navs[].hideIfNotActive布尔值如果设置为true,那么这个导航项以及它的子项只会当当前path处于这个导航项的path下才会出现。
navs[].children对象数组,类型与navs数组的每一项相同此导航项的子项。

关于返回的路径的说明

  • 如果
    • 返回的路径和传入参数的某项的path或者clickToPath相同,或者
    • 此路径是一个有效的URL
      • 检查方法:使用new URL(输入),若不抛出异常则为有效的URL
    • 这个路径将会保留原状,直接写入为<a>标签的href属性
  • 否则
    • 此路径为相当于扩展UI的/extensions下的路径,即
      • 当系统采用单个UI扩展配置语法时,对应的导航项的路径为:{OpenSCOW URL}/extensions/{path}
      • 当系统采用多个个UI扩展配置语法时,对应的导航项的路径为:{OpenSCOW URL}/extensions/{name}/{path}
  • 若当前浏览器的pathname以此开头,则此导航项将会高亮

多个UI扩展

如果配置了多个UI扩展,那么OpenSCOW将会按照配置中的顺序依次调用每个需要重写导航项的UI扩展的此接口,并将上一个UI扩展的输出作为下一个UI扩展的输入,并将最终结果作为OpenSCOW的导航项。

备注

在1.6.2版本中可用。

在门户(portal)或者管理系统(mis)导航栏右侧上部显示其他链接。若您在GET /api/manifests中返回的{portal/mis}.navbarLinkstrue,则必须实现对应的接口。

OpenSCOW在调用接口时,会将上下文参数作为查询字符串传入。

您需要返回以下类型的JSON,表示需要增加的导航栏的链接。

JSON属性路径类型是否必须解释
navbarLinks对象数组导航项
navbarLinks[].path字符串此导航项的路径,请参考下文关于返回的路径的的说明
navbarLinks[].text字符串导航项的文本
navbarLinks[].icon对象导航项的图标信息。如果不填,将显示Ant Design IconLinkOutlined
navbarLinks[].icon.src图标URL导航项的图标地址。必须是完整的、可公开访问的URL
navbarLinks[].icon.alt文本导航项的图标alt属性。可不填
navbarLinks[].openInNewPage布尔值此导航项的页面是否在新窗口中打开,默认true
navbarLinks[].priority数字此链接的优先级。默认为0.

关于返回的路径的说明

  • 如果返回的路径是一个有效的URL
    • 检查方法:使用new URL(输入),若不抛出异常则为有效的URL
    • 这个路径将会保留原状,直接写入为<a>标签的href属性
  • 否则
    • 此路径为相当于扩展UI的/extensions下的路径,即
      • 当系统采用单个UI扩展配置语法时,对应的导航项的路径为:{OpenSCOW URL}/extensions/{path}
      • 当系统采用多个个UI扩展配置语法时,对应的导航项的路径为:{OpenSCOW URL}/extensions/{name}/{path}

多个UI扩展

如果配置了多个UI扩展,那么OpenSCOW将会按照配置中的顺序依次调用每个需要增加导航栏链接的UI扩展的此接口,并将获得的所有链接按以下规则从左到右排列:

  • 优先级(priority)属性从大到小
  • 当优先级相同时,依照获取此链接的顺序,也即
    • 返回链接的UI扩展在配置中的顺序从前往后
    • 同一个UI扩展返回的链接在响应中的列表的顺序从前往后

其他注意事项

  • 当右上角导航栏链接数量大于等于5个,或者屏幕宽度小于768px时,所有导航栏链接将会仅显示图标。
  • 如果您开启了导航栏链接自动刷新功能,并希望每次都重新加载图标,请确保每次返回的图标的链接要有变化,否则浏览器缓存将不会重新刷新图标。

扩展消息

UI扩展可以通过消息postMessage(), MDN)与OpenSCOW交互。下列为OpenSCOW支持的所有类型的消息以及使用场景介绍。

scow.extensionPageHeightChanged: 控制扩展页面的高度

您的扩展页面将会通过一个iframe组件嵌入到OpenSCOW的页面中。由于浏览器的限制,OpenSCOW无法自动根据您网页的高度调整OpenSCOW页面承载您的页面的高度,在默认情况下,您的页面在OpenSCOW中会出现滚动条,影响用户体验。

为了解决这个问题,您需要在您的页面高度变化时,将您的页面的高度报告给OpenSCOW。

具体来说,当OpenSCOW接手到由iframe发出的如下格式的消息时,OpenSCOW将会修改iframe组件的高度为payload.height的值,单位为px。

{
"type": "scow.extensionPageHeightChanged",
"payload": {
"height": 20, // 您的页面高度,单位为px
}
}

如果您的扩展页面为React实现,您可以在您页面的根布局中增加以下代码。此代码使用ResizeObserver监控html组件的高度,并在html组件高度变化的时候,给父组件发送消息。

const useReportHeightToScow = () => {

useEffect(()=>{
// postIframeMessage();
const sendMessage = (height: number) => {
window.parent?.postMessage({
type: "scow.extensionPageHeightChanged", // 发送信息的类型,不允许更改
payload: {
height: height
}
}, '*')
}

const observer = new ResizeObserver((entries) => {

const e = entries[0];
sendMessage(e.contentRect.height);
});

const htmlElement = document.querySelector("html")!;

sendMessage(htmlElement.getBoundingClientRect().height);

observer.observe(htmlElement);

return () => {
observer.disconnect();
}

}, []);
}

export const RootLayout = () => {
// 使用此Hook
useReportHeightToScow();

// ...
}

您可以参考此PRPKUHPC/scow-ui-extension-demo#2实现。

scow.extensionPageTitleChanged: 修改扩展页面标题

您可以向OpenSCOW发送以下格式的消息以修改扩展页面的标题。最终标题为${payload.title} - scow

{
"type": "scow.extensionPageTitleChanged",
"payload": {
"title": "新的标题"
}
}

UI扩展实现参考: https://github.com/PKUHPC/scow-ui-extension-demo/commit/cf20685085889422818055513b238d31b815dc79

其他注意事项