fix(notification): 修复邮件中表格不渲染及排版松散问题 (fixes #134)

- 引入 markdown2 库替代简单的正则替换,支持 Markdown 表格渲染
  - 重构邮件 HTML 生成逻辑,添加完整的 CSS 样式支持
  - 优化邮件排版,调整间距使内容更加紧凑易读
  - 增加表格边框、隔行变色等样式,提升数据展示效果
This commit is contained in:
zhulinsen
2026-01-25 11:43:49 +08:00
parent 711a2a9343
commit c9272c55ea
2 changed files with 115 additions and 40 deletions

View File

@@ -19,6 +19,7 @@ import logging
import json
import smtplib
import re
import markdown2
from datetime import datetime
from typing import List, Dict, Any, Optional
from email.mime.text import MIMEText
@@ -1763,56 +1764,129 @@ class NotificationService:
def _markdown_to_html(self, markdown_text: str) -> str:
"""
将 Markdown 转换为简单的 HTML
支持:标题、加粗、列表、分隔线
将 Markdown 转换为 HTML,支持表格并优化排版
使用 markdown2 库进行转换,并添加优化的 CSS 样式
解决问题:
1. 邮件表格未渲染问题
2. 邮件内容排版过于松散问题
"""
html = markdown_text
# 转义 HTML 特殊字符
html = html.replace('&', '&')
html = html.replace('<', '&lt;')
html = html.replace('>', '&gt;')
# 标题 (# ## ###)
html = re.sub(r'^### (.+)$', r'<h3>\1</h3>', html, flags=re.MULTILINE)
html = re.sub(r'^## (.+)$', r'<h2>\1</h2>', html, flags=re.MULTILINE)
html = re.sub(r'^# (.+)$', r'<h1>\1</h1>', html, flags=re.MULTILINE)
# 加粗 **text**
html = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', html)
# 斜体 *text*
html = re.sub(r'\*(.+?)\*', r'<em>\1</em>', html)
# 分隔线 ---
html = re.sub(r'^---$', r'<hr>', html, flags=re.MULTILINE)
# 列表项 - item
html = re.sub(r'^- (.+)$', r'<li>\1</li>', html, flags=re.MULTILINE)
# 引用 > text
html = re.sub(r'^&gt; (.+)$', r'<blockquote>\1</blockquote>', html, flags=re.MULTILINE)
# 换行
html = html.replace('\n', '<br>\n')
# 包装 HTML
# 使用 markdown2 转换,开启表格和其他扩展支持
html_content = markdown2.markdown(
markdown_text,
extras=["tables", "fenced-code-blocks", "break-on-newline", "cuddled-lists"]
)
# 优化 CSS 样式:更紧凑的排版,美观的表格
css_style = """
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
line-height: 1.5;
color: #24292e;
font-size: 14px;
padding: 15px;
max-width: 900px;
margin: 0 auto;
}
h1 {
font-size: 20px;
border-bottom: 1px solid #eaecef;
padding-bottom: 0.3em;
margin-top: 1.2em;
margin-bottom: 0.8em;
color: #0366d6;
}
h2 {
font-size: 18px;
border-bottom: 1px solid #eaecef;
padding-bottom: 0.3em;
margin-top: 1.0em;
margin-bottom: 0.6em;
}
h3 {
font-size: 16px;
margin-top: 0.8em;
margin-bottom: 0.4em;
}
p {
margin-top: 0;
margin-bottom: 8px;
}
/* 表格样式优化 */
table {
border-collapse: collapse;
width: 100%;
margin: 12px 0;
display: block;
overflow-x: auto;
font-size: 13px;
}
th, td {
border: 1px solid #dfe2e5;
padding: 6px 10px;
text-align: left;
}
th {
background-color: #f6f8fa;
font-weight: 600;
}
tr:nth-child(2n) {
background-color: #f8f8f8;
}
tr:hover {
background-color: #f1f8ff;
}
/* 引用块样式 */
blockquote {
color: #6a737d;
border-left: 0.25em solid #dfe2e5;
padding: 0 1em;
margin: 0 0 10px 0;
}
/* 代码块样式 */
code {
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
background-color: rgba(27,31,35,0.05);
border-radius: 3px;
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
}
pre {
padding: 12px;
overflow: auto;
line-height: 1.45;
background-color: #f6f8fa;
border-radius: 3px;
margin-bottom: 10px;
}
hr {
height: 0.25em;
padding: 0;
margin: 16px 0;
background-color: #e1e4e8;
border: 0;
}
ul, ol {
padding-left: 20px;
margin-bottom: 10px;
}
li {
margin: 2px 0;
}
"""
return f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; padding: 20px; max-width: 800px; margin: 0 auto; }}
h1, h2, h3 {{ color: #333; }}
hr {{ border: none; border-top: 1px solid #ddd; margin: 20px 0; }}
blockquote {{ border-left: 4px solid #ddd; padding-left: 16px; color: #666; }}
li {{ margin: 4px 0; }}
{css_style}
</style>
</head>
<body>
{html}
{html_content}
</body>
</html>
"""

View File

@@ -32,6 +32,7 @@ google-search-results>=2.4.0 # SerpAPI每月 100 次免费)
# 网络请求
requests>=2.31.0 # HTTP 请求
markdown2>=2.4.0 # Markdown 转 HTML
fake-useragent>=1.4.0 # 随机 User-Agent 防封禁
httpx[socks] # HTTP 客户端 + SOCKS 代理支持OpenAI 可选依赖)
dingtalk-stream >= 0.24.3 # 钉钉 Stream SDK