### 摘要 文本讨论了BOS平台二开编程集成云星空SPA系统的方案,特别是iframe嵌套的问题。云星空通过数据驱动和动态渲染提升性能,但iframe嵌套因性能限制不适用。提出了一次加载、多功能复用的方案,详细说明了在BOS平台设计器中设置主控表单、编写Python或C#插件、配置入口角色、拷贝插件文件等操作步骤,并提供了解决配置报错、iframe被阻止、登录超时等问题的建议。
本篇基础:BOS平台的二开编程(论坛),C#编程(ms),python语法,json语法,xml语法,VistualStudio2012(up)工具(ms)
XX项目Iframe嵌套云星空spa系统备忘 (公有云环境不支持该方案)
一、前言
云星空实现基于领域模型的数据驱动和动态渲染架构,动态请求是依据服务端业务逻辑,通过数据指令来动态发起请求,同时实现一种类似分批增量取数的机制,实现请求时同步增量渲染,从而提高渲染效率。这种架构带来高业务扩展性,和架构弹性,配合BOS平台设计器,可最大程度的支持行业特性的二次开发,来满足各个行业特性需求。云星空HTML5整体采用SPA模式实现最大的内存资源复用。但如果每个功能都采用iframe直接嵌套刷新,由于iframe本身性能限制,会带来严重的性能损耗,因此并不适合。
二、集成解决方案
采用一次加载,多功能复用的方案可以解决这个问题,如下示意图图:可以在星空系统自定义主控台,实现子系统菜单。也可以通过外部系统实现菜单,然后由外部系统PostMessage到星空中执行相应二开逻辑,然后打开功能。
方案操作步骤如下:
1、BOSIDE:扩展主控表单
1.1、设置主控表单的【SL客户端插件类】字段为 HTML5_InIFrameConsolePlugIn。公有云HTML5主控台动态表单为 BOS_O2OHtmlConsoleMain, 私有云主控台动态表单 BOS_HtmlConsoleMain。
1.2、给主控表单增加接受消息打开功能的表单插件,下面为Python的演示插件,实施中可以翻译为c#插件。
1.3、演示插件代码如下:
(tips>:curPageId 也可以用外部指令的参数来生成36位以内字符串即可,而不用GUID,这样每个功能单据就和外部指令对应起来,使用时需要判断单据是否存在,不存在在创建新单据,存在就做相关处理即可)
# 通过CustomEvents接受 HTML5_InIFrameConsolePlugIn.onReceiveMessage事件,打开指定功能 from System import * from Kingdee.BOS.JSON import * from Kingdee.BOS.Authentication import * from Kingdee.BOS.Core import * curPageId = '' def CustomEvents(e): arg = JSONObject() if(e.Key == 'HTML5_InIFrameConsolePlugIn' and e.EventName=='onReceiveMessage'): msgArg = JSONObject.Parse(e.EventArgs) hpArgs = StartAppHomePageArgs() hpArgs.FormId = msgArg.GetString('formId') hpArgs.FormType = msgArg.GetString('formType') hpArgs.PkId = msgArg.GetString('pkId') hpArgs.TargetKey = 'FMAINTAB' curPageId = Guid.NewGuid().ToString() hpArgs.PageId = curPageId ViewUtils.OpenSpecFormsByStartArgs(this.View, hpArgs)
1.4、演示插件写入表单操作参考下图:
2、配置入口角色为iniframeemptyconsole
在服务器的website/app_data/EntryRouter.config 的EntryRouterConfig节点中末尾增加一下节点:
2.1、节点xml代码如下:(如果时自定义主控台,可以修改MainFormId和MainFormType为自定义主控的formId,注意自定义的主控台的必须包含FMAINTAB的页签控件来承载打开的功能。)
<EntryRoles> <EntryRole>iniframeemptyconsole</EntryRole> <MainFormId>BOS_HtmlConsole</MainFormId> <MainPageId>MainPageId</MainPageId> <MainFormType>BOS_HtmlConsole</MainFormType> <MenuFormId></MenuFormId> <MenuFormType></MenuFormType> <IncludeCss Version="1.0"> </IncludeCss> <IncludeJs Version="1.0"> </IncludeJs> <CssPlugins Version="1.0"> </CssPlugins> <JSPlugins Version="1.0"> <Item Value="HTML5_InIFrameConsolePlugIn_index" PageTypes=",index,"/> </JSPlugins> <ThemePlugins Version="1.0"> <ThemeItem Key="standard" Name_1033="Standard" Name="{2052:'默认皮肤',1033:'Default Skin',3076:'默認皮膚'}" Color="#C03515"></ThemeItem> </ThemePlugins> <LoginADPictures Version="1.0"> </LoginADPictures> </EntryRoles>
2.2、配置参考下图:
3、拷贝附件插件,按目录结构拷贝到插件目录中:
插件样式表:website\HTML5\Content\themes\kd\plugins\HTML5_InIFrameConsolePlugIn.css
html5前端dform主控台插件逻辑 :website\HTML5\Script\kd\plugins\HTML5_InIFrameConsolePlugIn.js
html5前端index登录页插件逻辑 :website\HTML5\Script\kd\plugins\HTML5_InIFrameConsolePlugIn_index.js
演示iframe嵌套demo页面文件:website\HTML5\test_iniframeconsole.html (该文件说明参考疑问6)
4、调整Demo页面的目标地址:
修改html5/test_iniframeconsole.html 页面源码中的 udurl 变量为星空部署地址
例如:var udurl= 'http://xxx.xxx.xxx.xxx/k3cloud/html5/index.aspx?EntryRole=iniframeemptyconsole&udencoding=utf-8&ud=xxxxx';
注意其中指定角色和编码类型 EntryRole=iniframeemptyconsole&udencoding=utf-8
5、重启IIS
浏览器打开 http://xxx.xxx.xxx.xxx/k3cloud/html5/test_iniframeconsole.html 可以进行demo操作.
6、效果
可以参考下面动画 https://vip.kingdee.com/article/223476640490577664
7、答疑篇:
疑问1:有人反馈不会python,需要翻译为c#
答:翻译C#后参考代码,该代码测试过,可执行上面动图效果。
(tips>:curPageId 也可以用外部指令的参数来生成36位以内字符串即可,而不用GUID,这样每个功能单据就和外部指令对应起来,使用时需要判断单据是否存在,不存在在创建新单据,存在就做相关处理即可)
using System; using System.Data; using Kingdee.BOS.Util; using Kingdee.BOS.JSON; using System.ComponentModel; using Kingdee.BOS.Core.DynamicForm.PlugIn; using Kingdee.BOS.Core.DynamicForm.PlugIn.Args; using Kingdee.BOS.Core; using Kingdee.BOS.Authentication; namespace Kingdee.BOS.Test.PlugIn.TestHTMLControls { [Description("测试Iframe中接受数据并打开表单")] public class TestInIframeReceiverMessagePlugin : AbstractDynamicFormPlugIn { string curPageId = string.Empty; public override void OnInitialize(InitializeEventArgs e) { base.OnInitialize(e); } public override void CustomEvents(CustomEventsArgs e) { var arg = new JSONObject(); if ("HTML5_InIFrameConsolePlugIn".EqualsIgnoreCase(e.Key) && "onReceiveMessage".EqualsIgnoreCase(e.EventName)) { var msgArg = JSONObject.Parse(e.EventArgs); var hpArgs = new StartAppHomePageArgs(); hpArgs.FormId = msgArg.GetString("formId"); hpArgs.FormType = msgArg.GetString("formType"); hpArgs.PkId = msgArg.GetString("pkId"); hpArgs.TargetKey = "FMAINTAB"; curPageId = Guid.NewGuid().ToString(); hpArgs.PageId = curPageId; ViewUtils.OpenSpecFormsByStartArgs(this.View, hpArgs); } else { base.CustomEvents(e); } } } }
疑问2:论坛文章中拷贝代码导致配置文件报错
答:关于论坛文章中拷贝代码导致配置文件报错问题,
参考解决方案: https://vip.kingdee.com/article/158166130920311808
疑问3:外部系统嵌套云星空系统,iframe嵌套被阻止,提示 “xx站点拒绝了我们的连接请求。”
答:iframe被浏览器安全设置阻止或者嵌套的iframe的src地址错误,
1、检查下iframe地址是否正确;
2、设置下云星空允许被iframe的外部系统白名单;(a系统嵌套云星空,需要在云星空中设置a系统为白名单,设置值是a系统的访问地址域名)
参考: https://vip.kingdee.com/article/146280 的 FrameOptionsWhiteList参数设置,摘录配置截图如下;
疑问4:直接登入云星空系统没问题,iframe嵌套云星空时,提示自动登入提示超时,抓取请求存在samesite=Lax ?
答:samesite=Lax, 导致asp.net_sessionid 的Cookie无法携带。这个参数2020年谷歌v80才开始启用,可以尝试下面方案,这个参数各个厂商也一直在变,下面方案可能存在一定的时效性。
1、参考:https://vip.kingdee.com/article/10581 的【17、如何配置cookie的samesite参数?】配置参数,方案1或方案2用rewrite规则。
2、如果客户端使用了谷歌91以上版本,则需要混合使用 上面的两个方案都启用才能生效,也就是升级framework4.7并配置web.config配置项,同时使用rewrite规则;
疑问5:发送消息后提示下面错误信息;
答:诊断结果是消息数据的数据类型传递错了,数据字段没有设置,入口角色没有定义。
1、formId和formType都传的是 undefined;
2、data数据是string格式,但实际传的是Json Object;外部系统传递数据应该为字符串,例如:data = JSON.stringify({ formId: "BD_Currency", formType: "list" }) ;
3、入口角色也没有加 EntryRole=iniframeemptyconsole ;
疑问6:有提问:test_iniframeconsole.html 文件中的localhost地址在上线系统中怎么改?
答:test_iniframeconsole.html 这个文件仅是本解决方案用来模拟外部系统的一个页面,是一个简单的demo页面,也可以让外部系统开发人员参考。实际上线系统时可以不需要这个文件。
疑问7:iframe嵌套spa方案的内部通知外部例子;
答:步骤1、BOS_HtmlConsoleMain 表单插件增加插件逻辑
from Kingdee.BOS.JSON import * def AfterBindData(e): args = JSONArray() arg = JSONObject() arg['operate'] = 'formready' arg['message'] = 'form is ready!' args.Add(arg) #通知外部系统已经准备好 this.View.AddAction('PostMessage', args) # 这里是例子,也可以是其他子单据通过this.View.SendDynamicFormaActons接口实现跨表单调用,例如: # var cview = this.View.GetView("主控"); # cview.AddAction(xxx, xxx); # this.View.SendAynDynamicFormAction(cview); def ButtonClick(e): args = JSONArray() arg = JSONObject() arg['operate'] = 'hideme' arg['id'] = 'menu01' arg['message'] = 'menu 01 click!' args.Add(arg) #通知外部系统执行指令 this.View.AddAction('PostMessage', args)
2、外部系统监听,并执行指令动作,如下代码例子:
<script type="text/javascript"> window.addEventListener('message', function (e) { var args = (e.data || {}), lblRevmsg = document.getElementById('receivemsg'); if (args.type == 'HTML5_InIFrameConsolePlugIn') { for (var i = 0; i < args.data.length; i++) { var arg = args.data[i] || {}; if (arg.operate == 'hideme') { var iel = document.getElementById('iframe_01'); iel.style.display = 'none'; } else if(arg.pagename=='index.aspx'){ //外部系统通过获取message数据后,判断数据包中的pagename=='index.aspx'可判断 lblRevmsg && (lblRevmsg.textContent = '接收数据index:' + JSON.stringify(arg)); } else { lblRevmsg && (lblRevmsg.textContent = '接收数据:' + JSON.stringify(arg)); } } } else { lblRevmsg && (lblRevmsg.textContent = '接收其他数据:' + (typeof (arg) == 'string' ? arg : JSON.stringify(args))); } }); </script>
-----------------------------------------------------------------------
创建于 2021-09-08 16:49
修改于 2021-09-09 10:16
修改于 2021-09-10 08:01
... ...
website_202109101644.zip(8.77KB)
推荐阅读