XX项目Iframe嵌套云星空spa系统备忘原创
金蝶云社区-yaojunsong
yaojunsong
7人赞赏了该文章 1,483次浏览 未经作者许可,禁止转载编辑于2021年09月13日 09:39:32

本篇基础:BOS平台的二开编程(论坛),C#编程(ms),python语法,json语法,xml语法,VistualStudio2012(up)工具(ms)


XX项目Iframe嵌套云星空spa系统备忘


一、前言

       云星空实现基于领域模型的数据驱动和动态渲染架构,动态请求是依据服务端业务逻辑,通过数据指令来动态发起请求,同时实现一种类似分批增量取数的机制,实现请求时同步增量渲染,从而提高渲染效率。这种架构带来高业务扩展性,和架构弹性,配合BOS平台设计器,可最大程度的支持行业特性的二次开发,来满足各个行业特性需求。云星空HTML5整体采用SPA模式实现最大的内存资源复用。但如果每个功能都采用iframe直接嵌套刷新,由于iframe本身性能限制,会带来严重的性能损耗,因此并不适合。


二、集成解决方案

       采用一次加载,多功能复用的方案可以解决这个问题,如下示意图图:可以在星空系统自定义主控台,实现子系统菜单。也可以通过外部系统实现菜单,然后由外部系统PostMessage到星空中执行相应二开逻辑,然后打开功能。

image.png

       方案操作步骤如下


1、BOSIDE:扩展主控表单

1.1、设置主控表单的【SL客户端插件类】字段为 HTML5_InIFrameConsolePlugIn。公有云HTML5主控台动态表单为 BOS_O2OHtmlConsoleMain, 私有云主控台动态表单 BOS_HtmlConsoleMain


image.png


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、演示插件写入表单操作参考下图:

image.png


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、配置参考下图:

image.png


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)


image.png


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

image.png


5、重启IIS

      浏览器打开 http://xxx.xxx.xxx.xxx/k3cloud/html5/test_iniframeconsole.html 可以进行demo操作. 


6、效果

      可以参考下面动画 https://vip.kingdee.com/article/223476640490577664 


xx项目iframe嵌套SPA消息通知demo_20210909.gif



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站点拒绝了我们的连接请求。”

image.png

iframe被浏览器安全设置阻止或者嵌套的iframe的src地址错误,

1、检查下iframe地址是否正确;

2、设置下云星空允许被iframe的外部系统白名单;(a系统嵌套云星空,需要在云星空中设置a系统为白名单,设置值是a系统的访问地址域名)

参考: https://vip.kingdee.com/article/146280  的 FrameOptionsWhiteList参数设置,摘录配置截图如下;

image.png


疑问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:发送消息后提示下面错误信息; 

image.png

:诊断结果是消息数据的数据类型传递错了,数据字段没有设置,入口角色没有定义。 

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

... ...


赞 7