open_lark/card/components/interactive_components/
button.rs

1use serde::{Deserialize, Serialize};
2
3use crate::card::{
4    components::{
5        content_components::plain_text::PlainText, interactive_components::input::InputConfirm,
6    },
7    icon::FeishuCardTextIcon,
8    interactions::Behaviors,
9};
10
11#[derive(Debug, Serialize, Deserialize)]
12pub struct FeishuCardButton {
13    /// 组件的标签。按钮组件的固定值为 button。
14    tag: String,
15    /// 按钮的类型。可选值:
16    ///
17    /// - default:黑色字体按钮,有边框
18    /// - primary:蓝色字体按钮,有边框
19    /// - danger:红色字体按钮,有边框
20    /// - text:黑色字体按钮,无边框
21    /// - primary_text:蓝色字体按钮,无边框
22    /// - danger_text:红色字体按钮,无边框
23    /// - primary_filled:蓝底白字按钮
24    /// - danger_filled:红底白字按钮
25    /// - laser:镭射按钮
26    #[serde(skip_serializing_if = "Option::is_none")]
27    r#type: Option<String>,
28    /// 按钮的尺寸。可选值:
29    ///
30    /// - tiny:超小尺寸,PC 端为 24 px;移动端为 28 px
31    /// - small:小尺寸,PC 端为 28 px;移动端为 28 px
32    /// - medium:中尺寸,PC 端为 32 px;移动端为 36 px
33    /// - large:大尺寸,PC 端为 40 px;移动端为 48 px
34    #[serde(skip_serializing_if = "Option::is_none")]
35    size: Option<String>,
36    /// 按钮的宽度。支持以下枚举值:
37    ///
38    /// - default:默认宽度
39    /// - fill:卡片最大支持宽度
40    /// - [100,∞)px:自定义宽度,如 120px。超出卡片宽度时将按最大支持宽度展示
41    #[serde(skip_serializing_if = "Option::is_none")]
42    width: Option<String>,
43    /// 按钮上的文本。
44    #[serde(skip_serializing_if = "Option::is_none")]
45    text: Option<PlainText>,
46    /// 添加图标作为文本前缀图标
47    #[serde(skip_serializing_if = "Option::is_none")]
48    icon: Option<FeishuCardTextIcon>,
49    /// 用户在 PC 端将光标悬浮在交互容器上方时的文案提醒。默认为空。
50    #[serde(skip_serializing_if = "Option::is_none")]
51    hover_tips: Option<PlainText>,
52    /// 是否禁按钮。可选值:
53    ///
54    /// - true:禁用按钮
55    /// - false:按钮组件保持可用状态
56    #[serde(skip_serializing_if = "Option::is_none")]
57    disabled: Option<bool>,
58    /// 禁用按钮后,用户触发交互时的弹窗文案提醒。默认为空,即不弹窗
59    #[serde(skip_serializing_if = "Option::is_none")]
60    disabled_tips: Option<PlainText>,
61    /// 二次确认弹窗配置。指在用户提交时弹出二次确认弹窗提示;只有用户点击确认后,
62    /// 才提交输入的内容。该字段默认提供了确认和取消按钮,你只需要配置弹窗的标题与内容即可。
63    ///
64    /// 注意:confirm 字段仅在用户点击包含提交属性的按钮时才会触发二次确认弹窗。
65    #[serde(skip_serializing_if = "Option::is_none")]
66    confirm: Option<InputConfirm>,
67    /// 配置交互类型和具体交互行为。支持同时生效跳转链接和回传交互。
68    #[serde(skip_serializing_if = "Option::is_none")]
69    behaviors: Option<Vec<Behaviors>>,
70    /// 表单容器内组件的唯一标识。用于识别用户提交的数据属于哪个组件。
71    ///
72    /// 注意:该字段必填且需在卡片全局内唯一。
73    #[serde(skip_serializing_if = "Option::is_none")]
74    name: Option<String>,
75    /// 组件的内容是否必填。当组件内嵌在表单容器中时,该属性生效。可取值:
76    ///
77    /// - true:必填。当用户点击表单容器的“提交”时,未填写该组件,则前端提示“有必填项未填写”,
78    ///   不会向开发者的服务端发起回传请求。
79    ///
80    /// - false:选填。当用户点击表单容器的“提交”时,未填写该组件,仍提交表单容器中的数据。
81    #[serde(skip_serializing_if = "Option::is_none")]
82    required: Option<bool>,
83    /// 内嵌在表单容器中的按钮的交互类型。枚举值包括:
84    ///
85    /// - link:当前按钮仅支持链接跳转
86    /// - request:当前按钮仅支持回传交互
87    /// - multi:当前按钮同时支持链接跳转和回传交互
88    /// - form_submit:将当前按钮与提交事件绑定。用户点击后,将触发表单容器的提交事件,
89    ///   异步提交所有已填写的表单项内容
90    /// - form_reset:将当前按钮与取消提交事件绑定。用户点击后,将触发表单容器的取消提交事件,
91    ///   重置所有表单组件的输入值为初始值
92    #[serde(skip_serializing_if = "Option::is_none")]
93    action_type: Option<String>,
94}
95
96impl Default for FeishuCardButton {
97    fn default() -> Self {
98        Self {
99            tag: "button".to_string(),
100            r#type: None,
101            size: None,
102            width: None,
103            text: None,
104            icon: None,
105            hover_tips: None,
106            disabled: None,
107            disabled_tips: None,
108            confirm: None,
109            behaviors: None,
110            name: None,
111            required: None,
112            action_type: None,
113        }
114    }
115}
116
117impl FeishuCardButton {
118    pub fn new() -> Self {
119        Self::default()
120    }
121
122    /// 按钮的类型。默认为 default。
123    pub fn r#type(mut self, typ: &str) -> Self {
124        self.r#type = Some(typ.to_string());
125        self
126    }
127
128    pub fn size(mut self, size: &str) -> Self {
129        self.size = Some(size.to_string());
130        self
131    }
132
133    pub fn width(mut self, width: &str) -> Self {
134        self.width = Some(width.to_string());
135        self
136    }
137
138    pub fn text(mut self, text: PlainText) -> Self {
139        self.text = Some(text);
140        self
141    }
142
143    pub fn icon(mut self, icon: FeishuCardTextIcon) -> Self {
144        self.icon = Some(icon);
145        self
146    }
147
148    pub fn hover_tips(mut self, hover_tips: PlainText) -> Self {
149        self.hover_tips = Some(hover_tips);
150        self
151    }
152
153    pub fn disabled(mut self, disabled: bool) -> Self {
154        self.disabled = Some(disabled);
155        self
156    }
157
158    pub fn disabled_tips(mut self, disabled_tips: PlainText) -> Self {
159        self.disabled_tips = Some(disabled_tips);
160        self
161    }
162
163    pub fn confirm(mut self, confirm: InputConfirm) -> Self {
164        self.confirm = Some(confirm);
165        self
166    }
167
168    pub fn behaviors(mut self, behaviors: Vec<Behaviors>) -> Self {
169        self.behaviors = Some(behaviors);
170        self
171    }
172
173    pub fn name(mut self, name: &str) -> Self {
174        self.name = Some(name.to_string());
175        self
176    }
177
178    pub fn required(mut self, required: bool) -> Self {
179        self.required = Some(required);
180        self
181    }
182
183    pub fn action_type(mut self, action_type: &str) -> Self {
184        self.action_type = Some(action_type.to_string());
185        self
186    }
187}
188
189#[cfg(test)]
190mod test {
191    use serde_json::json;
192
193    use crate::card::{
194        components::{
195            content_components::plain_text::PlainText,
196            interactive_components::{button::FeishuCardButton, input::InputConfirm},
197        },
198        icon::FeishuCardTextIcon,
199        interactions::{Behaviors, CallbackBehavior, FormBehavior, OpenUrlBehavior},
200    };
201
202    #[test]
203    fn test_button() {
204        let button = FeishuCardButton::new()
205            .r#type("primary")
206            .size("small")
207            .width("default")
208            .text(PlainText::text("确定"))
209            .icon(
210                FeishuCardTextIcon::new()
211                    .token("chat-forbidden_outlined")
212                    .color("orange")
213                    .img_key("img_v2_38811724"),
214            )
215            .disabled(false)
216            .confirm(InputConfirm::new("title", "content"))
217            .behaviors(vec![
218                Behaviors::OpenUrl(
219                    OpenUrlBehavior::new("https://www.baidu.com")
220                        .android_url("https://developer.android.com/")
221                        .ios_url("lark://msgcard/unsupported_action")
222                        .pc_url("https://www.windows.com"),
223                ),
224                Behaviors::Callback(CallbackBehavior::new(json!({"key": "value"}))),
225                Behaviors::Form(FormBehavior::new().behavior("submit")),
226            ]);
227
228        let json = json!({
229          "tag": "button", // 组件的标签。按钮组件的固定值为 button。
230          "type": "primary", // 按钮的类型。默认为 default。
231          "size": "small", // 按钮的尺寸。默认值 medium。
232          "width": "default", // 按钮的宽度。默认为 default。
233          "text": {
234            // 按钮上的文本。
235            "tag": "plain_text",
236            "content": "确定"
237          },
238          "icon": {
239            // 前缀图标。
240            "tag": "standard_icon", // 图标类型。
241            "token": "chat-forbidden_outlined", // 图标的 token。仅在 tag 为 standard_icon 时生效。
242            "color": "orange", // 图标颜色。仅在 tag 为 standard_icon 时生效。
243            "img_key": "img_v2_38811724" // 图片的 key。仅在 tag 为 custom_icon 时生效。
244          },
245
246          "disabled": false, // 是否禁用该按钮。默认值 false。
247
248          "confirm": {
249            // 二次确认弹窗配置
250            "title": {
251              "tag": "plain_text",
252              "content": "title"
253            },
254            "text": {
255              "tag": "plain_text",
256              "content": "content"
257            }
258          },
259          "behaviors": [
260            {
261              "type": "open_url", // 声明交互类型是打开链接的跳转交互
262              "default_url": "https://www.baidu.com", // 兜底跳转地址
263              "android_url": "https://developer.android.com/", // 安卓端跳转地址
264              "ios_url": "lark://msgcard/unsupported_action", // iOS 端跳转地址。
265              "pc_url": "https://www.windows.com" // 桌面端跳转地址
266            },
267            {
268              "type": "callback", // 声明交互类型是回传数据到服务端的回传交互。
269              "value": {
270                // 回传交互数据。支持 string 或 object 数据类型。
271                "key": "value"
272              }
273            },
274            {
275              "type": "form_action", // 声明交互类型为表单事件。
276              "behavior": "submit" // 声明表单事件类型。默认为 submit。
277            }
278          ],
279        });
280
281        assert_eq!(serde_json::to_value(&button).unwrap(), json);
282    }
283}