企业软件 · 新资源

SAP Fiori 设计系统.

通过 UI5 Web Components 使用 SAP Fiori 的 Horizon 主题:采用真实的主题参数、明确的内容密度、语义化的值状态,以及带本地验证的采购申请布局。

OpenDesign 指南8 个章节37 条参考条目原始应用指南
01

SAP Fiori 设计系统界面预览

编辑请购单、验证数量、更改密度,并生成本地摘要。

采购申请工作区打开完整示例
离线的 HTML 实现,采用 Morning Horizon 参数和 UI5 表单组合方式。原生包示例另行提供。· 实现参考
02

SAP Fiori 设计系统设计参考

Morning Horizon(sap_horizon)源参数,以及当前 UI5 Web Components 文档。下方的 Less @sap 名称是源参数;浏览器样式使用对应的 --sap 自定义属性。受保护尺寸是观测值,并非建议覆盖的参数。

Horizon 颜色角色

来源
名称 / 规则值 / 配置用途
@sapBrandColor#0070f2 主品牌角色。
@sapBackgroundColor#f5f6f7 页面背景。
@sapBaseColor#ffffff 基础界面表面。
@sapTextColor#131e29 主文本。
@sapNegativeColor#aa0808 负向状态。
@sapCriticalColor#e76500 关键角色;文本使用单独派生的角色。
@sapPositiveColor#256f3a 正向状态。
@sapInformationBackground#e1f4ff 信息提示界面表面。
@sapWarningBackground#fff8d6 关键提示界面表面。

字体与几何尺寸

来源
名称 / 规则值 / 配置用途
@sapFontFamily"72", "72full", Arial, Helvetica, sans-serif已发布的字体栈。
@sapFontSize0.875rem基础字号。
@sapContent_LineHeight1.5内容行高倍数。
@sapElement_Height2.25rem受保护的常规尺寸。
@sapElement_Compact_Height1.625rem受保护的紧凑尺寸。
@sapElement_LineHeight2.75rem受保护的常规行框。
@sapElement_Compact_LineHeight2rem受保护的紧凑行框。
@sapButton_BorderCornerRadius0.5rem按钮圆角参数。
@sapContent_FocusWidth0.125rem焦点宽度。
@sapContent_FocusColor#0032a5 焦点颜色。

主题与密度设置

来源
名称 / 规则值 / 配置用途
sap_horizonMorning Horizon组件内置默认主题。
sap_horizon_darkEvening Horizon其他主题需要额外资源。
sap_horizon_hcb / sap_horizon_hcwHigh contrast black / white分别支持多种高对比度主题。
Assets.jsOne import per component library used注册其他主题资源。
setTheme(name)@ui5/webcomponents-base/dist/config/Theme.js通过公开的 API 切换主题。

Select 约定

来源
名称 / 规则值 / 配置用途
ui5-select valueMatches option value or text不要与选项的 selected 标记混用。
selectedOptionRead-only IOption读取选中的选项对象。
changeevent.detail.selectedOption已确认的选择变更。
live-changeNavigation preview不要将其视为已提交的业务选择。
accessible-name-refID of visible labeling element将可见文本与控件关联。

输入与反馈

来源
名称 / 规则值 / 配置用途
valueString显式转换数值输入。
inputEach keystroke用于实时估算总额。
changeEnter or focusout这不同于每次按键都触发输入。
value-stateNone / Negative / Critical / Information / Positive当前 API 中的状态名称。
slot="valueStateMessage"One root element针对特定字段的说明。

消息条

来源
名称 / 规则值 / 配置用途
designInformation / Positive / Critical / Negative用于应用程序的语义化消息。
hide-close-buttonBoolean attribute始终显示必需的说明。
default slotPrefer text content避免将消息条变成复杂面板。
03

SAP Fiori 设计系统完整指南

实用指南,介绍如何为真实界面选择、应用和评审此系统。

DESIGN-sap-fiori.md

完整指导、实现起点和源代码链接。

下载指南

SAP Fiori 设计系统设计与 UI5 实现的适用范围不同

一个具体的切入点

SAP Fiori 描述的是企业级用户体验。本参考选择 UI5 Web Components,提供一个可实现的采购申请示例。这并不意味着安装这些组件就会创建 SAPUI5 应用、Fiori elements 页面布局,或获准执行的 SAP 业务事务。

摘自本 OpenDesign 指南

主要组件包提供 Input、Select 和 Button。更高级的 Fiori 组件位于单独的包中。只导入本示例用到的模块,可以让入口更明确,也避免让人误以为存在不受支持的全组件 CDN。

阅读配套规范

SAP Fiori 设计系统:使用具名 Horizon 参数,而非通用蓝色配色

界面表面与状态角色

Morning Horizon 将页面呈现为浅灰色界面表面,并使用白色作为基础表面。品牌蓝和文本颜色由不同的参数定义。Negative、Critical、Positive 和 Informative 表示语义角色,并非供卡片轮流使用的一组装饰色。

预览使用信息条展示准备好的摘要,因为本地准备并不等于采购审批。示例中的商品价格、成本中心和审批说明均为原创样例数据,不应被视为 SAP 提供的业务规则。

阅读配套规范

SAP Fiori 设计系统:紧凑模式是组件设置,不是缩小版截图

将密度应用于有意义的区域

UI5 支持在根元素、body、区域或组件上添加紧凑模式标记。属性 data-ui5-compact-size 和类 ui5-content-density-compact 是公开机制。动态更改标记不需要单独的密度 API。

仅在交互仍然合适时,才在密集的桌面任务中使用紧凑控件。嵌入式视图会缩小控件间距和行间距,但保留相同的信息。窄屏布局会将区域纵向堆叠,而不是把每个标签和按钮都缩小。

html
<section data-ui5-compact-size>
  <ui5-input accessible-name="Request title"></ui5-input>
</section>

代码参考

阅读配套规范

SAP Fiori 设计系统:72 字体系列及其加载行为

字体可用性是设置的一部分

Horizon 主题会在回退字体之前指定 72 和 72full。UI5 文档介绍了如何停用默认字体加载,以及如何在本地托管字体。便捷入口 FontFace.css 标记为实验性功能;如果选择此方式,请固定其路径并在之后重新检查。

独立预览明确使用 Arial 作为离线回退字体。原生 UI5 示例使用已发布的 font-family 变量。这样的区分可以避免把外观相似的回退字体截图误当作 SAP 字体资源已成功加载的证明。

阅读配套规范

SAP Fiori 设计系统:Select 有已确认和临时变更

选择一种受控的选择策略

Select 的 value 必须与某个 Option 的 value 或文本相匹配。不建议同时设置 Select 的 value 和 Option 的 selected 标记;如果 value 不存在,就不会选中任何选项。本示例先初始化一个有效的成本中心选项,再通过 Select 的 value 读取结果。

change 事件会携带选中的选项。浏览选项以及按 Escape 键还原选择时,也会触发 live-change。业务数据应使用已确认的选择;临时变更则留给预览使用。

  • 渲染这些标签前,先导入 Select 和 Option 注册模块。
  • 通过 accessible-name-ref 将可见的成本中心标签与控件关联。
  • 本示例中只使用 value 这一种选择策略。

阅读配套规范

SAP Fiori 设计系统:数值输入仍需应用层验证

值状态与消息应配套使用

即使输入类型为 Number,Input 的 value 仍是字符串。执行乘以示例价格的计算前,应先进行转换,并检查数值是否为有限整数且在范围内。空值不能被当作有效的零。当前语义化错误状态使用 Negative,而不是自行编造 Error 枚举值。

valueStateMessage 插槽会提供一个包含具体说明的根元素。实时总额会监听 input 事件;只有用户触发操作后,才会更新准备好的摘要。

  1. 从对应组件读取标题和数值字符串。
  2. 在本示例中,标题不能为空,数量必须是 1 到 20 之间的整数。
  3. 将受影响控件的状态标记为 Negative,并保留用户输入的值。
  4. 只有两项条件都满足后,才准备摘要。

阅读配套规范

SAP Fiori 设计系统:让结果简洁明了,并保持本地处理

用消息条呈现简要结果

MessageStrip 支持语义化设计和关闭控件。对于必需的说明,可以隐藏关闭按钮,确保说明始终可见。其默认插槽最好只放文本,不要扩展成表单或仪表板。

摘要包含标题、数量、示例总额和成本中心。最后一句说明尚未提交任何内容。实际服务会将此本地步骤替换为获准的请求,并返回包含真实后端状态的响应。

阅读配套规范

SAP Fiori 设计系统:主题资源与模块注册

主题选择需要对应的资源

Morning Horizon 是文档所述的默认主题,并内置于组件中。其他主题需要为所用的每个库提供 Assets.js 模块。然后,setTheme 才能选择 Evening Horizon 或某个高对比度变体。选择一个深色主题名称,却不确认相应资源已加载,并不算完整集成。

所提供的入口文件会先注册每个自定义元素,再创建标记。Vite 会处理动态 ES 模块导入和 JSON 资源。请单独编译原生项目,并验证字体、主题加载和键盘行为,不要将其与离线 HTML 示例混为一谈。

阅读配套规范

SAP Fiori 设计系统组件示例

完整的独立交互示例

将完整文档保存为 index.html,并在浏览器中打开。这是对 Morning Horizon 参数和 UI5 表单组合方式的离线 HTML 演绎。原生程序包示例另行提供。

html
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Fiori purchase request</title><style>
*{box-sizing:border-box}body{margin:0;color:#131e29;background:#f5f6f7;font:14px/1.5 Arial,sans-serif}header{background:white;padding:12px 24px;display:flex;gap:16px;align-items:center;border-bottom:1px solid #d5dadd}.symbol{width:32px;height:32px;display:grid;place-items:center;background:#0070f2;color:white;border-radius:8px;font-weight:bold}header strong{font-size:16px}header span:last-child{margin-left:auto;color:#556b82;font-size:12px}main{max-width:1100px;margin:auto;padding:24px}.objecthead{display:flex;justify-content:space-between;gap:20px;align-items:start;margin-bottom:24px}h1{font-size:28px;line-height:36px;margin:0 0 6px}h2{font-size:18px;margin:0 0 16px}.subtitle{color:#556b82;margin:0}.draft{background:#e1f4ff;color:#0032a5;padding:6px 12px;border-radius:6px}.layout{display:grid;grid-template-columns:minmax(0,1fr) 280px;gap:24px}.panel{background:white;border-radius:12px;padding:24px;border:1px solid #d5dadd}label{display:block;margin-bottom:6px;color:#556b82}input,select{width:100%;height:44px;border:1px solid #758ca4;border-radius:8px;padding:8px 12px;background:#fff;font:inherit;color:inherit}input:focus,select:focus,button:focus-visible{outline:2px solid #0032a5;outline-offset:3px}.fields{display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:24px}.fields>div:first-child{grid-column:1/-1}.line{border-top:1px solid #d5dadd;padding-top:20px;display:grid;grid-template-columns:64px minmax(0,1fr) auto;gap:16px;align-items:center}.monitor{background:#e1f4ff;border:2px solid #556b82;height:42px;border-radius:4px;position:relative;margin-bottom:12px}.monitor:after{content:'';position:absolute;bottom:-12px;left:25%;width:50%;height:10px;border-bottom:3px solid #556b82;border-left:8px solid transparent;border-right:8px solid transparent}.line strong{display:block}.line small{color:#556b82}.money{font-size:24px;font-weight:600;white-space:nowrap}.unit{font-size:12px;color:#556b82}.total{display:flex;justify-content:space-between;padding-top:24px;font-weight:600}.controls{display:flex;gap:12px;flex-wrap:wrap;margin-top:24px}button{border:1px solid #0070f2;background:white;color:#0057b8;padding:10px 16px;border-radius:8px;font:600 14px/20px Arial;cursor:pointer}button.primary{background:#0070f2;color:white}button:hover{filter:brightness(.95)}.steps{list-style:none;padding:0;margin:0;counter-reset:step}.steps li{position:relative;padding:0 0 24px 36px;color:#556b82}.steps li:before{counter-increment:step;content:counter(step);position:absolute;left:0;top:0;border:1px solid #758ca4;border-radius:50%;width:24px;height:24px;text-align:center;background:white}.steps strong{display:block;color:#131e29}.steps li:first-child:before{background:#e1f4ff;color:#0057b8;border-color:#0070f2}.strip{margin-top:24px;background:#e1f4ff;border:1px solid #0070f2;padding:12px 16px;border-radius:8px}.strip.error{background:#ffeaf4;border-color:#aa0808;color:#aa0808}.foot{font-size:12px;color:#556b82;margin-top:24px}.compact input,.compact select{height:32px;padding:4px 8px}.compact .fields{gap:12px}.compact .panel{padding:16px}.compact .line{padding-top:12px}.compact button{padding:6px 12px}.hint{font-size:12px;color:#556b82;margin:6px 0 0}@media(max-width:700px){main{padding:20px 16px}.layout{grid-template-columns:1fr}.fields{grid-template-columns:1fr}.objecthead{display:block}.draft{display:inline-block;margin-top:12px}.line{grid-template-columns:48px minmax(0,1fr)}.line>.money{grid-column:2}.panel{padding:20px}header{padding:12px 16px}}
</style><header><span class="symbol">P</span><strong>Procurement workspace</strong><span>Sample environment</span></header><main><div class="objecthead"><div><h1>Purchase request</h1><p class="subtitle">PR-DRAFT · Workplace equipment</p></div><span class="draft">Draft · not submitted</span></div><div class="layout"><section class="panel"><h2>Request details</h2><form id="form" novalidate><div class="fields"><div><label for="title">Request title</label><input id="title" placeholder="e.g. Studio display upgrade" aria-describedby="notice"></div><div><label for="team">Cost center</label><select id="team"><option value="Design · CC-240">Design · CC-240</option><option value="Research · CC-360">Research · CC-360</option></select></div><div><label for="qty">Monitor quantity</label><input id="qty" type="number" min="1" max="20" step="1" value="2" aria-describedby="qty-help notice"><p id="qty-help" class="hint">Whole number from 1 to 20.</p></div></div><div class="line"><div class="monitor" aria-hidden="true"></div><div><strong>27-inch studio monitor</strong><small>Equipment · Item 100240</small></div><div class="money">€320 <span class="unit">/ unit</span></div></div><div class="total"><span>Estimated line total</span><output id="total">€640</output></div><div class="controls"><button class="primary">Prepare summary</button><button type="button" id="density" aria-pressed="false">Compact controls</button></div></form></section><aside class="panel"><h2>Request path</h2><ol class="steps"><li><strong>Prepare draft</strong>Check quantities and cost center.</li><li><strong>Review summary</strong>Confirm the local request details.</li><li><strong>Submit to service</strong>Not connected in this preview.</li></ol><p class="hint">The review path and price are illustrative application data.</p></aside></div><div class="strip" id="notice" role="status">Complete the draft before preparing the summary.</div><p class="foot">HTML interpretation of SAP Morning Horizon · UI5 native integration provided in guide · No SAP transaction created</p></main><script>
const $=id=>document.getElementById(id);function amount(){const n=Number($('qty').value);return Number.isInteger(n)&&n>=1&&n<=20?n:null}$('qty').oninput=()=>{$('total').textContent=amount()===null?'Invalid quantity':'€'+amount()*320};$('density').onclick=()=>{const active=document.body.classList.toggle('compact');$('density').setAttribute('aria-pressed',String(active));$('density').textContent=active?'Cozy controls':'Compact controls'};$('form').onsubmit=e=>{e.preventDefault();const title=$('title').value.trim(),n=amount();$('title').setAttribute('aria-invalid',String(!title));$('qty').setAttribute('aria-invalid',String(n===null));$('notice').classList.toggle('error',!title||n===null);if(!title||n===null){$('notice').textContent=!title?'Enter a title for this request.':'Use a whole quantity from 1 to 20.';(!title?$('title'):$('qty')).focus();return}$('notice').textContent='Local summary: '+title+' · '+n+' monitors · €'+n*320+' · '+$('team').value+'. Nothing submitted.'};
</script></html>

代码参考

  • 本 HTML 文档中的密度切换是演绎示例。UI5 入口展示了真实的紧凑模式标记、已注册的自定义元素和 Horizon 资源。

Horizon 采购申请,数量已验证

使用 Input、Select/Option、Button 和 MessageStrip 组合本地请购摘要。每次按键后更新的总额与准备好的结果相互独立。

javascript
// main.js in a Vite vanilla project. index.html contains <div id="app"></div>.
import '@ui5/webcomponents/dist/Button.js';
import '@ui5/webcomponents/dist/Input.js';
import '@ui5/webcomponents/dist/Select.js';
import '@ui5/webcomponents/dist/Option.js';
import '@ui5/webcomponents/dist/MessageStrip.js';
import '@ui5/webcomponents/dist/Assets.js';
import {setTheme} from '@ui5/webcomponents-base/dist/config/Theme.js';
setTheme('sap_horizon');
document.querySelector('#app').innerHTML = `
  <main style="max-width:800px;margin:auto;padding:24px;font-family:var(--sapFontFamily)">
    <h1>Purchase request</h1>
    <p>Prepare a local requisition summary. No SAP transaction is created.</p>
    <p id="request-label">Request title</p>
    <ui5-input id="title" accessible-name-ref="request-label" required>
      <span slot="valueStateMessage">Enter a title for this request.</span>
    </ui5-input>
    <p id="team-label">Cost center</p>
    <ui5-select id="team" accessible-name-ref="team-label" value="design">
      <ui5-option value="design">Design · CC-240</ui5-option>
      <ui5-option value="research">Research · CC-360</ui5-option>
    </ui5-select>
    <p id="qty-label">Monitor quantity</p>
    <ui5-input id="qty" type="Number" accessible-name-ref="qty-label" value="2">
      <span slot="valueStateMessage">Use a whole number from 1 to 20.</span>
    </ui5-input>
    <p id="total">Estimated line total: €640</p>
    <ui5-button id="prepare" design="Emphasized">Prepare summary</ui5-button>
    <ui5-button id="density">Toggle compact controls</ui5-button>
    <ui5-message-strip id="notice" design="Information" hide-close-button style="display:block;margin-top:24px">
      Complete the draft before preparing the summary.
    </ui5-message-strip>
  </main>`;
const title=document.querySelector('#title'),qty=document.querySelector('#qty');
qty.addEventListener('input',()=>{
  const n=Number(qty.value);
  document.querySelector('#total').textContent=Number.isInteger(n)&&n>=1&&n<=20
    ? `Estimated line total: €${n*320}` : 'Enter a valid quantity to calculate the total.';
});
document.querySelector('#prepare').addEventListener('click',()=>{
  const n=Number(qty.value), validTitle=!!title.value.trim(), validQty=Number.isInteger(n)&&n>=1&&n<=20;
  title.valueState=validTitle?'None':'Negative';qty.valueState=validQty?'None':'Negative';
  const notice=document.querySelector('#notice');
  if(!validTitle||!validQty){notice.design='Negative';notice.textContent='Correct the request title or quantity before continuing.';return;}
  notice.design='Information';
  notice.textContent=`Local summary: ${title.value.trim()}, ${n} monitors, €${n*320}, ${document.querySelector('#team').value}. Nothing submitted.`;
});
document.querySelector('#density').addEventListener('click',()=>document.body.toggleAttribute('data-ui5-compact-size'));

代码参考

  • 数量规则、€320 的商品价格和成本中心均为虚构的应用数据,不代表 SAP 政策。
  • UI5 Web Components 是实现 Fiori 模式的一种方式;这不是完整的 SAPUI5 应用,也不是 Fiori elements 页面模板。
  • 不会创建 SAP 事务。通过离线预览验证也不意味着已完成原生依赖编译。
04

SAP Fiori 设计系统 Agent 设置

实现起点

将 Vite 的 main.js 替换为完整示例。UI5 文档要求使用 ES 模块打包器;官方并未提供涵盖所有组件的 CDN。

bash
npm create vite@latest fiori-request -- --template vanilla
cd fiori-request
npm install
npm install @ui5/webcomponents @ui5/webcomponents-base
npm run dev

适用于你项目的提示词

Agent 提示词
使用 UI5 Web Components 的 Input、Select、Button 和 MessageStrip,搭配 Horizon 主题,组合 Fiori 采购申请。保留数值验证、正确的 Negative 值状态、明确的紧凑密度,以及符合实际情况的本地摘要行为。
05

SAP Fiori 设计系统来源与范围

独立的 OpenDesign 参考示例。此处展示的是 UI5 Web Components,不是已部署的 SAP 事务或完整的 Fiori elements 应用。