ARTICLE DETAIL

资讯详情

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

RAP应用搜索优化实战:Value Help、Additional Binding与隐藏Key的组合打法

RAP应用搜索优化实战:Value Help、Additional Binding与隐藏Key的组合打法 接手一个 RAP 应用项目后业务用户第一轮测试就把搜索框骂翻了只能按内部 Key 搜索没有 Value Help列表第一列还直接暴露 UUIDAdditional Binding 一张白纸。后来我把 Value Help、Additional Binding 和 Key 隐藏这三件事组合成一套打法前后端模型联动做了调整搜索体验才算真正能看。这篇就是来拆这套打法的适合正在做 RAP / Fiori Elements 项目的 ABAP 开发、BTP 开发或功能顾问也适合那些刚把 BO 模型搭出来、正被用户吐槽“搜索框没法用”的人。1. 先从用户的真实抱怨入手搜索框和 Key 字段为什么难用很多团队做 RAP 应用验证功能时只看 CRUD到了用户接受性测试阶段才发现搜索体验完全不是一回事。这个阶段返工成本最高所以我建议建模一开始就把“用户怎么搜”想清楚。1.1 一个典型场景用户要查单据却只能输 UUID我接过的项目里有不少是资源申请、售后单这类业务。数据主键按 RAP 惯例用了 UUID也就是系统内部生成的一串 32 位十六进制值。传统 ECC 时代用户搜“申请单号”、搜“客户名称”都是理所应当的事但到了 RAP 生成的 Fiori Elements 列表页不清洗模型的话用户打开高级搜索看到的过滤字段就是UUID、createdAt、createdBy。用户问的第一个问题永远是“这串编号我从哪拿”第二个问题是“为什么不能直接输客户名称”。UUID 本身不是给人类看的但 RAP 默认会把主键元素暴露到 OData 服务里Fiori Elements 的搜索字段默认又会把模型里可过滤的字段都列出来。于是技术主键和内部审计字段全跑到了用户面前。看起来是“搜索框难用”本质上是你还没做面向用户的字段设计。1.2 Key 字段默认暴露的底层原因要理解这个现象得知道 RAP 世界里 UI 是模型驱动的。CDS 视图里的字段集合决定了 OData 服务的字段集合OData 字段是否出现在 UI 上又由UI注解控制。默认情况下如果 CDS 视图没有显式加UI.hidden: true主键字段就会出现在列表、搜索、详情页里。Fiori Elements 对“哪个字段可以做过滤条件”的判断也有自己的逻辑一般会包含所有未明确排除的字段。结果就是技术键、系统字段、内部字段全被当成业务搜索项。另一个被忽略的点是值帮助缺失。CDS 里有外键关联但如果你不在投影视图上声明Consumption.valueHelpDefinition或Consumption.searchHelp前端就不会生成值帮助对话框。用户面对一个光秃秃的输入框只能靠猜字段格式去输。这比字段暴露更致命——用户至少在 UUID 场景里知道“这是 ID”而完全没有值帮助时任何输入都做不到智能提示。所以搜索体验差不是 Fiori Elements 的锅也不是 RAP 框架的锅是模型层没有为“搜索”这个场景做专门设计。你要做三件事给字段提供可理解的值来源让选中值能携带更多的业务上下文再把不该露给用户看的技术键藏起来。2. 让搜索框真正可搜Value Help 的建模与绑定Value Help 的作用一句话就能说清楚让用户从一个候选列表里选择值而不是亲手去猜。ABAP 里做值帮助有两条路线一条是基于外键关联的自动值帮助一条是用Consumption注解显式指定值帮助实体或经典搜索帮助。两条路线各有适用场景。2.1 基于外键关联自动生成值帮助如果你的 CDS 视图里有外键关联最简单的方式是这样ObjectModel.foreignKey.association: _Customer customerID; associations [1..1] to I_Customer as _Customer on $projection.customerID _Customer.Customer只要关联目标实体包含了业务名称字段Fiori Elements 在运行时能通过 OData 元数据里的ValueList信息自动生成一个值帮助对话框。用户点击输入框旁边的漏斗图标就能看到客户列表选择后客户编号被回填到当前字段。这种做法的优点是不需要额外写太多注解模型自带关系前端就可以识别。局限性也很明显它只能提供单值回填。比如说客户编号选定后客户名称、客户组、联系人这些信息并不会一起带回来。如果用户期望看到“客户编号 客户名称”的回显那默认值帮助是不够的。还有一类常见问题是外键关联指向的目标实体可能很大直接作为值帮助数据源打开对话框就是一次全量 OData 请求动辄几万行。这种场景下我不建议用自动值帮助而是单独建一个轻量级的值帮助视图只挑必要字段再加合理的过滤条件。2.2 用注解显式指定值帮助实体显式定义值帮助是用Consumption.valueHelpDefinition把当前视图字段和某个值帮助视图中的字段绑定起来Consumption.valueHelpDefinition: [ { entity: { name: ZRAP_I_ProductHelp, element: Product }, additionalBinding: [ { localElement: ProductType, element: ProductType } ] } ] productID;这里的entity.name是值帮助视图的 CDS 实体名entity.element是这个实体里用于作为主选择值的字段。additionalBinding是附加绑定允许值帮助对话框里选中的其他字段也一起回填到当前视图。这个能力是后面第 3 章的重点。如果你已经有经典 ABAP 搜索帮助也可以走Consumption.searchHelpConsumption.searchHelp: [{ searchHelp: SH_HU_MATERIAL }] materialNumber;这种方式的优势是可以复用原有搜索帮助里的参数组合和文本逻辑对于从 ECC 迁移过来的项目特别友好。但也要注意经典搜索帮助的很多交互方式在 Fiori Elements 里表现会和 SAP GUI 不一样比如“多个字段联合过滤”“tab 页切换”这些能够保留但界面组件已经是 Fiori 原生形态。2.3 Value Help 从模型到前端按钮的完整链路Value Help 注解写完之后并不是马上就能看到效果。我见过很多刚上手 RAP 的人加了注解以后前端就是不出现漏斗图标最后发现是链路里某个环节断了。完整的链路是这样CDS 投影视图字段上声明值帮助注解该字段必须被UI.selectionField引入到搜索栏或高级搜索区域字段本身在服务里可见没有被逐出 entity setOData 服务的元数据里生成对应的ValueList能力Fiori Elements 渲染输入框时识别到ValueList自动挂上下拉图标和值帮助对话框。最容易漏的是第 2 步。很多字段虽然加了值帮助但没有放到任何selectionField集合里高级搜索里根本没有这个字段的位置值帮助自然不出现。第 4 步也很关键如果你用的是自定义 OData 服务或手工裁剪过元数据需要确认ValueList相关 capability 被保留。排查时我习惯直接看 metadataGET /sap/opu/odata/sap/ZRAP_ORDER_SRV/$metadata在返回的 XML 里搜ValueList如果能看到目标字段说明服务层正常看不到就往注解和 OData 服务发布范围方向查。这个习惯能帮你省掉大量前端调试时间。3. Additional Binding值帮助背后的“第二组绑定”Additional Binding 是容易被低估的一个能力。很多人只把它理解成“值帮助里多回填几个字段”但它在真实项目里解决的是业务上下文传递问题直接影响用户操作效率。3.1 为什么单单一个值帮助不够假设你的订单录入界面里有“供应商编号”和“供应商名称”两个字段。用户在值帮助里选中一家供应商之后理想结果是“供应商编号”被回填到存储字段“供应商名称”也被自动带回显示。否则用户要么自己再输一遍名称要么保存的时候再去关联查一次这种体验在业务录入端会被放大成每天几百次重复操作。更复杂的场景是值帮助里不只一张表。比如供应商值帮助还带了“联系人电话”“所在城市”选完之后界面上的联系电话和城市自动填过去。这就是 Additional Binding 的用武之地——它把值与值之间的关系建立起来而不是只回填单个主键。你可以把它理解成一条“数据导线”值帮助主字段决定选中了哪一行附加绑定决定这一行的其他字段如何流入当前模型的对应位置。3.2 Additional Binding 的字段映射写法写法上additionalBinding是一个数组每个元素包含localElement和element两个属性Consumption.valueHelpDefinition: [ { entity: { name: ZRAP_I_SupplierHelp, element: SupplierKey }, additionalBinding: [ { localElement: SupplierName, element: SupplierName }, { localElement: ContactPhone, element: ContactPhone } ] } ] supplierKey;localElement指的是当前投影视图里的目标字段element指值帮助实体里的源字段。两个字段名可以不一样但类型最好兼容。一个容易踩的坑localElement指向的字段必须在当前 CDS 投影视图里真实存在。如果这个字段只是 UI 上想显示、但投影视图里没定义附加绑定会静默失败不报错、不回填。另一个坑是如果当前模型里已经有字段被UI.hidden: true注释它仍然可以作为localElement参与绑定。因为 Additional Binding 作用在数据层不是 UI 层。这个特性可以用来做隐藏字段的自动赋值后面 Key 隐藏章节会用到。3.3 值帮助视图里用文本字段做附加绑定实际业务里用户通常看到的不是内部键而是有语义的文本。例如供应商编号是合法的业务键但界面上要展示供应商名称名称一般来自文本视图或关联表。我当时是这样设计的建一个专用的供应商值帮助视图它自身关联文本视图并把名称、电话等字段投影出来define view ZRAP_I_SupplierHelp as select from zsupplier as s association [1..1] to zsupplier_t as _Text on s.supplierKey _Text.supplierKey { key s.supplierKey, _Text.supplierName as SupplierName, s.contactPhone as ContactPhone }值帮助视图本身由键字段和文本字段组成前端对话框里可以显示一列“供应商编号”一列“供应商名称”用户按照名称找供应商选中后编号和名称都能带回。如果值帮助源里的文本字段来源于 association 的导航属性在additionalBinding里直接写字段名就行不要写成_Text.SupplierName这种点号导航形式。CDS 注解表达式不支持那种写法写了不会报编译错但运行时不生效。这个坑我在项目里排查了很久最后是挨个试才发现。4. 把 Key 藏起来而不藏功能隐藏字段与替代键的组合Value Help 解决的是“怎么选”Additional Binding 解决的是“选完之后怎么带”接下来要说的是“Key 字段要不要让用户看见”。我的答案很明确绝大多数场景下不要显示技术键但不要只做简单隐藏要把替代键和内部键的关系理清楚。4.1 UI.hidden 会让字段从 UI 消失但不影响服务最直接的做法是给内部主键加上UI.hidden: true keyID;这样列表、详情、搜索界面都不会再展示 UUID。这个操作不会把字段从 OData 服务里移除只是 UI 层隐藏前后台交互时字段仍然存在。但直接隐藏可能导致一个现象列表里所有行看起来都一样因为没有任何一个业务字段被突出展示。所以隐藏 Key 的同时必须保证列表里有业务可读字段比如applyNo、customerName、createdAt。这些字段要放到UI.lineItem列表的前几列而不是把 UUID 从第一列挪到最后一列就完事——用户一样会质疑为什么有一个看不懂的列。我见过有的项目最后干脆把UI.hidden加到了所有技术字段上结果界面确实干净了但用户不知道每行数据代表什么等于从一种难用跳到了另一种难用。隐藏 Key 的正确姿势是“隐藏了技术键但把业务标识顶上”。4.2 用替代键把业务字段变成可搜索定位的标识RAP 的 Behavior Definition 里支持alternate key定义。你可以把业务上唯一的一组字段声明为替代键这样 OData 服务上就能用业务字段组合来做实体定位而不需要暴露内部 UUID。一个简单的行为定义示例define behavior for ZRAP_I_MatApp alias MatApp persistent table zrap_matapp lock master { field ( readonly : update ) applyNo; field ( numbering : managed, readonly : update ) keyID; alternate key ( applyNo ); }设置之后前端对单据的读取、更新、删除请求都可以使用applyNo作为 key predicate。用户看到的是一串有业务含义的“申请单号”而不是 UUID。要注意的是替代键组合在业务上必须保持唯一。UUID 主键的天然唯一性不需要你操心但像“公司代码 单据类型 单据号”这种组合必须自行保证唯一性。如果不唯一更新时可能定位到多条记录产生不可预期结果。我一般会在行为实现的create里加一次唯一性校验或者在持久化表上建唯一索引。另外替代键字段要尽量稳定。如果业务上允许用户修改一个被用作替代键的字段——比如单据类型被改了那同一张单据定位就成问题了。这种字段我始终设置为readonly。4.3 隐藏 Key 之后behavior 里你还会遇到什么隐藏 Key 不只是 UI 配置行为实现必须有配套能力。如果前端不把内部 Key 传给后端后端怎么定位记录如果你定义了替代键OData 会通过 key predicate 把替代键传给行为实现。为了保证行为实现能正确解析你需要在read、update等方法里查出来内部键再执行操作。还有一个容易忽略的点如果你在 UI 里把 Key 隐藏的同时把keyID从服务级字段列表里排除了那么某些需要返回内部键的 API 会被截断前端缓存时可能无法正确关联数据。建议保持 Key 在服务里可见只是用 UI 注解把它隐藏。性能方面也有讲究。用 UUID 主键时数据库定位效率通常很好用多字段替代键时如果没有配套索引查询会走全表扫描。我通常在持久化表创建时就给常用业务键组合加二级索引尤其是applyNo、supplierKey这类高频搜索字段。5. 实战中我踩过的坑和最终方案理论部分讲完了下面说点真刀真枪的排查经验。以下这几个坑都是我在项目上线前后真实遇到过的有些花了大半天才定位。5.1 Value Help 不出候选值先查 OData 元数据值帮助不生效是出现频率最高的一个问题。现象是输入框旁边没有漏斗图标或者点击后对话框空白。我的排查顺序固定如下先打开 Fiori Elements 页面的 Network 面板看加载 metadata 时是否包含ValueList。如果 metadata 里没有检查投影视图是否正确使用了Consumption.valueHelpDefinition以及注解里的实体名是否拼错、实体是否存在。如果注解没错检查字段是否真的被包含在投影视图里。有些字段写在基础 CDS 视图上投影视图里没有当然不会生效。最后检查 OData 服务发布范围确认值帮助实体对应的视图是否被包含在服务里。其中第 4 点最隐蔽。用 RAP 生成服务时有时会根据授权或字段控制裁剪掉一部分实体。我遇到过一次值帮助视图本身没加任何权限控制但服务构建时由于那个视图在别的包引用缺失被静默忽略了。把视图加进服务扩展并重新生成之后问题才解决。5.2 Additional Binding 映射错乱往往是类型或投影视图的问题有一种情况是值帮助能正常弹出来主值能回填但附加字段就是空。这种问题跟值帮助本身无关而是 Additional Binding 链路断了。排查技巧分三步确认localElement在投影视图里真实存在哪怕它是UI.hidden的也没关系确认字段类型匹配abap.char和abap.char直接映射没问题如果一边是abap.raw一边是abap.char回填时会因为转换失败导致空值确认值帮助实体里的源字段不是虚字段。如果element指向的是一个由 CALCULATION 生成或带 fallback 语义的虚拟字段OData 服务端可能不把它作为可回填字段暴露给值帮助。针对类型问题最省事的办法是在值帮助视图里显式做类型转换让输出的字段类型和当前模型字段完全一致。比如把 RAW 转成 CHAR、把 CHAR 转成 NUMC都写在 CDS 里别等运行时去做隐式转换。5.3 Key 隐藏后保存报错让我重新理解了服务字段可见性我们项目早期把内部 Key 直接UI.hidden: true结果 Fiori Elements 在保存时 PATCH 请求缺少 key predicate后端报“实体未找到”。原因不是隐藏本身而是我们在 OData 服务层面把 Key 字段从 entity set 里逐出去了。正确做法不是把 Key 加回来显示在 UI 上而是保证 Key 在服务里可见UI 里隐藏。后来我保留了keyID字段在服务里只加UI.hidden然后把替代键applyNo暴露出来。保存恢复正常列表页也看不到 UUID。这个经验说明UI.hidden控制的是“显示”OData 字段控制的是“能力”两者分开配置。只隐藏不裁剪服务字段是最安全的做法。5.4 搜索框高频输入导致 OData 请求频繁需要控制值帮助体积Value Help 对话框默认是随着输入进行动态过滤的用户每敲一个字符前端就会发一次$filter请求。如果值帮助数据源是几万行的业务主数据这种频繁请求不仅慢还会给后端造成压力。我的方案是给值帮助视图加上费用更低的过滤条件比如按“启用状态”“公司代码”做预过滤同时在 Fiori Elements 里通过配置控制最小输入长度不到两位字符不发请求。这个调整之后对话框响应速度明显改善后端资源占用也降下来了。如果你在值帮助视图里做了额外的计算字段或关联表连接也要注意 OData 服务会不会把每一次交互都做成走关联查询。必要时直接把这个值帮助实体做成单独的表读取或者只投影必要字段减少 join 开销。6. 完整示例从需求到线上效果的落地过程前面讲了这么多我最后用一个完整的小例子把整套组合打法串一遍。这个案例来自一个内部物资申请应用需求不算复杂但把 Value Help、Additional Binding、Key 隐藏全用上了。6.1 需求定义与数据模型设计业务需求是申请人在列表页搜索“供应商名称”不能只按供应商编号选中供应商后界面自动带出供应商名称和联系电话列表第一列显示业务可读的“申请单号”不出现 UUID用户可以把申请单号复制到搜索框直接定位单据。数据模型简化后大致是这样表 ZRAP_MATAPP - key_id RAW16 内部主键 - apply_no CHAR15 业务申请单号 - supplier_key CHAR10 供应商编号 - supplier_name CHAR120 供应商名称 - contact_phone CHAR30 联系电话 - apply_date DATS 申请日期 - create_by CHAR12 创建人表结构里supplier_name和contact_phone是冗余存储的展示辅助字段数据通过值帮助的附加绑定自动填充。冗余字段的好处是列表和搜索不需要频繁 join 主数据表性能更好。6.2 值帮助、附加绑定与替代键的代码组合先在值帮助视图里把供应商主数据和电话号码准备好define view ZRAP_I_SupplierHelp as select from zsupplier as s { key s.supplierKey, s.supplierName, s.contactPhone }然后在投影视图ZRAP_I_MatApp里对供应商字段做值帮助声明Consumption.valueHelpDefinition: [ { entity: { name: ZRAP_I_SupplierHelp, element: SupplierKey }, additionalBinding: [ { localElement: SupplierName, element: SupplierName }, { localElement: ContactPhone, element: ContactPhone } ] } ] supplierKey; UI.hidden: true keyID; UI.searchSupport: { earlyNumberSearch: true } applyNo;earlyNumberSearch用来允许用户输入数字片段直接匹配applyNo。这里虽然applyNo是字符型但业务上基本可看成数字编号开启之后体验提升明显。如果你是 UUID 这类复杂值作为搜索项不建议开因为输入片段没有意义。行为定义里配置替代键define behavior for ZRAP_I_MatApp alias MatApp persistent table zrap_matapp lock master { field ( readonly : update ) applyNo; field ( numbering : managed, readonly : update ) keyID; alternate key ( applyNo ); }最后在 Fiori Elements 的页面配置里把applyNo、supplierName、applyDate放入高级搜索区域keyID因为已经UI.hidden不出现在搜索项中。6.3 最终效果、验证方法和还能优化的方向上线后的实际效果是高级搜索里能看到“供应商名称”输入“华”字值帮助对话框列出所有名称含“华”的供应商选中后供应商编号回填到存储字段供应商名称和联系电话自动带到界面上列表首列是申请单号用户看得到业务标识用户从邮件里复制申请单号直接在列表搜索框粘贴能定位到对应单据。验证这套配置有没有生效我会用浏览器开发者工具看两条关键信息一看 OData metadata 里是否有ValueList定义和附加绑定的字段映射二看选择供应商时 PATCH/CREATE 请求体里是否包含supplierName和contactPhone字段。通过这两个线索基本能判断是前端注解问题还是后端映射问题。如果后续数据量继续增长我会为applyNo和supplierKey建二级索引并把值帮助视图改成只读取必要的字段甚至考虑在值帮助上增加“仅显示启用状态的供应商”这种固定过滤条件避免打开对话框就是全量数据。再往后这个模式可以扩展到员工选择、成本中心选择、科目选择等所有“外键字段 文本展示”的场景。核心思路都一样给用户一个可读的值来源用附加绑定把上下文带过来再把技术键藏到背后。这套组合拳现在已经成为我们新项目 RAP 界面开发的默认基线。如果你也被搜索框、Key 字段这些事折腾得够呛可以直接照这个顺序做一轮模型层调整大概率能少踩几个坑。
返回列表