open_lark/card/components/containers/
interactive.rs

1use serde::{Deserialize, Serialize};
2
3use crate::card::{
4    components::{
5        content_components::plain_text::PlainText, interactive_components::input::InputConfirm,
6        CardElement,
7    },
8    interactions::Behaviors,
9};
10
11/// 交互容器
12#[derive(Debug, Serialize, Deserialize)]
13pub struct InteractiveContainer {
14    /// 交互容器的标签。固定值为 interactive_container。
15    tag: String,
16    /// 交互容器的宽度。可取值:
17    ///
18    /// - fill:卡片最大支持宽度
19    /// - auto:自适应宽度
20    /// - [16,999]px:自定义宽度,如 "20px"。最小宽度为 16px
21    #[serde(skip_serializing_if = "Option::is_none")]
22    width: Option<String>,
23    /// 交互容器的高度。可取值:
24    ///
25    /// - auto:自适应高度
26    /// - [10,999]px:自定义高度,如 "20px"
27    #[serde(skip_serializing_if = "Option::is_none")]
28    height: Option<String>,
29    /// 交互容器的背景色样式。可取值:
30    ///
31    /// - default:默认的白底样式,客户端深色主题下为黑底
32    /// - laser:镭射渐变彩色样式
33    /// - 卡片支持的颜色枚举值和 RGBA 语法自定义颜色。参考颜色枚举值
34    #[serde(skip_serializing_if = "Option::is_none")]
35    background_style: Option<String>,
36    /// 是否展示边框,粗细固定为 1px。
37    #[serde(skip_serializing_if = "Option::is_none")]
38    has_border: Option<bool>,
39    /// 边框的颜色,仅 has_border 为 true 时,此字段生效。枚举值为卡片支持的颜色枚举值和 RGBA
40    /// 语法自定义颜色,参考颜色枚举值。
41    #[serde(skip_serializing_if = "Option::is_none")]
42    border_color: Option<String>,
43    /// 交互容器的圆角半径,单位是像素(px)或百分比(%)。取值遵循以下格式:
44    ///
45    /// - [0,∞]px,如 "10px"
46    /// - [0,100]%,如 "30%"
47    #[serde(skip_serializing_if = "Option::is_none")]
48    corner_radius: Option<String>,
49    /// 交互容器的内边距。值的取值范围为 [0,28]px。支持填写单值或多值:
50    ///
51    /// - 单值:如 "10px",表示容器内四个内边距都为 10px
52    /// - 多值:如 "4px 12px 4px 12px",表示容器内上、右、下、左的内边距分别为
53    ///   4px,12px,4px,12px。四个值必填,使用空格间隔
54    #[serde(skip_serializing_if = "Option::is_none")]
55    padding: Option<String>,
56    /// 设置点击交互容器时的交互配置。如果交互容器内有交互组件,则优先响应交互组件定义的交互。
57    /// 详情参考配置卡片交互
58    behaviors: Vec<Behaviors>,
59    /// 用户在 PC 端将光标悬浮在交互容器上方时的文案提醒。默认为空。
60    #[serde(skip_serializing_if = "Option::is_none")]
61    hover_tips: Option<PlainText>,
62    /// 是否禁用交互容器。可选值:
63    ///
64    /// - true:禁用整个容器
65    /// - false:容器组件保持可用状态
66    #[serde(skip_serializing_if = "Option::is_none")]
67    disabled: Option<bool>,
68    /// 禁用交互容器后,用户触发交互时的弹窗文案提醒。默认为空,即不弹窗
69    #[serde(skip_serializing_if = "Option::is_none")]
70    disabled_tips: Option<PlainText>,
71    /// 二次确认弹窗配置。指在用户提交时弹出二次确认弹窗提示;只有用户点击确认后,
72    /// 才提交输入的内容。该字段默认提供了确认和取消按钮,你只需要配置弹窗的标题与内容即可。
73    ///
74    /// 注意:confirm 字段仅在用户点击包含提交属性的按钮时才会触发二次确认弹窗。
75    #[serde(skip_serializing_if = "Option::is_none")]
76    confirm: Option<InputConfirm>,
77    /// 交互容器内嵌的组件。仅支持内嵌普通文本、富文本、图片、备注、分栏、勾选器、交互容器组件
78    elements: Vec<CardElement>,
79}
80
81impl Default for InteractiveContainer {
82    fn default() -> Self {
83        InteractiveContainer {
84            tag: "interactive_container".to_string(),
85            width: None,
86            height: None,
87            background_style: None,
88            has_border: None,
89            border_color: None,
90            corner_radius: None,
91            padding: None,
92            behaviors: vec![],
93            hover_tips: None,
94            disabled: None,
95            disabled_tips: None,
96            confirm: None,
97            elements: vec![],
98        }
99    }
100}
101
102impl InteractiveContainer {
103    pub fn new() -> Self {
104        InteractiveContainer::default()
105    }
106
107    pub fn width(mut self, width: &str) -> Self {
108        self.width = Some(width.to_string());
109        self
110    }
111
112    pub fn height(mut self, height: &str) -> Self {
113        self.height = Some(height.to_string());
114        self
115    }
116
117    pub fn background_style(mut self, background_style: &str) -> Self {
118        self.background_style = Some(background_style.to_string());
119        self
120    }
121
122    pub fn has_border(mut self, has_border: bool) -> Self {
123        self.has_border = Some(has_border);
124        self
125    }
126
127    pub fn border_color(mut self, border_color: &str) -> Self {
128        self.border_color = Some(border_color.to_string());
129        self
130    }
131
132    pub fn corner_radius(mut self, corner_radius: &str) -> Self {
133        self.corner_radius = Some(corner_radius.to_string());
134        self
135    }
136
137    pub fn padding(mut self, padding: &str) -> Self {
138        self.padding = Some(padding.to_string());
139        self
140    }
141
142    pub fn behaviors(mut self, behaviors: Vec<Behaviors>) -> Self {
143        self.behaviors = behaviors;
144        self
145    }
146
147    pub fn hover_tips(mut self, hover_tips: PlainText) -> Self {
148        self.hover_tips = Some(hover_tips);
149        self
150    }
151
152    pub fn disabled(mut self, disabled: bool) -> Self {
153        self.disabled = Some(disabled);
154        self
155    }
156
157    pub fn disabled_tips(mut self, disabled_tips: PlainText) -> Self {
158        self.disabled_tips = Some(disabled_tips);
159        self
160    }
161
162    pub fn confirm(mut self, confirm: InputConfirm) -> Self {
163        self.confirm = Some(confirm);
164        self
165    }
166
167    pub fn elements(mut self, elements: Vec<CardElement>) -> Self {
168        self.elements = elements;
169        self
170    }
171}
172
173#[cfg(test)]
174mod test {
175    use serde_json::json;
176
177    use crate::card::{
178        components::{
179            containers::interactive::InteractiveContainer,
180            content_components::plain_text::PlainText,
181        },
182        interactions::{Behaviors, CallbackBehavior, FormBehavior, OpenUrlBehavior},
183    };
184
185    #[test]
186    fn test_interactive() {
187        let interactive = InteractiveContainer::new()
188            .width("fill")
189            .height("auto")
190            .background_style("default")
191            .has_border(false)
192            .border_color("grey")
193            .corner_radius("40px")
194            .padding("10px 20px 10px 20px")
195            .behaviors(vec![
196                Behaviors::OpenUrl(
197                    OpenUrlBehavior::new("https://www.baidu.com")
198                        .android_url("https://developer.android.com/")
199                        .ios_url("lark://msgcard/unsupported_action")
200                        .pc_url("https://www.windows.com"),
201                ),
202                Behaviors::Callback(CallbackBehavior::new(json!({
203                    "key": "value"
204                }))),
205                Behaviors::Form(FormBehavior::new().behavior("submit")),
206            ])
207            .hover_tips(PlainText::text("demo"))
208            .disabled(false)
209            .disabled_tips(PlainText::text("demo"))
210            .elements(vec![]);
211
212        let expect = json!({
213          "tag": "interactive_container", // 交互容器的标签。
214          "width": "fill", // 交互容器的宽度。默认值 fill。
215          "height": "auto", // 交互容器的高度。默认值 auto。
216          "background_style": "default", // 背景色。默认值 default(无背景色)。
217          "has_border": false, // 是否展示边框,粗细固定为 1px。默认值 false。
218          "border_color": "grey", // 交互容器的边框颜色,仅 has_border 为 true 时生效。
219          "corner_radius": "40px", // 交互容器的圆角半径。可选。
220          "padding": "10px 20px 10px 20px", // 交互容器的内边距。默认值 "4px 12px 4px 12px"。
221          "behaviors": [
222            {
223              "type": "open_url", // 声明交互类型是打开链接的跳转交互。
224              "default_url": "https://www.baidu.com", // 兜底跳转地址。
225              "android_url": "https://developer.android.com/", // 安卓端跳转地址。
226              "ios_url": "lark://msgcard/unsupported_action", // iOS 端跳转地址。
227              "pc_url": "https://www.windows.com" // 桌面端跳转地址。
228            },
229            {
230              "type": "callback", // 声明交互类型是回传数据到服务端的回传交互。
231              "value": {
232                // 回传交互数据
233                "key": "value"
234              }
235            },
236            {
237              "type": "form_action", // 声明交互类型为表单事件。
238              "behavior": "submit" // 声明表单事件类型。默认为 submit。
239            }
240          ],
241          "disabled": false,
242          "disabled_tips": { "tag": "plain_text", "content": "demo" },
243          "hover_tips": {
244            "tag": "plain_text",
245            "content": "demo"
246          },
247          "elements": [] // 容器子组件,仅支持内嵌普通文本、富文本、图片、备注、分栏、勾选器、交互容器组件。
248        });
249
250        assert_eq!(json!(interactive), expect);
251    }
252}