1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
use serde::{Deserialize, Serialize};

use crate::card::{
    components::{
        content_components::plain_text::PlainText, interactive_components::input::InputConfirm,
        CardElement,
    },
    interactions::Behaviors,
};

/// 交互容器
#[derive(Debug, Serialize, Deserialize)]
pub struct InteractiveContainer {
    /// 交互容器的标签。固定值为 interactive_container。
    tag: String,
    /// 交互容器的宽度。可取值:
    ///
    /// - fill:卡片最大支持宽度
    /// - auto:自适应宽度
    /// - [16,999]px:自定义宽度,如 "20px"。最小宽度为 16px
    #[serde(skip_serializing_if = "Option::is_none")]
    width: Option<String>,
    /// 交互容器的高度。可取值:
    ///
    /// - auto:自适应高度
    /// - [10,999]px:自定义高度,如 "20px"
    #[serde(skip_serializing_if = "Option::is_none")]
    height: Option<String>,
    /// 交互容器的背景色样式。可取值:
    ///
    /// - default:默认的白底样式,客户端深色主题下为黑底
    /// - laser:镭射渐变彩色样式
    /// - 卡片支持的颜色枚举值和 RGBA 语法自定义颜色。参考颜色枚举值
    #[serde(skip_serializing_if = "Option::is_none")]
    background_style: Option<String>,
    /// 是否展示边框,粗细固定为 1px。
    #[serde(skip_serializing_if = "Option::is_none")]
    has_border: Option<bool>,
    /// 边框的颜色,仅 has_border 为 true 时,此字段生效。枚举值为卡片支持的颜色枚举值和 RGBA
    /// 语法自定义颜色,参考颜色枚举值。
    #[serde(skip_serializing_if = "Option::is_none")]
    border_color: Option<String>,
    /// 交互容器的圆角半径,单位是像素(px)或百分比(%)。取值遵循以下格式:
    ///
    /// - [0,∞]px,如 "10px"
    /// - [0,100]%,如 "30%"
    #[serde(skip_serializing_if = "Option::is_none")]
    corner_radius: Option<String>,
    /// 交互容器的内边距。值的取值范围为 [0,28]px。支持填写单值或多值:
    ///
    /// - 单值:如 "10px",表示容器内四个内边距都为 10px
    /// - 多值:如 "4px 12px 4px 12px",表示容器内上、右、下、左的内边距分别为
    ///   4px,12px,4px,12px。四个值必填,使用空格间隔
    #[serde(skip_serializing_if = "Option::is_none")]
    padding: Option<String>,
    /// 设置点击交互容器时的交互配置。如果交互容器内有交互组件,则优先响应交互组件定义的交互。
    /// 详情参考配置卡片交互
    behaviors: Vec<Behaviors>,
    /// 用户在 PC 端将光标悬浮在交互容器上方时的文案提醒。默认为空。
    #[serde(skip_serializing_if = "Option::is_none")]
    hover_tips: Option<PlainText>,
    /// 是否禁用交互容器。可选值:
    ///
    /// - true:禁用整个容器
    /// - false:容器组件保持可用状态
    #[serde(skip_serializing_if = "Option::is_none")]
    disabled: Option<bool>,
    /// 禁用交互容器后,用户触发交互时的弹窗文案提醒。默认为空,即不弹窗
    #[serde(skip_serializing_if = "Option::is_none")]
    disabled_tips: Option<PlainText>,
    /// 二次确认弹窗配置。指在用户提交时弹出二次确认弹窗提示;只有用户点击确认后,
    /// 才提交输入的内容。该字段默认提供了确认和取消按钮,你只需要配置弹窗的标题与内容即可。
    ///
    /// 注意:confirm 字段仅在用户点击包含提交属性的按钮时才会触发二次确认弹窗。
    #[serde(skip_serializing_if = "Option::is_none")]
    confirm: Option<InputConfirm>,
    /// 交互容器内嵌的组件。仅支持内嵌普通文本、富文本、图片、备注、分栏、勾选器、交互容器组件
    elements: Vec<CardElement>,
}

impl Default for InteractiveContainer {
    fn default() -> Self {
        InteractiveContainer {
            tag: "interactive_container".to_string(),
            width: None,
            height: None,
            background_style: None,
            has_border: None,
            border_color: None,
            corner_radius: None,
            padding: None,
            behaviors: vec![],
            hover_tips: None,
            disabled: None,
            disabled_tips: None,
            confirm: None,
            elements: vec![],
        }
    }
}

impl InteractiveContainer {
    pub fn new() -> Self {
        InteractiveContainer::default()
    }

    pub fn width(mut self, width: &str) -> Self {
        self.width = Some(width.to_string());
        self
    }

    pub fn height(mut self, height: &str) -> Self {
        self.height = Some(height.to_string());
        self
    }

    pub fn background_style(mut self, background_style: &str) -> Self {
        self.background_style = Some(background_style.to_string());
        self
    }

    pub fn has_border(mut self, has_border: bool) -> Self {
        self.has_border = Some(has_border);
        self
    }

    pub fn border_color(mut self, border_color: &str) -> Self {
        self.border_color = Some(border_color.to_string());
        self
    }

    pub fn corner_radius(mut self, corner_radius: &str) -> Self {
        self.corner_radius = Some(corner_radius.to_string());
        self
    }

    pub fn padding(mut self, padding: &str) -> Self {
        self.padding = Some(padding.to_string());
        self
    }

    pub fn behaviors(mut self, behaviors: Vec<Behaviors>) -> Self {
        self.behaviors = behaviors;
        self
    }

    pub fn hover_tips(mut self, hover_tips: PlainText) -> Self {
        self.hover_tips = Some(hover_tips);
        self
    }

    pub fn disabled(mut self, disabled: bool) -> Self {
        self.disabled = Some(disabled);
        self
    }

    pub fn disabled_tips(mut self, disabled_tips: PlainText) -> Self {
        self.disabled_tips = Some(disabled_tips);
        self
    }

    pub fn confirm(mut self, confirm: InputConfirm) -> Self {
        self.confirm = Some(confirm);
        self
    }

    pub fn elements(mut self, elements: Vec<CardElement>) -> Self {
        self.elements = elements;
        self
    }
}

#[cfg(test)]
mod test {
    use serde_json::json;

    use crate::card::{
        components::{
            containers::interactive::InteractiveContainer,
            content_components::plain_text::PlainText,
        },
        interactions::{Behaviors, CallbackBehavior, FormBehavior, OpenUrlBehavior},
    };

    #[test]
    fn test_interactive() {
        let interactive = InteractiveContainer::new()
            .width("fill")
            .height("auto")
            .background_style("default")
            .has_border(false)
            .border_color("grey")
            .corner_radius("40px")
            .padding("10px 20px 10px 20px")
            .behaviors(vec![
                Behaviors::OpenUrl(
                    OpenUrlBehavior::new("https://www.baidu.com")
                        .android_url("https://developer.android.com/")
                        .ios_url("lark://msgcard/unsupported_action")
                        .pc_url("https://www.windows.com"),
                ),
                Behaviors::Callback(CallbackBehavior::new(json!({
                    "key": "value"
                }))),
                Behaviors::Form(FormBehavior::new().behavior("submit")),
            ])
            .hover_tips(PlainText::text("demo"))
            .disabled(false)
            .disabled_tips(PlainText::text("demo"))
            .elements(vec![]);

        let expect = json!({
          "tag": "interactive_container", // 交互容器的标签。
          "width": "fill", // 交互容器的宽度。默认值 fill。
          "height": "auto", // 交互容器的高度。默认值 auto。
          "background_style": "default", // 背景色。默认值 default(无背景色)。
          "has_border": false, // 是否展示边框,粗细固定为 1px。默认值 false。
          "border_color": "grey", // 交互容器的边框颜色,仅 has_border 为 true 时生效。
          "corner_radius": "40px", // 交互容器的圆角半径。可选。
          "padding": "10px 20px 10px 20px", // 交互容器的内边距。默认值 "4px 12px 4px 12px"。
          "behaviors": [
            {
              "type": "open_url", // 声明交互类型是打开链接的跳转交互。
              "default_url": "https://www.baidu.com", // 兜底跳转地址。
              "android_url": "https://developer.android.com/", // 安卓端跳转地址。
              "ios_url": "lark://msgcard/unsupported_action", // iOS 端跳转地址。
              "pc_url": "https://www.windows.com" // 桌面端跳转地址。
            },
            {
              "type": "callback", // 声明交互类型是回传数据到服务端的回传交互。
              "value": {
                // 回传交互数据
                "key": "value"
              }
            },
            {
              "type": "form_action", // 声明交互类型为表单事件。
              "behavior": "submit" // 声明表单事件类型。默认为 submit。
            }
          ],
          "disabled": false,
          "disabled_tips": { "tag": "plain_text", "content": "demo" },
          "hover_tips": {
            "tag": "plain_text",
            "content": "demo"
          },
          "elements": [] // 容器子组件,仅支持内嵌普通文本、富文本、图片、备注、分栏、勾选器、交互容器组件。
        });

        assert_eq!(json!(interactive), expect);
    }
}