From 669ca713cfacf8bc40dbb58bc69e3104f078989c Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 13 May 2026 11:43:37 +0800 Subject: [PATCH] fix(feishu): add configurable margin to card sections and actions for improved layout with images --- app/modules/feishu/feishu.py | 40 ++++++++++++++++++++++++------------ tests/test_feishu.py | 6 ++++++ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/app/modules/feishu/feishu.py b/app/modules/feishu/feishu.py index ce73fa43..e501ec44 100644 --- a/app/modules/feishu/feishu.py +++ b/app/modules/feishu/feishu.py @@ -554,15 +554,24 @@ class Feishu: return escaped @classmethod - def _build_markdown_section(cls, text: Optional[str], text_size: str = "normal") -> Optional[dict]: + def _build_markdown_section( + cls, + text: Optional[str], + text_size: str = "normal", + margin: Optional[str] = None, + ) -> Optional[dict]: content = cls._escape_card_text(text).strip() if not content: return None - return { + section = { "tag": "markdown", "text_size": text_size, "content": content, } + if margin: + # 图片卡片需要 body 零边距,文字留白转移到组件外边距上。 + section["margin"] = margin + return section @staticmethod def _build_message_text(title: Optional[str], text: Optional[str], link: Optional[str] = None) -> str: @@ -644,7 +653,7 @@ class Feishu: return str(callback_data).strip() if callback_data else None @staticmethod - def _card_actions(buttons: Optional[List[List[dict]]]) -> List[dict]: + def _card_actions(buttons: Optional[List[List[dict]]], margin: Optional[str] = None) -> List[dict]: """将统一按钮结构转换为飞书卡片按钮配置。""" if not buttons: return [] @@ -698,13 +707,14 @@ class Feishu: } ) if columns: - card_rows.append( - { - "tag": "column_set", - "flex_mode": "none", - "columns": columns, - } - ) + row = { + "tag": "column_set", + "flex_mode": "none", + "columns": columns, + } + if margin: + row["margin"] = margin + card_rows.append(row) return card_rows def _build_card( @@ -730,16 +740,20 @@ class Feishu: "mode": "fit_horizontal", } ) - title_section = self._build_markdown_section(title, text_size="heading") + text_margin = "12px 12px 0px 12px" if image_key else None + body_margin = "4px 12px 12px 12px" if image_key else None + action_margin = "0px 12px 12px 12px" if image_key else None + title_section = self._build_markdown_section(title, text_size="heading", margin=text_margin) body_section = self._build_markdown_section( self._build_message_text(title=None, text=text, link=link), text_size="normal", + margin=body_margin, ) if title_section: elements.append(title_section) if body_section: elements.append(body_section) - elements.extend(self._card_actions(buttons)) + elements.extend(self._card_actions(buttons, margin=action_margin)) return { # 飞书卡片消息要支持后续 PATCH 更新,发送和更新时都必须显式声明 update_multi。 "schema": "2.0", @@ -753,7 +767,7 @@ class Feishu: }, "body": { "direction": "vertical", - "padding": "0px 0px 0px 0px", + "padding": "0px 0px 0px 0px" if image_key else "12px 12px 12px 12px", "elements": elements, }, } diff --git a/tests/test_feishu.py b/tests/test_feishu.py index af4b4ea6..7e662b85 100644 --- a/tests/test_feishu.py +++ b/tests/test_feishu.py @@ -241,6 +241,7 @@ class TestFeishu(unittest.TestCase): self.assertNotIn("card", content) self.assertEqual(content["schema"], "2.0") self.assertTrue(content["config"]["update_multi"]) + self.assertEqual(content["body"]["padding"], "12px 12px 12px 12px") self.assertEqual(content["body"]["elements"][0]["text_size"], "heading") self.assertEqual(content["body"]["elements"][0]["tag"], "markdown") button = content["body"]["elements"][-1]["columns"][0]["elements"][0] @@ -287,6 +288,9 @@ class TestFeishu(unittest.TestCase): image_element = content["body"]["elements"][0] self.assertEqual(image_element["tag"], "img") self.assertEqual(image_element["img_key"], "img_v2_remote") + self.assertEqual(content["body"]["elements"][1]["margin"], "12px 12px 0px 12px") + self.assertEqual(content["body"]["elements"][2]["margin"], "4px 12px 12px 12px") + self.assertEqual(content["body"]["elements"][-1]["margin"], "0px 12px 12px 12px") self.assertEqual(content["body"]["elements"][-1]["tag"], "column_set") def test_send_notification_supports_user_id_target(self): @@ -586,7 +590,9 @@ class TestFeishu(unittest.TestCase): self.assertEqual(content["body"]["padding"], "0px 0px 0px 0px") self.assertEqual(content["body"]["elements"][0]["img_key"], "img_v2_uploaded") self.assertEqual(content["body"]["elements"][1]["content"], "图片标题") + self.assertEqual(content["body"]["elements"][1]["margin"], "12px 12px 0px 12px") self.assertEqual(content["body"]["elements"][2]["content"], "图片说明") + self.assertEqual(content["body"]["elements"][2]["margin"], "4px 12px 12px 12px") def test_send_file_keeps_non_image_file_message_and_caption(self): client = self._build_client()