ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SAP S/4HANA Cloud 公共云 Fiori Object Page 如何通过 Adaptation Project 加自定义检查按钮

SAP S/4HANA Cloud 公共云 Fiori Object Page 如何通过 Adaptation Project 加自定义检查按钮 某个客户的生产租户里业务顾问在标准采购订单审核页面上提了一个需求右上角要加一个自定义检查按钮点击后先在浏览器控制台把当前单据上下文打出来确认扩展链路是通的之后再慢慢接真实逻辑。这个需求乍看很小但放在 SAP S/4HANA Cloud Public Edition 的标准 Fiori Object Page 上意味着你不能改标准程序、不能把后端 OData 服务拆开、也不能像过去 NetWeaver 时代那样直接往视图里硬塞一段代码。你能依赖的只有 SAP 暴露出来的那一层 UI 扩展能力。这篇文章把我在实际项目里走通的路径完整写下来从创建 Adaptation Project到在 Object Page 上挂自定义 Action再到用 Controller Extension 把 console.log 真正打出来最后是部署验证和常见坑。适合正在 BTP / SAP Business Application Studio 上做 Fiori 扩展的顾问、开发也适合那些刚接手 S/4HANA Cloud 公共云扩展需求、想搞清楚这套机制到底怎么落地的人。1. 先把问题讲透标准 Object Page 上加按钮为什么需要绕这么多层1.1 一个 Action 的落点不在代码里而在扩展机制里Object Page 是 Fiori elements 里最常见的详情页模板采购订单、销售订单、业务伙伴这类标准页面的主体结构都是它。页面右上角那一排按钮比如更多创建采购订单原始日志在 Fiori elements 里统称为 Action。所谓自定义 Action就是在标准命令区里插入一个你自己命名的新按钮。插入按钮不难难的是按钮按下之后你想执行的代码放在哪里。标准 Object Page 的控制器是 SAP 封装好的SAP S/4HANA Cloud Public Edition 明确禁止修改标准对象所以你不能像在旧的 NetWeaver 客户端里那样直接写死一个方法。你必须通过官方扩展点把自定义逻辑注入进去。console.log 在这里有特殊价值它是验证注入是否成功的最短路径。Fiori 页面是一个由 OData 数据源、注解、控制器、视图片段组成的运行时环境任何一个环节没接上页面都可能是正常显示的但你的自定义代码就是没被触发。用一行 console.log配合浏览器开发者工具可以快速判断代码有没有被加载、事件有没有被绑定、上下文对象长什么样。把它理解为在墙上刷漆前先打一层腻子肉眼看不出大问题但手摸一下就知道附着力。1.2 公共云的限制边界能扩展哪里不能碰哪里在公共云版本里SAP 给出了清晰的扩展边界。底层的数据库表、后台大量 ABAP 对象、标准 OData 服务实现都属于不开放区域。可扩展的区域集中在三块业务配置、自定义业务对象/字段/逻辑、以及 UI 层。UI 层又能分成两类。一类是 Key User 在页面上直接做的 Adapt UI 个性化比如调整字段顺序、隐藏按钮、新增一个带固定跳转的瓷砖这类操作不需要代码。另一类是开发者通过 Adaptation Project 做的代码级扩展这正是我们需要的路。可以用一张表快速看清楚扩展方式能否在标准 Object Page 上加 Action能否执行自定义 JavaScript适合场景Key User 的 Adapt UI部分可以不可以简单布局调整、隐藏显示字段、创建磁贴Adaptation Project Controller Extension可以可以自定义按钮、埋点日志、调用扩展服务自定义 Fiori 应用不可以独立应用可以完全新的业务页面不走标准页面ABAP Cloud / RAP 扩展间接不可以自定义字段、自定义行为、后端逻辑服务所以标题里的需求标准的答案就是第二个Adaptation Project Controller Extension。1.3 为什么不是干脆做一个自定义 Fiori App经常有人问既然都要写 JS为什么不在 SAP Business Application Studio 里直接创建一个全新的 Fiori 应用页面由自己控制想打多少 log 都行因为目标页面是标准采购订单详情页。自定义 Fiori App 只能通过启动磁贴进入自己的界面它没法长在标准应用里面。你做一个一模一样的自定义页面意味着要维护一整条 OData 服务、复制关键 UI 逻辑到头来页面和标准功能的审批流、智能字段、校验逻辑全部脱节。公共云里 SAP 升级标准应用后你的自定义页面还得自己跟进。而 Adaptation Project 是在标准页面外面包一层增量补丁SAP 升级主体时你的按钮和逻辑仍然挂在补丁层上兼容性由官方扩展点机制兜底。这也是为什么我倾向于把这条路称为可行路径因为它不是绕过系统而是顺着系统的扩展点做增量。2. 开工前的三个前置条件账号权限、开发环境、可验证的测试页面2.1 账号权限扩展开发不是普通业务用户想开就能开很多人第一步就卡在权限上症状是 BAS 里已经创建了项目但拉取标准应用列表时一片空白或者 Fiori Launchpad 里右键没有 Adapt UI 菜单。在 SAP S/4HANA Cloud Public Edition 里要做代码级 UI 扩展通常需要两类账号配合BTP 子账户账号属于 Cloud Foundry 空间拥有开发者空间权限能创建 Dev Space、能绑定服务实例。S/4HANA Cloud 租户账号需要分配到包含 SAP_UI_FLEX 相关业务角色的用户组同时要有文档服务、业务编目等的访问权限。如果你用的是 SAP 官方推荐的带扩展性的开发租户一般会预置一个开发用户。但客户环境里经常是从生产租户复制过来的用户角色没同步那就得先在 Fiori Launchpad 里检查当前用户是否有 Adapt UI 入口。没有这个入口后面全白搭。2.2 开发环境基于 SAP Business Application Studio 的 Adaptation ProjectSAP S/4HANA Cloud 公共云的前端扩展开发官方推荐的 IDE 是 SAP Business Application Studio简称 BAS。打开 BAS 后先创建一个 Dev Space选择类型时用 SAP Fiori 或者 Full Stack Cloud Application 都行我习惯选前者启动更快预装的 Fiori 工具链也更全。创建项目时模板列表里找到 UI Adaptation有些版本叫 Adaptation Project 或 SAP S/4HANA Cloud Extensibility。填完项目名称后会让你指定要扩展的目标应用。这里有两种常见操作直接输入标准应用的 OData 服务或语义对象让系统帮你解析出对应的 List Report / Object Page。从系统已激活的应用列表中选择一个模板应用。选好之后BAS 会把标准应用的 Fiori elements 配置结构和本地扩展目录生成出来。这时注意看项目根目录里应该有一个webapp目录下面有annotations、manifest.json、ext之类的子目录。没有这些文件夹说明项目创建类型选错了后面没法做代码扩展。2.3 测试页面别选一个没数据的空页面选哪个标准应用做验证直接影响你调试的心情。建议选一个在系统里能稳定打开、且有主数据的详情页比如采购订单Purchase Order或销售订单Sales Order。原因很实在Object Page 的 Action 按钮通常在页头区域渲染页面如果连基本绑定上下文都没有Action 区域可能都不出现你加了按钮也无从点起。如果你的测试租户里没有现成的业务单据先用 Fiori Launchpad 建一张最简单的采购订单或者用标准 Demo 数据流程生成一份。console.log 本身不依赖业务数据但 Object Page 需要有个上下文对象才能触发正常渲染。3. 在 Adaptation Project 中挂上自定义 Action找对扩展点的两种姿势3.1 标准姿势从结构树里找到 Object Page用 Add Action 生成增量项目生成成功后在 BAS 左侧会看到与标准应用结构对应的树。不同版本展示形式略有差异但逻辑是一样的先看到 Application Structure展开之后能看到 List Report 节点和 Object Page 节点。在 Object Page 节点上右键通常会出现 Add Action 或 Add Custom Action 选项。点进去之后需要填几个字段Action ID给按钮一个稳定的内部 ID建议用命名空间前缀避免和标准按钮撞车。Label按钮显示文字比如自定义检查。多语言环境里这个 Label 可能会生成到 i18n 文件里。Icon可选比如sap-icon://inspect-down。Position按钮放在 Header 区、Footer 区还是更多菜单里面。确认后向导会生成一段增量配置核心就是往该 Object Page 的标准注解集合里注入一个UI.DataFieldForAction或类似的自定义 Action 定义。这正是 Fiori elements 页面运行时能够识别并渲染的机制标准页面本身没有被改动只是在运行期多拿了一层注解。这里我想特别强调很多人在这个界面里找不到事件处理代码的入口以为加完 Button 就结束了。实际上这是 UI Adaptation 工具的常见设计——Action 的视觉定义和它的行为定义是分开的。行为要交给 Controller Extension 去接这就是下一步的正题。3.2 曲线策略如果工具不提供事件绑定用运行时注入兜底如果你的标准应用基于较老的 Fiori elements 版本或者 SAP 并没有给这个页面暴露干净的 Action 扩展点工具里可能只有按钮定义没有事件绑定配置。这种时候还有一条兜底路线在控制器扩展的onAfterRendering生命周期里动态拿到 Object Page 的 Header 按钮容器手动画一个sap.m.Button塞进去由你控制它的 press 事件。看一段示意代码sap.ui.define([ sap/ui/core/mvc/ControllerExtension, sap/m/Button ], function (ControllerExtension, Button) { use strict; return ControllerExtension.extend(my.extension.controller.ObjectPageExt, { onAfterRendering: function () { var oPage this.base.getView().byId(page); var oActionBar oPage.getAggregation(headerContent) || oPage.getAggregation(headerTitle); if (!this._oCustomActionBtn oActionBar) { this._oCustomActionBtn new Button({ text: 自定义检查, press: this.onMyActionPress.bind(this) }); oActionBar.addAction(this._oCustomActionBtn); } }, onMyActionPress: function (oEvent) { console.log(custom action pressed, oEvent); } }); });这段代码的好处是不依赖注解的下发链路坏处是它更像打补丁尺寸不大但有点野。如果你有精力还是优先走官方 Add Action 的路线曲线策略适合做验证、做 POC或者遇到极端页面没有扩展点时临时顶上。4. Controller Extension 与 console.log代码怎么写、事件怎么绑才不踩坑4.1 控制器扩展的基础结构ControllerExtension.extendAdaptation Project 里能执行自定义 JS 的关键是 Fiori elements 支持通过ControllerExtension扩展标准控制器。你会发现它不是要你重写整个控制器而是让你插一根管子到标准控制器的生命周期里。这样 SAP 升级时不会覆盖你的代码扩展点依然有效。我通常会在webapp/ext/controller/目录下新建一个文件文件名带清晰的业务含义比如ObjectController.ext.js。核心结构长这样sap.ui.define([ sap/ui/core/mvc/ControllerExtension, sap/ui/model/json/JSONModel ], function (ControllerExtension, JSONModel) { use strict; return ControllerExtension.extend(my.extension.controller.ObjectController, { // Fiori elements 标准控制器的生命周期 onBeforeRendering: function () { console.log(ObjectPage onBeforeRendering); }, onAfterRendering: function () { console.log(ObjectPage onAfterRendering); }, // 自定义 Action 的事件处理 onMyActionPress: function (oEvent) { var oView this.base.getView(); var oBindingContext oView.getBindingContext(); var oCurrentObject oBindingContext ? oBindingContext.getObject() : null; console.log(Custom action pressed, oEvent); console.log(Current object:, oCurrentObject); } }); });注意重点是this.base。在ControllerExtension里this并不是标准页面的控制器实例你想要访问标准控制器的视图、模型、路由都要通过this.base把标准控制器对象拿过来。很多第一次写扩展的人会习惯性地this.getView()结果报错原因就在这里。4.2 把 Handler 和 Action 绑起来Action 加好之后需要在项目配置里把按钮的 press 事件指向上面这个方法。具体入口取决于你的 BAS 版本常见的是在 Action 的配置面板里找到 Handler 或 Event 字段填my.extension.controller.ObjectController.onMyActionPress。如果你手改manifest.json会看到类似这样的结构sap.ui5: { extends: { controllers: { sap.suite.ui.generic.template.ObjectPage.controllers.ObjectPageController: my.extension.controller.ObjectController } } }这行的意思是当标准 Object Page 控制器运行时额外加载扩展控制器ObjectController并把生命周期事件和自定义方法注入进去。配置完成后点击自定义按钮时标准控制器找不到这个处理函数就会到扩展控制器里找最终执行我们的onMyActionPress。这里有个非常容易犯的错Handler 字符串写成了my.extension.controller.ObjectController.onMyActionPress但文件实际暴露的 name 空间不一致。Fiori elements 做事件解析时对命名空间是强校验的大小写差一个字母都不会匹配。如果按钮点了没反应第一件事就是检查这个字符串和sap.ui.define里的第一个参数、以及extend方法的类名三者必须完全一致。4.3 在 console.log 里能看到什么上下文对象的读取执行console.log(Current object:, oCurrentObject)时预期能在浏览器的 Console 面板看到一整段采购订单的字段集合。getBindingContext()在 Object Page 上取到的通常就是当前单据行项的上下文可以直接拿到销售订单号、采购订单号、状态等关键字段。但是要注意某些 Object Page 的页面级上下文和页头详情区并非同一个绑定上下文。如果oView.getBindingContext()返回 null不要慌可以从事件源控件身上找onMyActionPress: function (oEvent) { var oSource oEvent.getSource(); var oCtx oSource.getBindingContext() || this.base.getView().getBindingContext(); console.log(oCtx oCtx.getObject()); }在 Fiori elements 里按钮往往由标准模板创建它所在的命名视图带有一个标准绑定上下文。直接getSource().getBindingContext()有时候拿不到因为按钮可能不在业务数据视图内。最稳妥的写法是两者都试先取控件上的上下文再退回视图级上下文。还有一个实践心得console.log不要只打字符串最好把整个对象打出来。浏览器控制台允许你展开这个对象查看所有字段结构这对理解标准对象页在运行时到底绑定了什么非常有帮助。我经常是先在按钮事件里打一次完整对象再把字段名一个个摘进去。5. 部署、激活、排障从 F12 无输出到 Console 打出一行行日志5.1 把 Adaptation Project 部署到云租户本地代码写完只是第一步公共云环境里用户访问的 Fiori Launchpad 不会实时感知 BAS 里的改动。你需要在 BAS 里执行部署把扩展内容推送到对应的 S/4HANA Cloud 租户。不同项目的部署入口名称略有不同常见的是 Deploy Application / Deploy to SAP S/4HANA Cloud。部署时会让你选择目标传输请求或软件包。商业上生产租户一般会有正式的传输管理流程测试租户如果配置了 PMS 或自定义传输请求也可以直接走。我建议分两步走先在开发/测试租户部署验证代码没问题再通过正式的传输链路上生产。公共云的好处是标准应用的版本升级由 SAP 负责你的扩展作为增量包存在只要扩展点没有在新版本里被废弃部署后按钮就会继续常驻。部署完成后重新进入 Fiori Launchpad打开扩展过的标准应用。如果页面看不到自定义按钮先做三件事确认用户角色里包含该 UI Adaptation 对应的编目。确认部署时选的传输请求真的发放到了当前租户。强制刷新浏览器缓存清除/ui2/upd相关缓存片段后重进。5.2 打开浏览器开发者工具验证链路的标准动作点击自定义按钮后如果一切正常浏览器 Console 面板会出现Custom action pressed。如果没有任何输出按下面的顺序排查现象可能原因处理方式按钮没出现扩展未部署角色未分配缓存重部署、检查业务角色、强制刷新按钮出现但点击无反应Handler 名称空间写错控制器扩展文件未被加载检查 manifest 的 controllers 映射在函数里加断点Console 报this is not allowed with this security level configuration当前用户安全上下文不支持该操作或事件用有扩展权限的管理员账号再测检查 IUUC 配置Console 报Cannot read properties of undefinedthis.base在某个生命周期里没有初始化完成把代码放到 onAfterRendering 或事件函数中避免在 onInit 里挂行为按钮出来了但 log 打了旧值浏览器缓存了旧版 JSCtrlF5 强制刷新或修改 JS 文件名带上版本号关于this action is not allowed with this security level configuration这类报错很多人以为是代码问题实际上往往是权限配置导致的运行时拦截。举个例子你在 BAS 的预览模式下打开页面本地开发环境的安全策略和云租户不一致某些非标准操作会被安全级别挡住。换成租户内用户账号访问后这个错误通常就消失了。5.3 光看 log 还不够加断点和查看 Source 面板console.log 只是最基本的验证方式如果代码复杂度上来了需要看更细的执行顺序我建议直接在onMyActionPress函数里加一个debugger;语句onMyActionPress: function (oEvent) { debugger; console.log(Custom action pressed, oEvent); }然后在浏览器开发者工具里重新点击按钮脚本会在debugger处停下来你可以沿着调用栈检查oEvent的来源、this.base的可用性、绑定上下文的取值。这一步最直观能省去很多瞎猜的时间。不过要记住debugger;只适合开发阶段部署到生产之前一定要删掉否则用户按 F12 能看到你的调试停点既不专业也容易在断点上拖慢页面交互。6. 从 console.log 到真业务逻辑公共云里后续还能怎么演化6.1 把按钮动作接到自定义后端服务上console.log 验证通过意味着整条 UI 扩展链路已经打通接下来就不要再打印日志了而是把 Action 指向真正的业务逻辑。公共云里如果你需要按钮去读或写数据最规范的路径不是直接调标准 OData 的修改服务而是新建一个自定义 CDS 视图或自定义业务对象把逻辑暴露成一个 OData 服务然后在前端通过oModel.create或oModel.callFunction调用。代码大概长这样onMyActionPress: function (oEvent) { var oModel this.base.getView().getModel(); var oPayload { purchaseOrder: this.base.getView().getBindingContext().getProperty(PurchaseOrder), // 其他业务字段 }; oModel.callFunction(/MY_CUSTOM_SRV/executeCheck, { method: POST, urlParameters: oPayload, success: function (oResult) { sap.m.MessageToast.show(检查完成); console.log(oResult); }, error: function (oError) { console.error(oError); } }); }这里把console.log用作结果观察其实是合理的因为后端服务的返回结构只有打出来看最直观。等确认无误再把console.log替换成页面提示、数据刷新或导航跳转。6.2 用 MessageToast 给用户可见反馈如果这个按钮是给业务人员用的他们不会打开浏览器控制台去看到底打了什么 log。所以最终形态里Action 触发后应该有一个可见反馈最常见的是sap.m.MessageToast.show或者标准消息框sap.ui.require([sap/m/MessageToast], function (MessageToast) { MessageToast.show(自定义检查已完成共处理 12 条记录); });对于错误场景用sap.m.MessageBox.error更合适。这里要额外注意在 Controller Extension 里引入这些库不要写在sap.ui.define的依赖数组里就完了还要保证代码在 UI5 的模块加载器里正确解析。如果只是想快速验证用sap.ui.require在函数内懒加载也行。6.3 长期维护的几个习惯最后聊几个我自己反复踩过的坑。第一console.log 不是免费的标签。公共云生产环境里如果每个用户点击一次按钮都会在浏览器控制台刷出几条日志虽然不影响系统性能但会影响页面调试的干净度。我的习惯是加一个环境判断var bDebugMode window.location.href.indexOf(debugtrue) ! -1; if (bDebugMode) { console.log(Custom action pressed); }这样生产环境默认安静需要排障时在 URL 后面拼debugtrue日志才输出。第二Controller Extension 方法不要命名得太通用。比如onPress、onClick这种很危险的词一旦某天标准控制器里也有同名方法有可能触发你意料之外的覆盖。建议都带上你的业务前缀比如onZCustomCheckPress这样既好搜也好排错。第三公共云升级问题。SAP 会在新版本里调整标准页面的控制器类名、注解结构、模板 API。适配项目的好处是升级时 SAP 尽量保证兼容但你还是应该在每次季度发布后花十分钟到测试租户上点一下自定义按钮确认 console.log 还能正常输出。扩展点失效往往不是报明显错误而是按钮静默消失或者 Handler 不再被调用。我自己的做法是把这条验证路径写成一个简单的冒烟测试文档每个季度发布后照着点一轮两分钟就能覆盖所有自定义 Action、控制器扩展和自定义服务链路。这个习惯看似朴素但真的能避免很多生产事故。从第一行 console.log 出现到按钮真正跑通自定义业务逻辑Adaptation Project 这条路说长不长说短不短。核心不是代码量而是搞清楚 Fiori elements 的扩展机制是怎么把按钮、控制器、注解串起来的。只要这个链路摸清了往后在任意标准 Object Page 上加工都只是重复这套框架而已。
返回列表