做 RAP(Restful ABAP Programming Model)项目时间一长,你就会撞上一个特别常见的尴尬:Fiori Elements 默认生成的搜索和列表,永远是“键值对”思维——界面上放一个 Supplier(供应商编号)、SalesOrder(销售订单内部编号),用户看着这些字段根本不知道要填什么。更别提 UUID 这类无意义主键,业务用户对着它完全发懵。于是 Value Help、Additional Binding 与 Key 隐藏这几件事,就成了 RAP 应用做用户体验兜底时绕不开的一套组合拳。
这三件事单拆都不难,难的是把它们组合起来用:Value Help 解决“该填什么”,Additional Binding 解决“填了描述怎么传到后端”,Key 隐藏解决“技术键别出现在界面上”。三者合在一起,搜索和键值对展示才真正从“给开发看”变成“给用户用”。这篇文章就从需求拆解、具体注解写法,一直写到踩坑排错的实录,给正在做 RAP + Fiori Elements 的同学一份能直接抄的作业。
1. 为什么默认的搜索和列表会让用户头疼
1.1 一个典型销售订单场景的尴尬操作
客户新上一个销售订单查询应用,数据量不大,但主键是内部号加 UUID 组合。Fiori Elements 默认把搜索字段排成:SalesOrder(销售订单内部号)、Customer(客户编号)、CompanyCode(公司代码)。开发看着没问题,业务用户直接投诉:我一个销售员,手里只有“上海XX贸易有限公司”这个客户名称,你让我输入客户编号,我上哪儿查去?
这不是个例。很多内部系统的主键是流水号、UUID、GUID,用户根本没法记忆。更烦的是列表页还把这些技术键一列列铺开,一屏里全是000000000123456789、1E2F3A4B-…这种毫无业务含义的数据。用户在系统里看到的应该是“客户名称”“供应商名称”“销售订单号(人话版本)”,而不是数据库里的物理主键。
打个比方,你去快递柜取件,正常情况下输入手机号后四位就能开柜;要是系统非要你输入运单号,还得是完整的那种,90% 的人当场就卡住。RAP 应用的搜索和键值展示没做优化,就是这个效果。
1.2 把问题拆成三层:录入、传递、展示
这个问题的解法不是单一注解能搞定的。我习惯把它拆成三个层次:
| 层次 | 用户痛点 | 目标 | 典型手段 |
|---|---|---|---|
| 录入层 | 不知道搜索字段该填什么 | 输入框能查、能选、能模糊搜索 | Value Help |
| 传递层 | 用户输入描述文本,后端需要按 Key 过滤 | 描述文本与主键自动转换 | Additional Binding |
| 展示层 | 列表页、详情页暴露技术键 | 界面上只出现业务字段 | Key 隐藏 |
三个动作是一个闭环:Value Help 让用户选得舒服,选中后 Additional Binding 把描述对应的 Key 塞进过滤条件,最终列表和详情页里 Key 不出现、描述字段正常显示。缺了任何一个,体验都不完整。只做 Value Help 不做 Key 隐藏,用户还是要面对一堆技术列;只做 Key 隐藏不做 Additional Binding,搜索和展示之间就断了链路。
这个拆解也决定了后面的配置顺序:先做数据来源(Value Help),再做字段绑定(Additional Binding),最后做展示裁剪(Key 隐藏)。代码层面它们不是同一个注解,思路却是同一条线。
2. Value Help 落地:给输入框装一个“查询雷达”
2.1 通过 CDS 注解声明值帮助
RAP 里最常用的值帮助实现方式,是在 CDS 视图上用@Consumption.valueHelpDefinition声明。这个注解的作用简单直接:告诉 Fiori Elements,哪个字段的输入框要挂一个值帮助弹窗,弹窗的数据从哪个视图来。
@Consumption.valueHelpDefinition: [ { entity: { name: 'ZC_RAP_VH_Supplier', element: 'Supplier' } } ] supplier_key: abap.char(10);这段注解挂在实体视图的 Supplier 字段上。entity指向值帮助视图ZC_RAP_VH_Supplier,element指向该视图里负责返回 Key 的那个元素。用户在弹窗里选中一条记录,Fiori Elements 会把这条记录的 Supplier 值回填到当前搜索字段。
这个“选主键、回填主键”的机制,很多人一开始会理解成“选中一个名称,输入框里显示名称”。实际上输入框显示的到底是什么,取决于你把它绑到一个描述字段还是 Key 字段。更常见的做法是:搜索字段本身是描述字段(比如 SupplierName),用它来触发值帮助,选中后值帮助视图里那个 element 指向的 Key 会被作为关联值带出去。这就是下一章 Additional Binding 的入口,这里先不展开。
2.2 值帮助视图上的文本关联与列裁剪
值帮助视图本身也要做好文本关联,否则弹窗里全是编号没有名称,等于没优化。典型写法如下:
@EndUserText.label: 'Supplier Value Help' @ObjectModel.text.element: ['SupplierName'] define view ZC_RAP_VH_Supplier as select from i_supplier { key supplier as Supplier, supplier_name as SupplierName, country as Country }@ObjectModel.text.element是值帮助视图里非常关键的一行。它让 SupplierName 成为 Supplier 这个 Key 的“文本展示”,Fiori Elements 在弹窗、列表、以及选中后的回填场景里,都会优先去消费这个文本字段。
还有一件事值得提醒:值帮助视图不要贪多,只保留key + 文本 + 必要的过滤字段就够了。有的项目把值帮助视图做成一个大宽表,几十个字段全扔进去,结果弹窗里列一大排,用户看着头大,搜索性能也直线下降。Value Help 的定位是“快速定位一条记录”,不是业务查询报表。
2.3 Value Help 的一些使用细节与性能建议
实际项目中我积累了几条关于 Value Help 的经验:
第一,值帮助视图必须暴露在 Service Binding 里,否则注解写了也白写。报错不会太明显,常见现象是搜索字段整个消失,或者输入框旁边根本没有值帮助图标。排查时先去 Service Binding 的 Entity Set 列表里确认视图有没有被暴露。
第二,如果值帮助数据量较大,建议在视图上加默认过滤字段。比如供应商按国家过滤,客户按销售组织过滤,通过@Consumption.filter定义弹窗里的默认筛选项,让用户先缩小范围再搜索,效果比一上来全表搜好得多。
@Consumption.filter: { defaultValue: 'CN' }第三,Draft 版本的赋值帮助行为要单独观察。列表搜索场景连着 Draft 时会有点绕,建议在测试阶段就把 Draft Enabled 的场景一起覆盖,不要只在 Active 版本里验证。
3. Additional Binding:用描述搜索,用 Key 过滤
3.1 Additional Binding 到底解决什么问题
用户友好度做到“能选”还不够,还有个隐藏问题:如果搜索字段是 SupplierName,而数据库真正需要精确过滤的是 Supplier 这个编号,那用户在搜索框里输入的“上海XX贸易有限公司”,后端拿什么条件去查询?
最简单的方案是后端直接对 SupplierName 做字符串模糊匹配。但这样做有副作用:文本字段往往没有索引,数据库层面容易慢;用户在值帮助里选的是一个确定供应商,他期望的也是精确结果,结果系统跑去对名称做 LIKE 查询,逻辑上就不对。
Additional Binding 解决的就是这个“显示字段”和“过滤字段”不一致的问题。它的机制是:搜索界面上用户看到的是 SupplierName,操作时输入或选择 SupplierName;与此同时,Fiori Elements 会把值帮助里选中的 Supplier Key 作为额外绑定条件,一起放进过滤请求里发给后端。用户无感知,后端却拿到了精确的 Key 条件。
还是拿餐厅打比方:顾客在菜单上点“招牌牛肉面”,服务员在后厨下单时记的是“套餐18号的牛肉面”。你选的是“人话”,系统记录的是“键值”,Additional Binding 就是那个默默帮你翻译的服务员。
3.2 在 CDS 注解里配置 Additional Binding
在 RAP 的 CDS 视图里,Additional Binding 一般配合@UI.SelectionField一起使用。SelectionField 定义的是“搜索界面上的字段”,AdditionalBinding 定义“当这个搜索字段出现值时,还应该把哪个关联键一起带上”。
@UI.SelectionField: [ { position: 10, element: 'SupplierName', additionalBinding: [ { element: 'Supplier' } ] } ] define view ZC_RAP_SO_TP as projection on ZI_RAP_SO { key SalesOrder, Supplier, SupplierName, ... }代码里三层意思:
position: 10控制搜索字段在搜索区的排列顺序,数字越小越靠前。它不影响业务逻辑,只负责界面布局。element: 'SupplierName'表示搜索界面上用户操作的是哪个字段。它可以是描述字段、文本字段,不一定非得是 Key。additionalBinding: [ { element: 'Supplier' } ]表示用户填了这个搜索字段后,除了 SupplierName 本身,还要把 Supplier 作为附加条件一并传给后端过滤。
这个配置写完,用户在搜索区输入供应商名称的一部分,或者通过值帮助选中一个供应商,后端拿到的是两条过滤条件:SupplierName 上的条件 + Supplier 上的精确 Key 条件。前者照顾用户输入的灵活性,后者保证结果精确。
3.3 在 Fiori Elements 中通过注解文件配置
有的项目里 CDS 视图层不适合动注释,或者搜索配置要跟着前端应用走,这时候可以用 Fiori Elements 的 annotation.xml 来配。效果是一样的,只是位置不同。
<Annotations Target="ZC_RAP_SO_TP"> <Annotation Term="com.sap.vocabularies.UI.v1.SelectionFields"> <Collection> <Record Type="com.sap.vocabularies.UI.v1.SelectionField"> <PropertyValue Property="Name" String="SupplierName"/> <PropertyValue Property="AdditionalBinding"> <Collection> <Record Type="com.sap.vocabularies.UI.v1.SelectionField"> <PropertyValue Property="Name" String="Supplier"/> </Record> </Collection> </PropertyValue> </Record> </Collection> </Annotation> </Annotations>两种写法效果等价。区别在于,CDS 注解是后端的、跟随视图定义走的;annotation.xml 是前端的、跟随 Fiori Elements 应用走的。我个人推荐优先写在 CDS 视图层。原因很简单:RAP 项目的注解尽量集中管理,别散落到前端各个应用里,否则字段一变,前端配置忘改,线上又开始出“搜索不到”的诡异问题。
3.4 多字段组合绑定的实战用法
Additional Binding 不是只能一对一,它也支持多个搜索字段各自绑定自己的 Key。比如销售订单搜索区里同时有“客户名称”和“销售组织名称”,可以分别配置:
@UI.SelectionField: [ { position: 10, element: 'CustomerName', additionalBinding: [ { element: 'Customer' } ] }, { position: 20, element: 'SalesOrgName', additionalBinding: [ { element: 'SalesOrganization' } ] } ]这里有个容易踩坑的点:两个搜索字段千万不要绑定到同一个 Key 元素。比如客户名称绑定了 Customer,销售组织名称也“顺便”绑定到 Customer,那用户同时填两个框时,后端会收到两个 Customer 过滤条件,相互打架。Additional Binding 的每个目标 Key 在一组搜索条件里只能出现一次,这是我在代码评审时必查的一条。
| 搜索字段 | 绑定键字段 | 场景建议 |
|---|---|---|
| SupplierName | Supplier | 单层主数据,如供应商、客户 |
| CustomerName | Customer | 单层主数据,但注意客户主数据可能多范围 |
| LongDescription | KeyField | 文本搜索场景,慎用,注意性能 |
| SalesOrgName | SalesOrganization | 组织架构等“有名称的编码” |
4. Key 隐藏:让技术键不再打扰用户
4.1 Key 不能删,只能藏
很多新手做到这里会问:既然 Key 不想让用户看到,那我能不能直接在 CDS 视图里把 Key 字段删掉?
答案是:不能物理删。RAP 的行为定义(BDEF)里,Key 是 Update、Delete 等操作定位行数据的根本依据。Fiori Elements 的行选中、跳转详情、编辑保存,都依赖行数据自带的主键上下文。你把 CDS 投影视图里的 Key 字段剪掉,服务根本起不来,或者起起来了也无法正常维护数据。
所以正确做法是:Key 在数据模型和行为层保留,在 UI 展示层隐藏/裁剪。这是两个不同的层面,别混在一起。
4.2 用 LineItem 注解控制列表列
列表页展示由@UI.LineItem注解控制。想让 Key 不出现在列表里,最直接的办法是:不要在 LineItem 集合里放 Key 字段。
@UI.LineItem: [ { position: 10, label: '供应商名称', element: 'SupplierName' }, { position: 20, label: '国家/地区', element: 'Country' } ]这个写法比“把所有字段都写上去再把 Key 标 hidden”更干净。列表要展示什么,就在 LineItem 里列什么;没列进去的字段,在列表里自然不显示,但数据上下文仍在,不影响行操作。
如果某个字段确实需要参与 UI 逻辑,但不想被看到,可以用隐藏注解做总控:
@UI.hidden: true supplier_key: abap.char(10);@UI.hidden: true是“一刀切”的隐藏方式,在列表、表单、字段组里都生效。更要紧的是隐藏含义,你还是得拿捏好。
4.3 隐藏 Key 的边界场景要提前想清楚
隐藏 Key 之后有两个场景容易出问题。
第一,对象页的 HeaderInfo。如果 HeaderInfo 的 Title 绑定的仍是 Key 字段,那列表隐藏了,点进去详情页标题又是“订单号 000000123”,这就前功尽弃了。正确做法是 Title 绑描述字段,比如“订单编号 + 客户名称”这种组合。
@UI.HeaderInfo: { typeName: '销售订单', typeNamePlural: '销售订单', title: { type: #STANDARD, value: 'SalesOrder' }, description: { value: 'CustomerName' } }第二,自定义操作(Custom Action)里通过前端按钮传参时会引用行上下文,如果你把上下文里的 Key 字段也隐藏掉,部分前端逻辑可能拿不到值。这种情况不是把 Key 加回来,而是确保自定义操作所需的值在数据上下文中存在即可。隐藏不代表不存在,OData V4 的请求里 Key 通常仍然会被携带,只是 UI 不渲染成可见列。
4.4 什么时候必须保留 Key
不是所有场景都适合隐藏 Key。我总结了几个“必须保留 Key”的情况,供你参考:
| 场景 | 原因 | 建议 |
|---|---|---|
| 系统间接口对接 | 外部系统需要稳定主键来关联数据 | 单独设导出视图或 API,不隐藏 |
| 用户需要手动记录并核对编号 | 业务习惯,例如财务对账 | 保留一个“编号”列,位置排在靠后 |
| 排错/支持阶段 | 技术支持需要根据主键定位数据 | 临时用版本备份视图或日志暴露 Key |
这里没有一律之规,核心是“让业务用户不困惑,同时该查到底的时候有依据”。与其一刀切隐藏,不如按场景做配置。比如列表里放一个“业务编号”字段,它本身也是主键但带有业务含义(如订单号、凭证号),这个就不属于“技术键”,不该隐藏;真正的技术键是 UUID、GUID、无意义的内部自增 ID,这些才是隐藏的重点对象。
5. 典型问题排查:这三板斧翻车实录
5.1 Value Help 不出现,甚至搜索字段直接消失
之前有次项目上线前,同事跑来问:值帮助注解写了,Service Binding 也刷新了,但前台的搜索字段就是没有值帮助图标,甚至整个字段在搜索区里都看不到。
排查下来原因很典型:值帮助视图确实在服务里,但@Consumption.valueHelpDefinition里entity指向的投影视图没有在 Service Binding 里暴露。Fiori Elements 在读取元数据时找不到那个实体,就干脆把消费者的整个字段都吞掉了。这个报错有时候并不会在后台日志里醒目地冒出来,前端只会表现为“字段消失”。
处理办法:检查值帮助视图在 Service Binding 的 Entity Set 中是否存在,如果不存在,补上暴露;如果存在还不行,去 /IWFND/MAINT_SERVICE 看激活状态,很多坑是旧服务缓存没刷新。
另外还要看一下注解挂在哪个视图上。如果你在投影视图(Projection View)里写值帮助,而值帮助视图又是基于底层视图的,建议把 valueHelpDefinition 挂在底层视图或投影视图的同一层,保持注解层级一致,避免元数据里互相找不到。
5.2 Additional Binding 没生效,后端收到的还是描述文本
Additional Binding 配置完,用 OData 调试工具看请求,发现后端还是只拿到 SupplierName 条件,Supplier 的 Key 条件根本没进来。
这个问题大多出在注解作用域上。@UI.SelectionField写在 CDS 视图定义里没问题,但如果你同时又在 Fiori Elements 前端的 annotation.xml 里配了一套 SelectionFields,两套配置会合并或者冲突,前端最终采用的是后者的排序和绑定关系。结果就是你压在 CDS 里的additionalBinding等于没写。
解决方式是统一配置来源。要么全部放在 CDS 视图层,要么全部放在前端注解文件里,不要两处各写一半。我遇到的大多数“加了不生效”问题,最后都查出来是配置有重复来源。
另一个易错点是:Additional Binding 只有在用户通过值帮助实际选中一条记录后,才会被可靠地带入请求。如果用户直接在搜索框里敲一段文本然后回车,Fiori Elements 有可能只把这段文本当作 SupplierName 的过滤条件,关联的 Supplier 可能是空的。所以搜索体验设计上应该引导用户“点开值帮助选择”,不要寄希望于手输描述后系统自动翻译成 Key。
5.3 Key 隐藏后,自定义操作拿不到主键
隐藏 Key 后,有同事写了个自定义按钮,想在点击时读取当前行的主键,后端一直提示取到的值不对。查到最后发现,他在自定义操作里绑定的是“可见字段”中的 Key,Key 隐藏后这个值自然就没有了。
这里要理解 Fiori Elements 的一个基础机制:你通过行上下文(context)拿到的数据,不一定包含所有字段。如果字段在 UI 注解中被隐藏,有些 LIB 组件可能只消费可见字段。解决方法不是把 Key 加回列表,而是在自定义动作的表单/弹窗里,通过注解显式绑定你要传的值,让它走在界面可见但渲染不出来的路径里。
做法:在确认弹窗或者动作参数里手动绑定行元素上下文,不依赖列表展示字段。RAP 后台的行为定义里,Key 本身作为%key是存在的,问题只在前端“传值”环节。
5.4 值帮助搜不出数据,模糊搜索失效
Value Help 弹窗打开后,输入关键词搜不出结果,原因多半出在值帮助视图的字段没有做模糊搜索支持,或字段类型定义导致搜索匹配不上。Fiori Elements 的值帮助默认会对文本字段做通配符匹配,但如果你查的字段被定义成 Key 字段,且底层数据库列是 UUID、GUID 这种类型,那模糊搜索天然走不通。
建议:值帮助视图里专门放一个“可搜索的描述字段”,比如 SupplierName,配合@ObjectModel.text.element让弹窗展示和搜索都走这个文本字段。不要试图拿 UUID 类型的 Key 去做模糊查询。
5.5 常见问题速查表
| 现象 | 可能原因 | 解决方向 |
|---|---|---|
| 值帮助图标不出现 | 值帮助视图未暴露到 Service | 补暴露,刷新缓存 |
| 搜索字段整个消失 | valueHelpDefinitionentity配置错误 | 检查视图名与 element 名 |
| 后端收到的是文本而不是 Key | Additional Binding 配置来源冲突 | 统一 CDS 或前端配置,二选一 |
| 手输搜索时 AB 不生效 | 非值帮助选择,额外绑定无法传递 | 引导用户使用值帮助 |
| 隐藏 Key 后自定义操作报错 | 前端传参依赖可见字段 | 显式绑定行上下文参数 |
| 值帮助搜不出结果 | Key 字段被当作搜索字段 | 改用文本描述字段做搜索 |
6. 整套打法组合实例:从 CDS 到注解一次打通
6.1 一个完整的实体视图配置
光讲注解太散,我把整套组合放在一个销售订单的投影视图里串一遍,方便你直接参考。
底层视图先确保有 Supplier 和 SupplierName 两个字段,Supplier 是 Key,SupplierName 是文本。投影视图里把 Key 保留,但列表展示不引用它:
@EndUserText.label: '销售订单投影视图' @ObjectModel.text.element: ['SupplierName'] define view ZC_RAP_SO_TP as projection on ZI_RAP_SO { key SalesOrder, SalesOrderType, Supplier, SupplierName, CustomerName, CompanyCode, Currency, GrossAmount }注意两个点:Supplier是真正的主键,不能去掉;SupplierName用于列表展示和搜索。投影视图里没有写@UI.hidden,但后续 LineItem 里不引用Supplier,它就不会出现。
6.2 搜索区的配置
搜索区采用@UI.SelectionField控制,这里把 Additional Binding 配上:
@UI.SelectionField: [ { position: 10, element: 'SupplierName', additionalBinding: [ { element: 'Supplier' } ] }, { position: 20, element: 'SalesOrder' } ] define view ZC_RAP_SO_TP as projection on ZI_RAP_SO { ... }这样配置后,搜索区第一个输入框显示“供应商名称”,用户直接填名称;选中或输入后,后端过滤条件里带上Supplier的精确 Key。
6.3 列表展示与值帮助的最终效果
值帮助视图和列表展示分别配置,最后组合起来的效果是:
- 搜索区:输入框显示“供应商名称”,带值帮助图标,用户可以模糊搜索名称并选中。
- 列表页:只显示 SalesOrder、SalesOrderType、SupplierName、CustomerName 等业务字段,不显示 Supplier 技术键。
- 详情页:HeaderInfo 的标题绑定 SalesOrder 业务编号,描述绑定 CustomerName,整页看不到无意义的 Key。
这个组合跑起来后,业务用户的反馈普遍是“这个系统终于说人话了”。你在界面上不会觉得有什么特别重的东西,但逐渐你会发现,用户开始自己探索搜索功能了——以前他们是不打开的,因为不知道能填什么。
6.4 组合落地时我最看重的三件事
做完实例,我再说三条从项目里沉淀下来的经验。
第一,三件套之间不是“有了就行”,而是顺序要对。Value Help 让用户找到描述文本,Additional Binding 把描述变 Key,Key 隐藏保证 Key 不出来破坏体验。这个链路少一环,搜索就会从“友好”退回“半残废”。
第二,注解分层要克制。CDS 层能配完的,就别再去前端注解文件里重复配。一旦两端都有配置,维护时你会不断遇到“哪边才是生效的”这种问题。前面 5.2 提到的坑,十有八九都是从双份配置开始的。
第三,Key 隐藏不是“一刀切隐藏所有主键”。有业务含义的编号(销售订单号、凭证号)该留就留,真正要藏的是 UUID、自增内码、GUID 这种纯技术键。分清这两类,后续才不会因为“用户想记住订单号去查接口”而被迫返工。
最后再分享一个我常用的土办法:做完这套配置后,不要只看 Fiori Elements 界面好不好看,直接打开浏览器的 Network 请求,看看搜索时发出的过滤条件里是不是同时带上了描述字段和 Key 字段。如果请求里只有描述字段没有 Key,说明 Additional Binding 没走通,你界面再顺滑也白搭。这个检查动作花不了五分钟,但能帮你堵掉一大半“看着没问题、实际逻辑不对”的隐藏地雷。