Skip to main content

aster_a2ui/
catalog.rs

1//! A2UI 标准组件目录
2//!
3//! 对应 A2UI 规范中的 standard_catalog.json
4
5use serde::{Deserialize, Serialize};
6
7use crate::common::{
8    AccessibilityAttributes, Action, Checkable, ChildList, ComponentId, DynamicBoolean,
9    DynamicNumber, DynamicString, DynamicStringList,
10};
11
12/// 标准组件目录 ID
13pub const STANDARD_CATALOG_ID: &str = "https://a2ui.org/specification/v0_10/standard_catalog.json";
14
15// ============================================================================
16// 组件通用属性
17// ============================================================================
18
19/// 组件通用属性
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
21#[serde(rename_all = "camelCase")]
22pub struct ComponentCommon {
23    /// 组件唯一标识符
24    pub id: ComponentId,
25    /// 无障碍属性
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub accessibility: Option<AccessibilityAttributes>,
28    /// 布局权重(仅在 Row/Column 子组件中有效)
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub weight: Option<f64>,
31}
32
33// ============================================================================
34// 组件枚举
35// ============================================================================
36
37/// 所有标准组件的枚举
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
39#[serde(tag = "component")]
40pub enum Component {
41    Text(TextComponent),
42    Image(ImageComponent),
43    Icon(IconComponent),
44    Video(VideoComponent),
45    AudioPlayer(AudioPlayerComponent),
46    Row(RowComponent),
47    Column(ColumnComponent),
48    List(ListComponent),
49    Card(CardComponent),
50    Tabs(TabsComponent),
51    Modal(ModalComponent),
52    Divider(DividerComponent),
53    Button(ButtonComponent),
54    TextField(TextFieldComponent),
55    CheckBox(CheckBoxComponent),
56    ChoicePicker(ChoicePickerComponent),
57    Slider(SliderComponent),
58    DateTimeInput(DateTimeInputComponent),
59}
60
61impl Component {
62    /// 获取组件 ID
63    pub fn id(&self) -> &str {
64        match self {
65            Component::Text(c) => &c.common.id,
66            Component::Image(c) => &c.common.id,
67            Component::Icon(c) => &c.common.id,
68            Component::Video(c) => &c.common.id,
69            Component::AudioPlayer(c) => &c.common.id,
70            Component::Row(c) => &c.common.id,
71            Component::Column(c) => &c.common.id,
72            Component::List(c) => &c.common.id,
73            Component::Card(c) => &c.common.id,
74            Component::Tabs(c) => &c.common.id,
75            Component::Modal(c) => &c.common.id,
76            Component::Divider(c) => &c.common.id,
77            Component::Button(c) => &c.common.id,
78            Component::TextField(c) => &c.common.id,
79            Component::CheckBox(c) => &c.common.id,
80            Component::ChoicePicker(c) => &c.common.id,
81            Component::Slider(c) => &c.common.id,
82            Component::DateTimeInput(c) => &c.common.id,
83        }
84    }
85}
86
87// ============================================================================
88// 展示组件
89// ============================================================================
90
91/// 文本组件
92#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
93#[serde(rename_all = "camelCase")]
94pub struct TextComponent {
95    #[serde(flatten)]
96    pub common: ComponentCommon,
97    /// 文本内容(支持简单 Markdown)
98    pub text: DynamicString,
99    /// 文本样式变体
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub variant: Option<TextVariant>,
102}
103
104/// 文本样式变体
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
106#[serde(rename_all = "lowercase")]
107pub enum TextVariant {
108    H1,
109    H2,
110    H3,
111    H4,
112    H5,
113    Caption,
114    Body,
115}
116
117/// 图片组件
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
119#[serde(rename_all = "camelCase")]
120pub struct ImageComponent {
121    #[serde(flatten)]
122    pub common: ComponentCommon,
123    /// 图片 URL
124    pub url: DynamicString,
125    /// 图片适应方式
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub fit: Option<ImageFit>,
128    /// 图片样式变体
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub variant: Option<ImageVariant>,
131}
132
133/// 图片适应方式
134#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
135#[serde(rename_all = "kebab-case")]
136pub enum ImageFit {
137    Contain,
138    Cover,
139    Fill,
140    None,
141    ScaleDown,
142}
143
144/// 图片样式变体
145#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
146#[serde(rename_all = "camelCase")]
147pub enum ImageVariant {
148    Icon,
149    Avatar,
150    SmallFeature,
151    MediumFeature,
152    LargeFeature,
153    Header,
154}
155
156/// 图标组件
157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
158#[serde(rename_all = "camelCase")]
159pub struct IconComponent {
160    #[serde(flatten)]
161    pub common: ComponentCommon,
162    /// 图标名称或自定义路径
163    pub name: IconName,
164}
165
166/// 图标名称
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
168#[serde(untagged)]
169pub enum IconName {
170    /// 预定义图标
171    Preset(PresetIcon),
172    /// 自定义 SVG 路径
173    Custom { path: String },
174}
175
176/// 预定义图标列表
177#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
178#[serde(rename_all = "camelCase")]
179pub enum PresetIcon {
180    AccountCircle,
181    Add,
182    ArrowBack,
183    ArrowForward,
184    AttachFile,
185    CalendarToday,
186    Call,
187    Camera,
188    Check,
189    Close,
190    Delete,
191    Download,
192    Edit,
193    Event,
194    Error,
195    FastForward,
196    Favorite,
197    FavoriteOff,
198    Folder,
199    Help,
200    Home,
201    Info,
202    LocationOn,
203    Lock,
204    LockOpen,
205    Mail,
206    Menu,
207    MoreVert,
208    MoreHoriz,
209    NotificationsOff,
210    Notifications,
211    Pause,
212    Payment,
213    Person,
214    Phone,
215    Photo,
216    Play,
217    Print,
218    Refresh,
219    Rewind,
220    Search,
221    Send,
222    Settings,
223    Share,
224    ShoppingCart,
225    SkipNext,
226    SkipPrevious,
227    Star,
228    StarHalf,
229    StarOff,
230    Stop,
231    Upload,
232    Visibility,
233    VisibilityOff,
234    VolumeDown,
235    VolumeMute,
236    VolumeOff,
237    VolumeUp,
238    Warning,
239}
240
241/// 视频组件
242#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
243#[serde(rename_all = "camelCase")]
244pub struct VideoComponent {
245    #[serde(flatten)]
246    pub common: ComponentCommon,
247    /// 视频 URL
248    pub url: DynamicString,
249}
250
251/// 音频播放器组件
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
253#[serde(rename_all = "camelCase")]
254pub struct AudioPlayerComponent {
255    #[serde(flatten)]
256    pub common: ComponentCommon,
257    /// 音频 URL
258    pub url: DynamicString,
259    /// 音频描述
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub description: Option<DynamicString>,
262}
263
264// ============================================================================
265// 布局组件
266// ============================================================================
267
268/// 水平布局组件
269#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
270#[serde(rename_all = "camelCase")]
271pub struct RowComponent {
272    #[serde(flatten)]
273    pub common: ComponentCommon,
274    /// 子组件列表
275    pub children: ChildList,
276    /// 主轴对齐方式
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub justify: Option<JustifyContent>,
279    /// 交叉轴对齐方式
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub align: Option<AlignItems>,
282}
283
284/// 垂直布局组件
285#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
286#[serde(rename_all = "camelCase")]
287pub struct ColumnComponent {
288    #[serde(flatten)]
289    pub common: ComponentCommon,
290    /// 子组件列表
291    pub children: ChildList,
292    /// 主轴对齐方式
293    #[serde(skip_serializing_if = "Option::is_none")]
294    pub justify: Option<JustifyContent>,
295    /// 交叉轴对齐方式
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub align: Option<AlignItems>,
298}
299
300/// 主轴对齐方式
301#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
302#[serde(rename_all = "camelCase")]
303pub enum JustifyContent {
304    Start,
305    Center,
306    End,
307    SpaceBetween,
308    SpaceAround,
309    SpaceEvenly,
310    Stretch,
311}
312
313/// 交叉轴对齐方式
314#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
315#[serde(rename_all = "camelCase")]
316pub enum AlignItems {
317    Start,
318    Center,
319    End,
320    Stretch,
321}
322
323/// 列表组件
324#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
325#[serde(rename_all = "camelCase")]
326pub struct ListComponent {
327    #[serde(flatten)]
328    pub common: ComponentCommon,
329    /// 子组件列表
330    pub children: ChildList,
331    /// 列表方向
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub direction: Option<ListDirection>,
334    /// 交叉轴对齐方式
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub align: Option<AlignItems>,
337}
338
339/// 列表方向
340#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
341#[serde(rename_all = "camelCase")]
342pub enum ListDirection {
343    Vertical,
344    Horizontal,
345}
346
347/// 卡片组件
348#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
349#[serde(rename_all = "camelCase")]
350pub struct CardComponent {
351    #[serde(flatten)]
352    pub common: ComponentCommon,
353    /// 子组件 ID
354    pub child: ComponentId,
355}
356
357/// 标签页组件
358#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
359#[serde(rename_all = "camelCase")]
360pub struct TabsComponent {
361    #[serde(flatten)]
362    pub common: ComponentCommon,
363    /// 标签页列表
364    pub tabs: Vec<TabItem>,
365}
366
367/// 标签页项
368#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
369#[serde(rename_all = "camelCase")]
370pub struct TabItem {
371    /// 标签标题
372    pub title: DynamicString,
373    /// 标签内容组件 ID
374    pub child: ComponentId,
375}
376
377/// 模态框组件
378#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
379#[serde(rename_all = "camelCase")]
380pub struct ModalComponent {
381    #[serde(flatten)]
382    pub common: ComponentCommon,
383    /// 触发器组件 ID
384    pub trigger: ComponentId,
385    /// 内容组件 ID
386    pub content: ComponentId,
387}
388
389/// 分隔线组件
390#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
391#[serde(rename_all = "camelCase")]
392pub struct DividerComponent {
393    #[serde(flatten)]
394    pub common: ComponentCommon,
395    /// 分隔线方向
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub axis: Option<DividerAxis>,
398}
399
400/// 分隔线方向
401#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
402#[serde(rename_all = "camelCase")]
403pub enum DividerAxis {
404    Horizontal,
405    Vertical,
406}
407
408// ============================================================================
409// 交互组件
410// ============================================================================
411
412/// 按钮组件
413#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
414#[serde(rename_all = "camelCase")]
415pub struct ButtonComponent {
416    #[serde(flatten)]
417    pub common: ComponentCommon,
418    /// 按钮内容组件 ID
419    pub child: ComponentId,
420    /// 按钮动作
421    pub action: Action,
422    /// 按钮样式变体
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub variant: Option<ButtonVariant>,
425    /// 验证规则
426    #[serde(flatten, skip_serializing_if = "Option::is_none")]
427    pub checkable: Option<Checkable>,
428}
429
430/// 按钮样式变体
431#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
432#[serde(rename_all = "camelCase")]
433pub enum ButtonVariant {
434    Primary,
435    Borderless,
436}
437
438/// 文本输入框组件
439#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
440#[serde(rename_all = "camelCase")]
441pub struct TextFieldComponent {
442    #[serde(flatten)]
443    pub common: ComponentCommon,
444    /// 输入框标签
445    pub label: DynamicString,
446    /// 输入框值
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub value: Option<DynamicString>,
449    /// 输入框类型
450    #[serde(skip_serializing_if = "Option::is_none")]
451    pub variant: Option<TextFieldVariant>,
452    /// 验证规则
453    #[serde(flatten, skip_serializing_if = "Option::is_none")]
454    pub checkable: Option<Checkable>,
455}
456
457/// 文本输入框类型
458#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
459#[serde(rename_all = "camelCase")]
460pub enum TextFieldVariant {
461    ShortText,
462    LongText,
463    Number,
464    Obscured,
465}
466
467/// 复选框组件
468#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
469#[serde(rename_all = "camelCase")]
470pub struct CheckBoxComponent {
471    #[serde(flatten)]
472    pub common: ComponentCommon,
473    /// 复选框标签
474    pub label: DynamicString,
475    /// 复选框值
476    pub value: DynamicBoolean,
477    /// 验证规则
478    #[serde(flatten, skip_serializing_if = "Option::is_none")]
479    pub checkable: Option<Checkable>,
480}
481
482/// 选择器组件
483#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
484#[serde(rename_all = "camelCase")]
485pub struct ChoicePickerComponent {
486    #[serde(flatten)]
487    pub common: ComponentCommon,
488    /// 选择器标签
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub label: Option<DynamicString>,
491    /// 选项列表
492    pub options: Vec<ChoiceOption>,
493    /// 当前选中值
494    pub value: DynamicStringList,
495    /// 选择器类型
496    #[serde(skip_serializing_if = "Option::is_none")]
497    pub variant: Option<ChoicePickerVariant>,
498    /// 验证规则
499    #[serde(flatten, skip_serializing_if = "Option::is_none")]
500    pub checkable: Option<Checkable>,
501}
502
503/// 选择器选项
504#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
505#[serde(rename_all = "camelCase")]
506pub struct ChoiceOption {
507    /// 选项显示文本
508    pub label: DynamicString,
509    /// 选项值
510    pub value: String,
511}
512
513/// 选择器类型
514#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
515#[serde(rename_all = "camelCase")]
516pub enum ChoicePickerVariant {
517    MultipleSelection,
518    MutuallyExclusive,
519}
520
521/// 滑块组件
522#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
523#[serde(rename_all = "camelCase")]
524pub struct SliderComponent {
525    #[serde(flatten)]
526    pub common: ComponentCommon,
527    /// 滑块标签
528    #[serde(skip_serializing_if = "Option::is_none")]
529    pub label: Option<DynamicString>,
530    /// 最小值
531    pub min: f64,
532    /// 最大值
533    pub max: f64,
534    /// 当前值
535    pub value: DynamicNumber,
536    /// 验证规则
537    #[serde(flatten, skip_serializing_if = "Option::is_none")]
538    pub checkable: Option<Checkable>,
539}
540
541/// 日期时间输入组件
542#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
543#[serde(rename_all = "camelCase")]
544pub struct DateTimeInputComponent {
545    #[serde(flatten)]
546    pub common: ComponentCommon,
547    /// 输入框标签
548    #[serde(skip_serializing_if = "Option::is_none")]
549    pub label: Option<DynamicString>,
550    /// 当前值(ISO 8601 格式)
551    pub value: DynamicString,
552    /// 是否启用日期选择
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub enable_date: Option<bool>,
555    /// 是否启用时间选择
556    #[serde(skip_serializing_if = "Option::is_none")]
557    pub enable_time: Option<bool>,
558    /// 最小日期时间
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub min: Option<DynamicString>,
561    /// 最大日期时间
562    #[serde(skip_serializing_if = "Option::is_none")]
563    pub max: Option<DynamicString>,
564    /// 验证规则
565    #[serde(flatten, skip_serializing_if = "Option::is_none")]
566    pub checkable: Option<Checkable>,
567}