open_lark/
card.rs

1use std::{collections::HashMap, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4#[cfg(feature = "im")]
5use serde_json::json;
6use strum_macros::EnumString;
7
8use crate::card::{
9    components::{
10        content_components::{plain_text::PlainText, title::FeishuCardTitle},
11        CardElement,
12    },
13    text::CustomTextSize,
14};
15
16#[cfg(feature = "im")]
17use crate::service::im::v1::message::SendMessageTrait;
18
19/// 卡片组件模块
20///
21/// 提供各种卡片UI组件,包括内容组件、交互组件、布局组件等
22pub mod components;
23
24/// 链接处理模块
25///
26/// 处理卡片中的链接和跳转逻辑
27pub mod href;
28
29/// 图标管理模块
30///
31/// 管理卡片中使用的各种图标资源
32pub mod icon;
33
34/// 交互处理模块
35///
36/// 处理卡片的用户交互事件和回调
37pub mod interactions;
38
39/// 文本样式模块
40///
41/// 定义卡片中文本的样式和格式化
42pub mod text;
43
44/// 飞书消息卡片
45///
46/// 用于创建和发送交互式消息卡片。支持多语言、富文本、交互组件等丰富功能。
47/// 卡片可以包含文本、图片、按钮、表单等多种组件,提供丰富的用户交互体验。
48///
49/// # 主要特性
50///
51/// - 🌐 多语言支持
52/// - 🎨 丰富的UI组件
53/// - 🔄 交互式操作
54/// - 📱 响应式布局
55/// - 🔧 高度可定制
56///
57/// # 支持的组件
58///
59/// - **文本组件**: 纯文本、富文本、标题
60/// - **媒体组件**: 图片、视频
61/// - **交互组件**: 按钮、输入框、选择器
62/// - **布局组件**: 分栏、折叠面板
63/// - **数据组件**: 表格、图表
64///
65/// # 示例
66///
67/// ```no_run
68/// use open_lark::card::{FeishuCard, FeishuCardConfig};
69/// use open_lark::card::components::content_components::title::FeishuCardTitle;
70/// use open_lark::card::components::content_components::title::Title;
71/// use open_lark::card::components::CardElement;
72///
73/// // 创建简单卡片
74/// let card = FeishuCard::new()
75///     .config(
76///         FeishuCardConfig::new()
77///             .enable_forward(true)
78///             .update_multi(false)
79///     )
80///     .header("zh_cn",
81///         FeishuCardTitle::new()
82///             .title(Title::new("欢迎使用飞书卡片"))
83///     )?
84///     .elements("zh_cn", vec![
85///         // 添加卡片元素
86///     ])?;
87/// # Ok::<(), open_lark::core::error::LarkAPIError>(())
88/// ```
89#[derive(Debug, Serialize, Deserialize, Default)]
90pub struct FeishuCard {
91    /// config 用于配置卡片的全局行为,包括是否允许被转发、是否为共享卡片等。
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub config: Option<FeishuCardConfig>,
94    /// 用于配置卡片的标题
95    pub i18n_header: HashMap<FeishuCardLanguage, FeishuCardTitle>,
96    /// 卡片的多语言正文内容
97    pub i18n_elements: HashMap<FeishuCardLanguage, Vec<CardElement>>,
98}
99
100#[cfg(feature = "im")]
101impl SendMessageTrait for FeishuCard {
102    fn msg_type(&self) -> String {
103        "interactive".to_string()
104    }
105
106    fn content(&self) -> String {
107        json!(self).to_string()
108    }
109}
110
111impl FeishuCard {
112    /// 创建新的飞书卡片
113    ///
114    /// 默认使用中文语言环境,创建包含默认标题和空元素列表的卡片。
115    pub fn new() -> Self {
116        let lng = FeishuCardLanguage::ZhCN;
117        let mut header = HashMap::new();
118        header.insert(lng, FeishuCardTitle::default());
119        let mut elements = HashMap::new();
120        elements.insert(lng, vec![]);
121        Self {
122            config: None,
123            i18n_header: header,
124            i18n_elements: elements,
125        }
126    }
127
128    /// 设置卡片全局配置
129    ///
130    /// # 参数
131    /// * `config` - 卡片配置对象
132    pub fn config(mut self, config: FeishuCardConfig) -> Self {
133        self.config = Some(config);
134        self
135    }
136
137    /// 设置卡片标题
138    ///
139    /// # 参数  
140    /// * `lng` - 语言代码 (如 "zh_cn", "en_us")
141    /// * `header` - 卡片标题对象
142    pub fn header(
143        mut self,
144        lng: &str,
145        header: FeishuCardTitle,
146    ) -> Result<Self, crate::core::error::LarkAPIError> {
147        let language: FeishuCardLanguage = lng.parse().map_err(|e| {
148            crate::core::error::LarkAPIError::illegal_param(format!(
149                "unknown language '{lng}': {e}"
150            ))
151        })?;
152        let origin_header = self.i18n_header.entry(language).or_default();
153        *origin_header = header;
154
155        Ok(self)
156    }
157
158    /// 添加卡片组件
159    ///
160    /// # 参数
161    /// * `lng` - 语言代码 (如 "zh_cn", "en_us")
162    /// * `elements` - 卡片组件列表
163    pub fn elements(
164        mut self,
165        lng: &str,
166        elements: Vec<CardElement>,
167    ) -> Result<Self, crate::core::error::LarkAPIError> {
168        let language: FeishuCardLanguage = lng.parse().map_err(|e| {
169            crate::core::error::LarkAPIError::illegal_param(format!(
170                "unknown language '{lng}': {e}"
171            ))
172        })?;
173        let self_elements = self.i18n_elements.entry(language).or_default();
174        self_elements.extend(elements);
175        Ok(self)
176    }
177}
178
179/// 卡片全局行为设置
180#[derive(Debug, Serialize, Deserialize, Default)]
181pub struct FeishuCardConfig {
182    /// 是否允许转发卡片。取值:
183    ///
184    /// - true:允许
185    /// - false:不允许 默认值为 true,该字段要求飞书客户端的版本为 V3.31.0 及以上。
186    #[serde(skip_serializing_if = "Option::is_none")]
187    enable_forward: Option<bool>,
188    /// 是否为共享卡片。取值:
189    ///
190    /// - true:是共享卡片,更新卡片的内容对所有收到这张卡片的人员可见。
191    /// - false:非共享卡片,即独享卡片,仅操作用户可见卡片的更新内容。
192    ///
193    /// 默认值为 false。
194    #[serde(skip_serializing_if = "Option::is_none")]
195    update_multi: Option<bool>,
196    /// 卡片宽度模式。取值:
197    ///
198    /// - default:默认宽度。PC 端宽版、iPad 端上的宽度上限为 600px。
199    /// - fill:自适应屏幕宽度
200    width_mode: Option<FeishuCardWidthMode>,
201    /// 是否使用自定义翻译数据。取值:
202    ///
203    /// - true:在用户点击消息翻译后,使用 i18n 对应的目标语种作为翻译结果。若 i18n
204    ///   取不到,则使用当前内容请求飞书的机器翻译。
205    /// - false:不使用自定义翻译数据,直接请求飞书的机器翻译。
206    #[serde(skip_serializing_if = "Option::is_none")]
207    use_custom_translation: Option<bool>,
208    /// 转发的卡片是否仍然支持回传交互。
209    #[serde(skip_serializing_if = "Option::is_none")]
210    enable_forward_interaction: Option<bool>,
211    ///  添加自定义字号和颜色。可应用于组件的 JSON 数据中,设置字号和颜色属性。
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub style: Option<FeishuCardStyle>,
214}
215
216impl FeishuCardConfig {
217    /// 创建新的卡片配置
218    pub fn new() -> Self {
219        Self::default()
220    }
221
222    /// 设置是否允许转发卡片
223    ///
224    /// # 参数
225    /// * `enable_forward` - true为允许转发,false为不允许
226    pub fn enable_forward(mut self, enable_forward: bool) -> Self {
227        self.enable_forward = Some(enable_forward);
228        self
229    }
230
231    /// 设置是否为共享卡片
232    ///
233    /// # 参数  
234    /// * `update_multi` - true为共享卡片,false为独享卡片
235    pub fn update_multi(mut self, update_multi: bool) -> Self {
236        self.update_multi = Some(update_multi);
237        self
238    }
239
240    /// 设置卡片宽度模式
241    ///
242    /// # 参数
243    /// * `width_mode` - 宽度模式:默认宽度或自适应屏幕宽度
244    pub fn width_mode(mut self, width_mode: FeishuCardWidthMode) -> Self {
245        self.width_mode = Some(width_mode);
246        self
247    }
248
249    /// 设置是否使用自定义翻译数据
250    ///
251    /// # 参数
252    /// * `use_custom_translation` - true为使用自定义翻译,false为使用机器翻译
253    pub fn use_custom_translation(mut self, use_custom_translation: bool) -> Self {
254        self.use_custom_translation = Some(use_custom_translation);
255        self
256    }
257
258    /// 设置转发的卡片是否仍然支持回传交互
259    ///
260    /// # 参数
261    /// * `enable_forward_interaction` - true为支持交互,false为不支持
262    pub fn enable_forward_interaction(mut self, enable_forward_interaction: bool) -> Self {
263        self.enable_forward_interaction = Some(enable_forward_interaction);
264        self
265    }
266
267    /// 设置卡片样式
268    ///
269    /// # 参数
270    /// * `style` - 卡片样式配置,包括字号和颜色
271    pub fn style(mut self, style: FeishuCardStyle) -> Self {
272        self.style = Some(style);
273        self
274    }
275}
276
277/// 卡片宽度模式
278#[derive(Debug, Serialize, Deserialize, Default)]
279#[serde(rename_all = "lowercase")]
280pub enum FeishuCardWidthMode {
281    /// 默认宽度。PC 端宽版、iPad 端上的宽度上限为 600px。
282    #[default]
283    Default,
284    /// 自适应屏幕宽度
285    Fill,
286}
287
288/// 卡片样式配置
289///
290/// 用于定义卡片的字号和颜色样式,支持为不同主题和设备定制样式
291#[derive(Debug, Serialize, Deserialize)]
292pub struct FeishuCardStyle {
293    /// 分别为移动端和桌面端添加自定义字号。用于在普通文本组件和富文本组件 JSON
294    /// 中设置字号属性。支持添加多个自定义字号对象。
295    #[serde(skip_serializing_if = "Option::is_none")]
296    text_size: Option<HashMap<String, CustomTextSize>>,
297    /// 分别为飞书客户端浅色主题和深色主题添加 RGBA 语法。用于在组件 JSON
298    /// 中设置颜色属性。支持添加多个自定义颜色对象。
299    #[serde(skip_serializing_if = "Option::is_none")]
300    color: Option<HashMap<String, String>>,
301}
302
303/// 飞书卡片支持的语言类型
304///
305/// 用于卡片的多语言支持,可为不同语言环境提供相应的内容
306#[derive(Debug, Serialize, Deserialize, Default, Eq, PartialEq, Hash, Clone, Copy)]
307pub enum FeishuCardLanguage {
308    /// 简体中文
309    #[serde(rename = "zh_cn")]
310    #[default]
311    ZhCN,
312    /// 英文(美国)
313    #[serde(rename = "en_us")]
314    EnUS,
315    /// 日文
316    #[serde(rename = "ja_jp")]
317    JaJP,
318    /// 繁体中文(香港)
319    #[serde(rename = "zh_hk")]
320    ZhHK,
321    /// 繁体中文(台湾)
322    #[serde(rename = "zh_tw")]
323    ZhTW,
324}
325
326impl FromStr for FeishuCardLanguage {
327    type Err = String;
328
329    fn from_str(s: &str) -> Result<Self, Self::Err> {
330        match s.to_ascii_lowercase().as_str() {
331            "zh_cn" => Ok(FeishuCardLanguage::ZhCN),
332            "en_us" => Ok(FeishuCardLanguage::EnUS),
333            "ja_jp" => Ok(FeishuCardLanguage::JaJP),
334            "zh_hk" => Ok(FeishuCardLanguage::ZhHK),
335            "zh_tw" => Ok(FeishuCardLanguage::ZhTW),
336            _ => Err(format!("unknown language: {s}")),
337        }
338    }
339}
340
341/// 标题的标签属性。最多可配置 3 个标签内容,如果配置的标签数量超过 3 个,则取前 3
342/// 个标签进行展示。标签展示顺序与数组顺序一致。
343#[derive(Debug, Serialize, Deserialize)]
344pub struct TextTag {
345    /// 标题标签的标识。固定取值:text_tag
346    tag: String,
347    /// 标题标签的内容。基于文本组件的 plain_text 模式定义内容。
348    text: Option<PlainText>,
349    /// 标题标签的颜色,默认为蓝色(blue)
350    color: Option<String>,
351}
352
353impl Default for TextTag {
354    fn default() -> Self {
355        TextTag {
356            tag: "text_tag".to_string(),
357            text: None,
358            color: None,
359        }
360    }
361}
362
363impl TextTag {
364    /// 创建新的文本标签
365    pub fn new() -> Self {
366        Self::default()
367    }
368
369    /// 设置标签文本内容
370    ///
371    /// # 参数
372    /// * `text` - 标签的文本内容
373    pub fn text(mut self, text: PlainText) -> Self {
374        self.text = Some(text);
375        self
376    }
377
378    /// 设置标签颜色
379    ///
380    /// # 参数
381    /// * `color` - 标签的颜色值
382    pub fn color(mut self, color: &str) -> Self {
383        self.color = Some(color.to_string());
384        self
385    }
386}
387
388/// 标题样式表
389///
390/// 定义飞书卡片标题的颜色主题模板
391#[derive(Debug, Serialize, Deserialize, Default, EnumString)]
392#[serde(rename_all = "lowercase")]
393#[strum(serialize_all = "lowercase")]
394pub enum FeishuCardHeaderTemplate {
395    /// 蓝色主题
396    Blue,
397    /// 浅蓝色主题
398    Wathet,
399    /// 青色主题
400    Turquoise,
401    /// 绿色主题
402    Green,
403    /// 黄色主题
404    Yellow,
405    /// 橙色主题
406    Orange,
407    /// 红色主题
408    Red,
409    /// 胭脂红主题
410    Carmine,
411    /// 紫罗兰主题
412    Violet,
413    /// 紫色主题
414    Purple,
415    /// 靛蓝色主题
416    Indigo,
417    /// 灰色主题
418    Grey,
419    /// 默认主题
420    #[default]
421    Default,
422}
423
424/// 消息卡片颜色主题
425///
426/// 定义消息卡片的颜色主题选项
427#[derive(Debug, Serialize, Deserialize, Default)]
428#[serde(rename_all = "lowercase")]
429pub enum MessageCardColor {
430    /// 中性色主题
431    Neutral,
432    /// 蓝色主题(默认)
433    #[default]
434    Blue,
435    /// 青色主题
436    Turquoise,
437    /// 青柠色主题
438    Lime,
439    /// 橙色主题
440    Orange,
441    /// 紫罗兰主题
442    Violet,
443    /// 靛蓝色主题
444    Indigo,
445    /// 浅蓝色主题
446    Wathet,
447    /// 绿色主题
448    Green,
449    /// 黄色主题
450    Yellow,
451    /// 红色主题
452    Red,
453    /// 紫色主题
454    Purple,
455    /// 胭脂红主题
456    Carmine,
457}