IATA Rave 实战:用 Web Components 构建航空业统一设计系统
2026/8/30 20:06:15 网站建设 项目流程

如果你曾参与过航空公司、机场或旅游分销平台的数字化项目,大概率遇到过同一个尴尬:业务系统多,界面风格却各自为战,同一个“航班查询”功能在不同系统中长着完全不同的脸。前端团队每接手一个新项目,都要重新设计一遍基础组件,既浪费时间,又让用户学得头疼。

IATA(国际航空运输协会)这几年在航空零售化和标准统一上做了不少事情,其中一个面向行业前端的开源设计系统,就是 Rave。简单说,Rave 让航空行业的开发者可以基于一套统一的 UI 组件和设计规范去构建 Web 应用。本文围绕“成为 Rave 开发者”这个主题展开,适合想进入航空业做前端或全栈的开发者,也适合已经在航空 IT 项目中摸爬滚打、想提升组件化效率的团队。读完你会理解 Rave 解决了什么问题、它的技术栈长什么样,并动手搭建第一个 Rave 风格的应用和自定义航班业务组件。

正文中的代码属于工程示例,Rave 的包名、组件前缀和 API 在不同版本中可能调整,请以你当前使用的官方文档为准。

1. 为什么航空行业需要 Rave 开发者

1.1 航空 IT 的 UI 碎片化问题

航空公司的数字化体系远比普通人想象中复杂。一套面向旅客的购票官网背后,往往连接着 PSS 旅客服务系统、订单管理系统、支付网关、常旅客计划、机场离港系统等多个平台。这些系统来自不同供应商,有些是几年前的旧系统,有些是新上的云原生应用。消费者前端可以被设计得很漂亮,但内部运营人员每天使用的后台工具,仍然充满了表格式界面、密集表单和风格不一致的按钮。

这种碎片化带来的不只是视觉问题,还有真实的开发和维护成本。一个机场地服系统要增加一个“登机口变更”功能,前端团队可能要从零写一套组件;另一家供应商开发的同类系统,又因为内部封装完全不同,几乎所有代码都要重写。行业缺少的是一种可共享、可扩展的 UI 资产。

Rave 的定位,就是航空运输行业的开源设计系统和前端组件生态。它由 IATA 推动,目标不是做一套“谁都通用的普通组件库”,而是专注航空业务场景,让航空公司、机场、技术供应商在同一个生态里协作。

1.2 什么是 IATA Rave

从技术角度看,Rave 是一套基于现代 Web 标准构建的组件库与设计规范;从行业角度看,它是航空业数字化转型过程中的公共基础设施。Rave 组件覆盖了表单、表格、按钮、弹窗、日期选择等常用能力,同时把颜色、字体、圆角、间距等视觉要素抽象成设计令牌,方便团队在统一规范下做品牌化定制。

Rave 与航空业务标准关系紧密。航空公司正在从传统“航班为中心”的销售模式,转向“订单为中心”的零售模式,这个过程中涉及 NDC 标准、One Order 标准等。Rave 的价值在于,把这类航空业务模型在界面上做出一致的表达。比如乘客信息录入、行李选项、辅营产品选择、航班变更等场景,都可以用同一套组件语言实现。

所以,“Rave 开发者”并不是只会用组件库的页面拼接工人,而是既懂现代前端工程化,又能把航空业务标准落地成高质量界面的一类角色。

2. Rave 的技术架构与核心概念

2.1 基于 Web Components

Rave 最值得关注的技术选择是 Web Components。它由浏览器原生支持的自定义元素、Shadow DOM、HTML 模板等标准组成,核心优势是框架无关。

你不需要担心项目用的是 React 还是 Vue,只要浏览器支持自定义元素,就能直接使用 Rave 组件。这对航空行业尤其重要:同一套组件库要被十几个不同技术栈的团队使用,如果只提供 React 版本,Vue 和 Angular 团队就难以复用;但如果基于 Web Components 发布,所有业务系统都能以最小成本接入。

用户在页面中看到的是一个自定义元素标签,例如一个按钮可能被渲染成<rave-button>。组件内部的 DOM 结构和样式被隔离在 Shadow DOM 中,不轻易受到外部全局样式污染。这种封装特性在大型后台系统中非常实用。

如果你刚开始接触这个概念,可以把 Web Components 理解为“浏览器原生支持的组件化能力”。它不依赖某个框架的运行时,定义一次,随处使用。

2.2 设计令牌与主题

Rave 组件在样式层大量使用 CSS 变量,也就是设计令牌。设计令牌并不是某个库的专属概念,而是一种将颜色、字体、间距、圆角等设计决策集中管理的方式。

通常 Rave 会在:root或特定主题作用域下定义一组变量,例如主色、边框色、常用间距。组件内部通过var(--rv-color-primary)这类写法引用。这样,业务系统可以基于一套变量定制品牌色,而不需要重写组件源码。

这种设计给航空企业带来的直接好处是:下属子品牌或不同地区站点需要调整视觉时,只需要覆盖主题变量,不需要修改业务组件逻辑。后续升级组件库时,也不会因为本地改动过多产生合并冲突。

2.3 组件分层

一个成熟的航空业务前端项目,组件通常会分三个层次:

  • 基础组件:按钮、输入框、下拉菜单、表格、弹窗等。
  • 业务组件:航班卡片、乘客信息表单、行李选项、优惠券选择器等。
  • 页面模板:查询页、订单页、乘客管理页等完整界面。

Rave 设计系统覆盖的范围,越往上层越需要业务团队自己沉淀。基础组件可以直接复用 Rave 的能力,业务组件则要结合航空领域的数据模型做封装。作为 Rave 开发者,不能只满足于调用基础组件,还要具备业务组件的抽象能力。

3. 环境准备与开发工具

3.1 开发环境

在动手写代码之前,先把基础环境准备好。

  • Node.js 环境,建议 16 或以上版本,npm 或 yarn 任选。
  • 代码编辑器,推荐 VS Code,并安装 ESLint、Prettier 等前端工程化插件。
  • 现代浏览器,推荐 Chrome 或 Edge,用于调试自定义元素和 Shadow DOM。
  • Git,用于项目管理和提交代码。

如果你是想用 Rave 官方组件开始,需要先确认你的项目能访问 npm registry 并安装依赖。国内网络环境下,如果安装慢,可以配置 npm 镜像,这里不做展开。

3.2 项目脚手架

本文的示例使用 Vite 构建一个原生 Web Components 项目。Vite 开发服务器启动快、配置简单,适合用来做组件原型验证。

# 创建项目 npm create vite@latest rave-demo -- --template vanilla # 进入目录 cd rave-demo # 安装依赖 npm install

执行完后,项目里会看到一个标准的index.htmlmain.jsstyle.css文件。后续我们会在这些文件上修改。

3.3 关于版本与组件前缀

Rave 组件库的发布形式和命名在不同阶段可能有变化。有些版本使用<rv-*>前缀,有些版本使用<rave-*>前缀;安装包名也可能从单包变为按模块拆分。所以,尽量不要把“某个固定前缀”背死,而是在项目中统一查文档,或者在浏览器开发者工具的 Elements 面板里,确认自定义元素的实际标签名。

下面的示例中,我会统一用rave-作为标签前缀,目的是让结构更直观。你实际开发时,请以官方文档暴露出来的标签名为准。

4. 第一个 Rave 风格应用:从零搭建

4.1 引入组件库

假设你已经通过 npm 安装了 Rave 的相关组件包。如果组件库是用 Stencil 这类工具构建的,通常需要调用它的 loader 方法,把自定义元素注册到浏览器中。

src/main.js中写入:

// 文件路径:src/main.js import { defineCustomElements } from 'rave-components/dist/loader'; defineCustomElements();

这里的rave-components/dist/loader是示例写法。实际项目中,请根据你安装的包名和导出路径调整。

如果你是直接在一个普通 HTML 页面中使用,也可以通过<script>标签引入组件库的构建产物。哪种方式适合你,取决于项目架构和组件库官方给出的接入方式。

4.2 在页面中使用组件

修改index.html,加入一个简单的航空订单创建界面:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Rave Demo</title> </head> <body> <main class="page"> <h1>航班订单管理</h1> <rave-button>npm run dev

浏览器打开终端提示的本地地址,正常情况下页面会显示标题、按钮和输入框。打开开发者工具的 Elements 面板,可以观察到这些标签内部存在 Shadow DOM 结构。这就是 Web Components 的典型表现。

如果页面中组件没有渲染,优先检查两个方面:一是defineCustomElements是否在入口执行成功;二是组件标签名称是否和官方文档一致,浏览器不认识的标签会被当成普通行内元素,无法看到组件效果。

4.4 与主流框架集成

Rave 的 Web Components 特性让集成变得简单。用 React 举例,你仍然需要处理一个细节:React 的 JSX 语法对自定义元素支持不算特别原生,官方推荐通过createElement或 ref 结构使用。

下面是一个在 React 组件中包装flight-card自定义元素的示例:

// 文件路径:src/components/FlightCardWrapper.jsx import { useEffect, useRef, createElement } from 'react'; export default function FlightCardWrapper(props) { const ref = useRef(null); useEffect(() => { const el = ref.current; const handler = (event) => { console.log('用户选择航班:', event.detail); }; el?.addEventListener('flight-select', handler); return () => el?.removeEventListener('flight-select', handler); }, []); return createElement('flight-card', { ...props, ref }); }

这里的关键点是:React 的合成事件只覆盖标准事件,自定义事件flight-select需要拿到原生 DOM 元素后,通过addEventListener手动监听,并在组件卸载时移除监听器,避免内存泄漏。

如果你使用 TypeScript,还需要给自定义元素声明类型,否则 JSX 和createElement的类型校验会报“未知元素”的错。

// 文件路径:src/types/global.d.ts declare namespace JSX { interface IntrinsicElements { 'flight-card': React.DetailedHTMLProps< React.HTMLAttributes<HTMLElement>, HTMLElement > & { from?: string; to?: string; 'deps-time'?: string; 'arrs-time'?: string; price?: string; }; } }

5. 实战:开发一个航空航班组件

5.1 需求分析

进入具体业务场景。假设我们要开发一个“航班卡片”组件,用于在搜索结果列表中展示一条航班信息。需求包括:

  • 展示出发地、目的地机场三字码。
  • 展示出发和到达时间。
  • 展示价格。
  • 点击卡片时触发一个自定义事件,方便外部业务逻辑监听。

这个组件既要符合 Rave 的设计语言,又要保持业务上的可复用性。我们采用原生 Web Components 实现,不依赖任何框架。

5.2 设计令牌定义

为了贴合 Rave 的设计系统,我们先用一组 CSS 变量定义主题。

/* 文件路径:src/styles/tokens.css */ :root { --rv-color-primary: #005fc5; --rv-color-primary-hover: #004a9a; --rv-color-bg: #ffffff; --rv-color-border: #e5e7eb; --rv-color-text: #1f2937; --rv-color-text-secondary: #6b7280; --rv-font-family: Inter, "PingFang SC", "Microsoft YaHei", sans-serif; --rv-radius: 8px; --rv-space-sm: 8px; --rv-space-md: 16px; --rv-space-lg: 24px; }

这些变量命名体现了 Rave 生态里常见的--rv-前缀。具体变量名以你使用的设计系统为准,但思路是一致的:把颜色、空间、字体这些可复用的设计决策沉淀成变量。

5.3 航班卡片组件实现

创建FlightCard组件类。

// 文件路径:src/components/FlightCard.js class FlightCard extends HTMLElement { static get observedAttributes() { return ['from', 'to', 'deps-time', 'arrs-time', 'price']; } constructor() { super(); // 使用阴影 DOM 隔离内部样式 this.attachShadow({ mode: 'open' }); } connectedCallback() { this.render(); this.bindEvents(); } attributeChangedCallback() { this.render(); } formatPrice(value) { const amount = Number(value); if (Number.isNaN(amount)) { return '--'; } return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(amount); } bindEvents() { const card = this.shadowRoot.querySelector('.card'); card?.addEventListener('click', () => { this.dispatchEvent( new CustomEvent('flight-select', { detail: { from: this.getAttribute('from'), to: this.getAttribute('to'), depsTime: this.getAttribute('deps-time'), arrsTime: this.getAttribute('arrs-time'), price: this.getAttribute('price') }, bubbles: true, composed: true }) ); }); } render() { const from = this.getAttribute('from') || '---'; const to = this.getAttribute('to') || '---'; const depsTime = this.getAttribute('deps-time') || ''; const arrsTime = this.getAttribute('arrs-time') || ''; const price = this.formatPrice(this.getAttribute('price')); this.shadowRoot.innerHTML = ` <style> :host { display: block; font-family: var(--rv-font-family, sans-serif); } .card { border: 1px solid var(--rv-color-border, #e5e7eb); border-radius: var(--rv-radius, 8px); padding: var(--rv-space-md, 16px); display: flex; justify-content: space-between; align-items: center; cursor: pointer; transition: box-shadow 0.2s ease; background: var(--rv-color-bg, #fff); } .card:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); } .airport { font-size: 20px; font-weight: 700; color: var(--rv-color-text, #1f2937); } .time { color: var(--rv-color-text-secondary, #6b7280); font-size: 14px; margin-top: 4px; } .price { color: var(--rv-color-primary, #005fc5); font-size: 18px; font-weight: 700; } </style> <div class="card" role="button" tabindex="0"> <div> <div class="airport">${from} → ${to}</div> <div class="time">${depsTime} — ${arrsTime}</div> </div> <div class="price">${price}</div> </div> `; } } customElements.define('flight-card', FlightCard);

代码中有几个细节值得注意:

  • observedAttributes声明了组件监听哪些属性变化,属性变化时会自动重新渲染。
  • attachShadow({ mode: 'open' })创建了隔离的样式作用域。
  • render()方法是简单的字符串模板渲染,适合组件内部状态不复杂的场景。
  • formatPrice使用Intl.NumberFormat做货币格式化,航空业务中涉及多语言、多币种时非常实用。

composed: true的设置很关键:它让自定义事件可以穿透 Shadow DOM,被外层业务代码监听。如果你忘记设置composed,事件可能无法正确向上冒泡。

5.4 页面使用航班卡片

index.html中加入组件引用。

<flight-card from="PEK" to="SIN" deps-time="09:30" arrs-time="15:45" price="3680" ></flight-card>

然后在主入口中注册组件。

// 文件路径:src/main.js import './components/FlightCard.js'; import './styles/tokens.css';

启动开发服务器后,页面上应该能看到一张包含航班信息和价格的卡片。点击卡片,控制台会打印出flight-select事件以及对应的航班明细。

注意,这里字段中的fromto在完整的航空业务中可能需要搭配城市名称,而不仅仅是三字码。真实项目中,你可以增加from-cityto-city等属性,或者从后台接口直接返回完整的展示对象。

5.5 组件测试思路

这个组件是纯前端实现,建议至少覆盖以下测试点:

  • 属性不传时,能否正常渲染且不报错。
  • 价格字段传入非法字符串时,是否显示--
  • 点击组件是否触发flight-select事件。
  • 重复更新属性时,内容是否正确刷新。

对于更复杂的业务组件,建议引入 Playwright 做端到端测试,在真实浏览器中验证自定义元素行为。

6. 常见问题与排查思路

Web Components 和 Rave 相关开发中,最容易遇到下面几类问题。

问题现象常见原因解决思路
页面中自定义元素没有渲染loader 未调用、组件 JS 未加载、标签名拼写错误检查入口文件是否调用了defineCustomElements,并用浏览器的 Elements 面板确认标签名
Shadow DOM 内部样式异常写入了组件外部的全局样式,或 CSS 变量没有被正确继承将样式定义在组件内部,并有意识地使用 CSS 变量控制主题
React 中自定义元素事件不生效JSX 合成事件无法覆盖自定义事件通过ref获取原生 DOM 元素,用addEventListener手动绑定,并在卸载时移除
服务端渲染或构建时报window is not definedWeb Components 依赖浏览器 API,Node 环境没有window在客户端动态导入组件包,或使用dynamic import隔离
组件之间有样式冲突全局使用非隔离性 CSS 类名推荐使用 Shadow DOM 或规范化类名,遵循 Rave 的命名约定
主题变量不生效变量定义在选择器优先级较低的位置,或变量名不一致统一在:root定义,并通过开发者工具检查元素的 Computed 样式

这类问题的排查思路通常有一个共性:先确认元素是否真正渲染,再确认事件和样式层级。使用 Chrome DevTools 的 Elements 面板可以快速定位自定义元素的边界和 Shadow DOM 内部结构,这是日常调试的推荐起点。

7. 成为 Rave 开发者的最佳实践与学习路线

7.1 从业务标准出发

Rave 不只是一个 UI 组件库,它背后是航空业对“零售化系统体验一致性”的追求。学习 Rave 时,最好先理解几个航空业务概念:

  • NDC:航空零售领域的 API 通信标准,让航空公司能够动态地销售辅营产品。
  • One Order:用“订单”统一替代传统的“PNR + E-Ticket + EMD”多记录结构。
  • 机场三字码与航段时间:航空界的基础数据概念。

界面上的航班卡片、订单表单,本质上都是这些业务模型的视觉表达。只有理解了字段含义,才能在组件设计时做出合理的抽象。

7.2 打好前端技术基础

Rave 开发者能力的核心,仍然是扎实的前端功底。建议按这个顺序学习:

  1. HTML5 自定义元素、Shadow DOM、<template><slot>
  2. TypeScript,特别是泛型与声明文件编写。
  3. 至少一门主流框架,并理解框架与 Web Components 相互操作时的优缺点。
  4. 设计系统基础,包括设计令牌、主题化、可访问性。

7.3 组件开发规范

实际项目中,我建议团队内部形成一套组件开发约定:

  • 组件命名统一,比如以flight-order-passenger-作为业务组件前缀。
  • 所有可配置的视觉内容尽量通过属性或 CSS 变量暴露,而不是让使用方直接修改内部样式。
  • 每个组件必须考虑无属性和异常属性的渲染状态。
  • 事件名与组件名强关联,例如flight-selectorder-submit
  • 在组件文档中记录可用的属性、事件、CSS 变量,方便团队其他人接入。

7.4 无障碍与国际化

航空软件的用户来自全球各地,无障碍和国际化不是加分项,而是基础要求。

  • 颜色对比度需要满足 WCAG 标准。
  • 按钮、卡片等可交互元素要支持键盘操作。
  • 日期、时间要区分 UTC 与当地时间,避免直接拼接字符串。
  • 货币金额尽量使用Intl.NumberFormat按地区格式化。

7.5 参与开源与社区

Rave 是开源设计系统,意味着你可以通过阅读源码理解航空级组件的设计思路,也可以通过提交 Issue、修复文档、贡献新组件来进入这个生态。参与开源的好处不仅仅是代码能力提升,还能帮助你建立航空领域的人脉和行业认知。

7.6 学习资源建议

学习 Rave 和航空前端开发,优先看这几类资源:

  • IATA 官方关于零售化标准和 Rave 的文档。
  • Rave 设计系统官方组件示例与使用说明。
  • Web Components 标准文档和 MDN 相关教程。
  • GitHub 上航空领域开源项目,特别是 Rave 相关仓库。

如果所在公司已经有航空类项目,争取在真实业务中沉淀一个业务组件,远比只看文档有效。

8. 写在最后

Rave 的价值,在于让航空行业的界面开发从“重复造轮子”走向“共享组件资产”。作为 Rave 开发者,你不需要等整个行业标准完全成熟才开始行动。可以先从今天示例中的航班卡片组件入手,把 Web Components、设计令牌、事件封装这几点练熟,再逐步接触 IATA 的行业标准和更复杂的航空业务场景。

航空业务系统的需求往往比普通企业应用更复杂,数据和状态也更严肃。但正因如此,一个懂得组件化、标准化和业务建模的前端开发者,在这个行业里会非常有价值。

如果你也想尝试,建议第一步就是把环境搭好,跑通一个最简单的 Rave 风格页面,再往里面加业务组件。动手遇到问题的时候,欢迎在评论区把报错信息发出来,我们一起讨论排查思路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询