open_lark/card/components/content_components/
table.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Debug, Serialize, Deserialize)]
5pub struct FeishuCardTable {
6    /// 组件的标签。表格组件的固定取值为 table。
7    tag: String,
8    /// 每页最大展示的数据行数。支持 [1,10] 整数。
9    #[serde(skip_serializing_if = "Option::is_none")]
10    page_size: Option<i32>,
11    /// 表格的行高。单元格高度如无法展示一整行内容,则上下裁剪内容。可取值:
12    ///
13    /// low:低
14    /// middle:中
15    /// high:高
16    /// [32,124]px:自定义行高,单位为像素,如 40px。取值范围是 [32,124]
17    #[serde(skip_serializing_if = "Option::is_none")]
18    row_height: Option<String>,
19    /// 表头样式风格。
20    #[serde(skip_serializing_if = "Option::is_none")]
21    header_style: Option<FeishuCardTableHeaderStyle>,
22    /// 列对象数组
23    columns: Vec<FeishuCardTableColumn>,
24    /// 行对象数组。与列定义对应的数据。用 "name":VALUE
25    /// 的形式,定义每一行的数据内容。name即你自定义的列标记。
26    rows: Value,
27}
28
29impl Default for FeishuCardTable {
30    fn default() -> Self {
31        FeishuCardTable {
32            tag: "table".to_string(),
33            page_size: None,
34            row_height: None,
35            header_style: None,
36            columns: vec![],
37            rows: Value::Null,
38        }
39    }
40}
41
42/// 用于设置表头的样式、风格等。header_style 的子字段如下表所示。
43#[derive(Debug, Serialize, Deserialize, Default)]
44pub struct FeishuCardTableHeaderStyle {
45    /// 表头文本对齐方式。可取值:
46    ///
47    /// - left:左对齐
48    /// - center:居中对齐
49    /// - right:右对齐
50    #[serde(skip_serializing_if = "Option::is_none")]
51    text_align: Option<String>,
52    /// 表头文本大小。可取值:
53    ///
54    /// normal:正文(14px)
55    /// heading:标题(16px)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    text_size: Option<String>,
58    /// 表头背景色。可取值:
59    ///
60    /// - grey:灰色
61    /// - none:无背景色
62    #[serde(skip_serializing_if = "Option::is_none")]
63    background_style: Option<String>,
64    /// 文本颜色。可取值:
65    ///
66    /// - default:客户端浅色主题模式下为黑色;客户端深色主题模式下为白色
67    /// - grey:灰色
68    #[serde(skip_serializing_if = "Option::is_none")]
69    text_color: Option<String>,
70    /// 表头文本是否加粗。可取值:
71    ///
72    /// - true:加粗
73    /// - false:不加粗
74    #[serde(skip_serializing_if = "Option::is_none")]
75    bold: Option<bool>,
76    /// 表头文本是否加粗。可取值:
77    /// 表头文本的行数。支持大于等于 1 的整数。
78    #[serde(skip_serializing_if = "Option::is_none")]
79    lines: Option<i32>,
80}
81
82impl FeishuCardTableHeaderStyle {
83    pub fn new() -> Self {
84        Self::default()
85    }
86
87    pub fn text_align(mut self, text_align: &str) -> Self {
88        self.text_align = Some(text_align.to_string());
89        self
90    }
91
92    pub fn text_size(mut self, text_size: &str) -> Self {
93        self.text_size = Some(text_size.to_string());
94        self
95    }
96
97    pub fn background_style(mut self, background_style: &str) -> Self {
98        self.background_style = Some(background_style.to_string());
99        self
100    }
101
102    pub fn text_color(mut self, text_color: &str) -> Self {
103        self.text_color = Some(text_color.to_string());
104        self
105    }
106
107    pub fn bold(mut self, bold: bool) -> Self {
108        self.bold = Some(bold);
109        self
110    }
111
112    pub fn lines(mut self, lines: i32) -> Self {
113        self.lines = Some(lines);
114        self
115    }
116}
117
118/// 用于定义表格的列。最多支持添加 10 列,超出 10 列的内容不展示。
119#[derive(Debug, Serialize, Deserialize)]
120pub struct FeishuCardTableColumn {
121    /// 自定义列的标记。用于唯一指定行数据对象数组中,需要将数据填充至这一行的具体哪个单元格中。
122    name: String,
123    /// 在表头展示的列名称。不填或为空则不展示列名称。
124    #[serde(skip_serializing_if = "Option::is_none")]
125    display_name: Option<String>,
126    /// 列宽度。可取值:
127    ///
128    /// - auto:自适应内容宽度
129    /// - 自定义宽度:自定义表格的列宽度,如 120px。取值范围是 [80px,600px] 的整数
130    /// - 自定义宽度百分比:自定义列宽度占当前表格画布宽度的百分比(表格画布宽度 =
131    ///   卡片宽度-卡片左右内边距),如 25%。取值范围是 [1%,100%]
132    #[serde(skip_serializing_if = "Option::is_none")]
133    width: Option<String>,
134    /// 列内数据对齐方式。可选值:
135    ///
136    /// - left:左对齐
137    /// - center:居中对齐
138    /// - right:右对齐
139    #[serde(skip_serializing_if = "Option::is_none")]
140    horizontal_align: Option<String>,
141    /// 列数据类型。可选值:
142    ///
143    /// - text:不带格式的普通文本
144    /// - options:选项标签
145    /// - number:数字。默认在单元格中右对齐展示。若选择该数据类型,你可继续在 column 中添加 format
146    ///   字段,设置数字的子格式属性
147    data_type: String,
148    /// 该字段仅当 data_type 为 number
149    /// 时生效,你可以在该字段内选择设置小数点位数、货币单位以及千分位样式。
150    #[serde(skip_serializing_if = "Option::is_none")]
151    format: Option<FeishuCardTableColumnFormat>,
152}
153
154/// 设置小数点位数、货币单位以及千分位样式。
155#[derive(Debug, Serialize, Deserialize, Default)]
156pub struct FeishuCardTableColumnFormat {
157    /// 数字的小数点位数。默认不限制小数点位数,原样透传展示开发者输入的数字。可填 0~10
158    /// 的整数。小数点位数为 0 表示取整数。
159    #[serde(skip_serializing_if = "Option::is_none")]
160    precision: Option<i32>,
161    /// 数字前的货币单位。不填或为空不展示。可填 1 个字符的货币单位文本,如 “¥”。
162    #[serde(skip_serializing_if = "Option::is_none")]
163    symbol: Option<String>,
164    /// 是否生效按千分位逗号分割的数字样式。
165    #[serde(skip_serializing_if = "Option::is_none")]
166    separator: Option<bool>,
167}
168
169impl FeishuCardTableColumnFormat {
170    pub fn new() -> Self {
171        FeishuCardTableColumnFormat::default()
172    }
173
174    pub fn precision(mut self, precision: i32) -> Self {
175        self.precision = Some(precision);
176        self
177    }
178
179    pub fn symbol(mut self, symbol: &str) -> Self {
180        self.symbol = Some(symbol.to_string());
181        self
182    }
183
184    pub fn separator(mut self, separator: bool) -> Self {
185        self.separator = Some(separator);
186        self
187    }
188}
189
190impl Default for FeishuCardTableColumn {
191    fn default() -> Self {
192        FeishuCardTableColumn {
193            name: "".to_string(),
194            display_name: None,
195            width: None,
196            horizontal_align: None,
197            data_type: "".to_string(),
198            format: None,
199        }
200    }
201}
202
203impl FeishuCardTableColumn {
204    pub fn new() -> Self {
205        Self::default()
206    }
207
208    pub fn name(mut self, name: &str) -> Self {
209        self.name = name.to_string();
210        self
211    }
212
213    pub fn display_name(mut self, display_name: &str) -> Self {
214        self.display_name = Some(display_name.to_string());
215        self
216    }
217
218    pub fn width(mut self, width: &str) -> Self {
219        self.width = Some(width.to_string());
220        self
221    }
222
223    pub fn horizontal_align(mut self, horizontal_align: &str) -> Self {
224        self.horizontal_align = Some(horizontal_align.to_string());
225        self
226    }
227
228    pub fn data_type(mut self, data_type: &str) -> Self {
229        self.data_type = data_type.to_string();
230        self
231    }
232
233    pub fn format(mut self, format: FeishuCardTableColumnFormat) -> Self {
234        self.format = Some(format);
235        self
236    }
237}
238
239impl FeishuCardTable {
240    pub fn new() -> Self {
241        Self::default()
242    }
243
244    pub fn page_zie(mut self, page_zie: i32) -> Self {
245        self.page_size = Some(page_zie);
246        self
247    }
248
249    pub fn row_height(mut self, row_height: &str) -> Self {
250        self.row_height = Some(row_height.to_string());
251        self
252    }
253
254    pub fn header_style(mut self, header_style: FeishuCardTableHeaderStyle) -> Self {
255        self.header_style = Some(header_style);
256        self
257    }
258
259    pub fn columns(mut self, columns: Vec<FeishuCardTableColumn>) -> Self {
260        self.columns = columns;
261        self
262    }
263
264    pub fn rows(mut self, rows: Value) -> Self {
265        self.rows = rows;
266        self
267    }
268}
269
270#[cfg(test)]
271mod test {
272    use serde_json::json;
273
274    use crate::card::components::content_components::table::{
275        FeishuCardTable, FeishuCardTableColumn, FeishuCardTableColumnFormat,
276        FeishuCardTableHeaderStyle,
277    };
278
279    #[test]
280    fn test_table() {
281        let table = FeishuCardTable::new()
282            .page_zie(1)
283            .row_height("middle")
284            .header_style(
285                FeishuCardTableHeaderStyle::new()
286                    .bold(true)
287                    .background_style("grey")
288                    .lines(1)
289                    .text_size("heading")
290                    .text_align("center")
291                ,
292            )
293            .columns(vec![
294                FeishuCardTableColumn::new()
295                    .name("customer_name")
296                    .display_name("客户名称")
297                    .data_type("text"),
298                FeishuCardTableColumn::new()
299                    .name("customer_scale")
300                    .display_name("客户规模")
301                    .data_type("options")
302                    .width("90px"),
303                FeishuCardTableColumn::new()
304                    .name("customer_arr")
305                    .display_name("ARR(万元)")
306                    .data_type("number")
307                    .format(
308                        FeishuCardTableColumnFormat::new()
309                            .precision(2)
310                            .symbol("¥"),
311                    )
312                    .width("120px"),
313                FeishuCardTableColumn::new()
314                    .name("customer_year")
315                    .display_name("签约年限")
316                    .data_type("text"),
317            ])
318            .rows(json!([
319                    {
320                        "customer_name": "飞书消息卡片是飞书中的一种功能,它允许用户通过机器人或应用以结构化(JSON)的方式发送和接收消息。",
321                        "customer_scale": [
322                            {
323                                "text": "S2",
324                                "color": "green"
325                            }
326                        ],
327                        "customer_arr": 26.57774928467545,
328                        "customer_year": "2年"
329                    }
330                ]));
331
332        let json = json!( {
333            "tag": "table",
334            "page_size": 1,
335            "row_height": "middle",
336            "header_style": {
337                "bold": true,
338                "background_style": "grey",
339                "lines": 1,
340                "text_size": "heading",
341                "text_align": "center"
342            },
343            "columns": [
344                {
345                    "name": "customer_name",
346                    "display_name": "客户名称",
347                    "data_type": "text"
348                },
349                {
350                    "name": "customer_scale",
351                    "display_name": "客户规模",
352                    "data_type": "options",
353                    "width": "90px"
354                },
355                {
356                    "name": "customer_arr",
357                    "display_name": "ARR(万元)",
358                    "data_type": "number",
359                    "format": {
360                        "symbol": "¥",
361                        "precision": 2
362                    },
363                    "width": "120px"
364                },
365                {
366                    "name": "customer_year",
367                    "display_name": "签约年限",
368                    "data_type": "text"
369                }
370            ],
371            "rows": [
372                {
373                    "customer_name": "飞书消息卡片是飞书中的一种功能,它允许用户通过机器人或应用以结构化(JSON)的方式发送和接收消息。",
374                    "customer_scale": [
375                        {
376                            "text": "S2",
377                            "color": "green"
378                        }
379                    ],
380                    "customer_arr": 26.57774928467545,
381                    "customer_year": "2年"
382                }
383            ]
384        });
385
386        assert_eq!(serde_json::to_value(&table).unwrap(), json);
387    }
388}