子应用改造
改造思路
- 页面初始化,从URL可以拿到isInIframe和page等参数,这个参数是Godzilla加载子应用的时候会提供的,如果是以子应用的被加载,则这个参数为true
- 拿到isInIframe这个参数后,子应用就可以根据这个参数做相应的布局调整,一般常见系统的布局如下,分为头部、底部、左侧菜单栏和右侧内容区,但我们作为一个子应用的时候,应该只需要内容区,其它都不需要显示。如果是以路由为基础的子应用(例如采用Angular、React、Vue开发的框架),那么只需要渲染路由的那一部分,其它都不需要渲染。
- 如果作为子系统,那么页面的加载和切换就不能够通过用户点击来操作了,因为页面的加载和切换是由主系统(portal)来控制的,在这种情况下,只能是主系统通知子系统要加载页面,子系统收到指令后,自己调用API来控制页面,目前可以采用postMessage这种技术来实现主系统和子系统之间的通信
- 子系统监听主系统的消息指令,并做相应的处理
- 子系统完成初始化,向主系统发送iframeReady事件,告知子系统已经完成初始化
- 子系统接收openPage事件,根据page参数利用api打开相应的页面
- 子系统接收closePage事件,根据page参数利用api关闭相应的页面
- 子系统接收reloadPage事件,根据page参数利用api重新加载相应的页面
接入 Iframe 子应用
第一步,portal 应用在打开子应用的时候,会将认证信息传递过去,子应用完成自身认证
假设在【应用接入】填写的应用地址是http://sample.com:8080/login
应用 ID是sample
,那么 portal 应用在打开子应用的时候,会将 authToken、isIframe 等信息传递过去,例如http://sample.com:8080/login?authToken=xxx&&appId=xxx&&page=xxx&&isIframe=true&&theme=xx
,各个参数含义如下
- authToken,必传,认证的 jwt 信息,在开发环境下私钥是
quantex
- appId,必传,这个就是在【应用接入】那里填写的应用 ID,这里暂时是冗余信息
- page,必传,这个是子应用的页面地址(配置在菜单上),这里暂时是冗余信息,可以不必理会,在页面加载完之后,portal 应用还会通过 postMessage 告之这个页面地址
- isIframe, 参数值为 true,子应用可以根据这个参数来识别
- theme, 必传,第一次打开页面的主题,后续主题切换会通过 postMessage 方式
第二步,portal 应用统一管理子应用菜单,子应用将菜单维护到 portal 应用
打开【菜单管理】,将子应用的菜单信息添加进去,注意里面的应用 ID 就是上文提到,需要对应起来
第三步,子应用需进行一系列改造,在嵌入到 portal 应用时,只保留子应用的页面信息,其它信息需要隐藏(例如左侧的菜单信息、顶部的导航栏信息、底部的状态栏信息),portal 应用和子应用有如下交互
子应用主页加载完后,需要向 portal 应用发送 iframeReady 信息,通知子应用已经加载完毕,portal 应用可以向子应用发送消息(通过 postMessage 的方式)
if (window.isInIframe) {
// NOTE: 当项目使用 iframe 模式打开时, 通知父窗口, iframe 加载完成
window.parent.postMessage(
{
type: 'iframeReady', // 消息类型
appId: 'sample' // 这个appId就是上文提到的应用Id,必须提供
},
'*'
);
}子应用收到
closePage
时, 可以向portal
发起通知阻止关闭对应页面的 tab// 省略不相关代码
window.addEventListener('message', e => {
const { type } = e.data;
switch (type) {
case 'closePage':
notifyClosePages(e.data);
break;
// other logic you need
}
});
function notifyClosePages({ page }) {
// portal收到消息后会阻止关闭 appId page 同时匹配的 tab
// appId 和 page 都必须有值, 否则将忽略此次消息通知
window.parent.postMessage(
{
type: 'stopClosePage',
appId: process.env.APP_ID, // 必传参数
page // 必传参数
},
'*'
);
// 收到通知后子系统可以需要发起弹窗通知, 以下仅为示例, 具体实现子应用自行考虑
// 重要的一点: portal 在收到消息后对 tab 处理, 可能会发生页面更新, 为了避免更新时与弹窗通知发生冲突, 弹窗等反馈操作需要作延时处理
setTimeout(() => {
if (window.confirm(`确认关闭${process.env.APP_ID}吗?`)) {
window.parent.postMessage(
{
type: 'closePage',
appId: process.env.APP_ID,
page
},
'*'
);
}
}, 200);
}portal 应用通知子应用打开指定页面 openPage,子应用接收到 openPage 信息后,拿到 page 信息,打开相关页面
window.postMessageToIframe({
appId: 'sample',
page: 'sample/User', // 这个就是在菜单管理那里填入的菜单URL
type: 'openPage' // 消息类型
});portal 应用通知子应用重新指定页面,子应用接收到 reloadPage 信息后,拿到 page 信息,重新加载相关页面
window.postMessageToIframe({
appId: 'sample',
type: 'reloadPage',
page: 'sample/User'
});portal 应用通知子应用关闭指定页面,子应用接收到 closePage 信息后,拿到 page 信息,关闭相关页面
window.postMessageToIframe({
appId: 'sample',
type: 'closePage',
page: 'sample/User'
});portal 应用通知子应用切换主题 switchTheme,子应用接收到 switchTheme 信息后,拿到 theme 信息,切换主题
window.postMessageToIframe({
appId: 'sample',
type: 'switchTheme',
theme: getCurrentTheme()
});