使用Python生成個性化的電子郵件簽名
1. 引言
在數(shù)字通信時代,電子郵件仍然是商務溝通和個人交流的重要工具。據(jù)統(tǒng)計,全球每天發(fā)送的電子郵件數(shù)量超過3000億封,而專業(yè)的電子郵件簽名不僅能夠提供必要的聯(lián)系信息,還能增強品牌形象、建立信任關系并促進業(yè)務增長。
一個精心設計的電子郵件簽名具有以下重要作用:
- 專業(yè)形象:展示個人或公司的專業(yè)素養(yǎng)
- 品牌推廣:強化品牌識別和一致性
- 信息完整:提供全面的聯(lián)系方式和社交媒體鏈接
- 營銷機會:包含推廣信息和行動號召
- 法律合規(guī):滿足商業(yè)通信的法律要求
然而,手動創(chuàng)建和維護電子郵件簽名存在諸多挑戰(zhàn):
- 格式不一致,影響專業(yè)形象
- 更新聯(lián)系信息繁瑣且容易出錯
- 難以在不同郵件客戶端保持顯示一致性
- 缺乏個性化定制能力
本文將詳細介紹如何使用Python構建一個智能的個性化電子郵件簽名生成系統(tǒng)。通過這個系統(tǒng),用戶可以快速生成美觀、專業(yè)且一致的電子郵件簽名,支持動態(tài)內容、響應式設計和多平臺兼容。
2. 系統(tǒng)架構設計
2.1 整體架構概述
電子郵件簽名生成系統(tǒng)的核心架構采用模塊化設計,確保代碼的可維護性和擴展性。

2.2 核心模塊設計
系統(tǒng)包含以下關鍵模塊:
- 數(shù)據(jù)模型:定義簽名數(shù)據(jù)的結構和驗證規(guī)則
- 模板引擎:基于Jinja2的HTML模板渲染
- 樣式處理器:管理CSS樣式和響應式設計
- 圖像處理器:處理頭像、Logo等圖像資源
- 輸出生成器:生成HTML、純文本等多種格式
2.3 技術選型理由
選擇Python作為開發(fā)語言的主要原因:
- 豐富的庫生態(tài):擁有強大的HTML/CSS處理庫
- 模板引擎成熟:Jinja2模板引擎功能強大
- 圖像處理能力:Pillow庫提供專業(yè)的圖像處理功能
- 跨平臺兼容:確保在不同操作系統(tǒng)上的一致性
- 易于部署:簡單的部署和維護流程
3. 環(huán)境配置與依賴安裝
3.1 系統(tǒng)要求
- Python 3.8+
- 支持HTML5和CSS3的現(xiàn)代瀏覽器
- 足夠的磁盤空間存儲模板和生成的文件
3.2 依賴包安裝
創(chuàng)建requirements.txt文件:
Jinja2==3.1.2 Pillow==10.0.0 python-dotenv==1.0.0 click==8.1.4 colorama==0.4.6 requests==2.28.2 beautifulsoup4==4.12.2 lxml==4.9.2 qrcode==7.4.2
安裝依賴:
pip install -r requirements.txt
3.3 項目結構設計
email_signature_generator/
├── src/
│ ├── __init__.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── signature_data.py
│ │ └── validators.py
│ ├── generators/
│ │ ├── __init__.py
│ │ ├── html_generator.py
│ │ ├── text_generator.py
│ │ └── image_processor.py
│ ├── templates/
│ │ ├── html/
│ │ │ ├── corporate.html
│ │ │ ├── modern.html
│ │ │ └── minimal.html
│ │ └── css/
│ │ ├── corporate.css
│ │ ├── modern.css
│ │ └── minimal.css
│ ├── utils/
│ │ ├── __init__.py
│ │ ├── helpers.py
│ │ └── config.py
│ └── cli.py
├── tests/
├── examples/
├── docs/
├── requirements.txt
└── README.md
4. 數(shù)據(jù)模型設計
4.1 簽名數(shù)據(jù)模型
創(chuàng)建基礎數(shù)據(jù)模型來存儲簽名信息:
# src/models/signature_data.py
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Any
from datetime import datetime
import re
from enum import Enum
class SocialPlatform(Enum):
"""支持的社交媒體平臺枚舉"""
LINKEDIN = "linkedin"
TWITTER = "twitter"
FACEBOOK = "facebook"
INSTAGRAM = "instagram"
GITHUB = "github"
WEBSITE = "website"
YOUTUBE = "youtube"
class ThemeStyle(Enum):
"""主題樣式枚舉"""
CORPORATE = "corporate"
MODERN = "modern"
MINIMAL = "minimal"
CREATIVE = "creative"
@dataclass
class SocialMedia:
"""社交媒體鏈接數(shù)據(jù)類"""
platform: SocialPlatform
url: str
username: Optional[str] = None
@property
def display_name(self) -> str:
"""獲取平臺顯示名稱"""
platform_names = {
SocialPlatform.LINKEDIN: "LinkedIn",
SocialPlatform.TWITTER: "Twitter",
SocialPlatform.FACEBOOK: "Facebook",
SocialPlatform.INSTAGRAM: "Instagram",
SocialPlatform.GITHUB: "GitHub",
SocialPlatform.WEBSITE: "Website",
SocialPlatform.YOUTUBE: "YouTube"
}
return platform_names.get(self.platform, self.platform.value)
@dataclass
class ContactInfo:
"""聯(lián)系信息數(shù)據(jù)類"""
phone: Optional[str] = None
mobile: Optional[str] = None
email: Optional[str] = None
address: Optional[str] = None
website: Optional[str] = None
def get_display_phone(self) -> Optional[str]:
"""格式化顯示電話號碼"""
if not self.phone:
return None
# 簡單的電話號碼格式化
cleaned = re.sub(r'\D', '', self.phone)
if len(cleaned) == 10:
return f"({cleaned[:3]}) {cleaned[3:6]}-{cleaned[6:]}"
return self.phone
@dataclass
class SignatureData:
"""電子郵件簽名主數(shù)據(jù)類"""
# 基本信息
full_name: str
job_title: str
company: str
department: Optional[str] = None
# 聯(lián)系信息
contact: ContactInfo = field(default_factory=ContactInfo)
# 社交媒體
social_media: List[SocialMedia] = field(default_factory=list)
# 品牌信息
logo_url: Optional[str] = None
profile_picture_url: Optional[str] = None
brand_color: Optional[str] = None
secondary_color: Optional[str] = None
# 營銷信息
promotional_text: Optional[str] = None
call_to_action: Optional[str] = None
disclaimer: Optional[str] = None
# 樣式配置
theme: ThemeStyle = ThemeStyle.MODERN
include_border: bool = True
include_qr_code: bool = False
qr_code_url: Optional[str] = None
# 元數(shù)據(jù)
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
def add_social_media(self, platform: SocialPlatform, url: str, username: Optional[str] = None):
"""添加社交媒體鏈接"""
social = SocialMedia(platform=platform, url=url, username=username)
self.social_media.append(social)
def remove_social_media(self, platform: SocialPlatform):
"""移除社交媒體鏈接"""
self.social_media = [sm for sm in self.social_media if sm.platform != platform]
def get_social_media_by_platform(self, platform: SocialPlatform) -> Optional[SocialMedia]:
"""根據(jù)平臺獲取社交媒體信息"""
for social in self.social_media:
if social.platform == platform:
return social
return None
def to_dict(self) -> Dict[str, Any]:
"""轉換為字典格式"""
return {
'full_name': self.full_name,
'job_title': self.job_title,
'company': self.company,
'department': self.department,
'contact': {
'phone': self.contact.phone,
'mobile': self.contact.mobile,
'email': self.contact.email,
'address': self.contact.address,
'website': self.contact.website
},
'social_media': [
{
'platform': sm.platform.value,
'url': sm.url,
'username': sm.username
} for sm in self.social_media
],
'theme': self.theme.value,
'brand_color': self.brand_color,
'secondary_color': self.secondary_color,
'promotional_text': self.promotional_text,
'call_to_action': self.call_to_action,
'disclaimer': self.disclaimer
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'SignatureData':
"""從字典創(chuàng)建實例"""
contact_data = data.get('contact', {})
contact = ContactInfo(
phone=contact_data.get('phone'),
mobile=contact_data.get('mobile'),
email=contact_data.get('email'),
address=contact_data.get('address'),
website=contact_data.get('website')
)
signature = cls(
full_name=data['full_name'],
job_title=data['job_title'],
company=data['company'],
department=data.get('department'),
contact=contact,
brand_color=data.get('brand_color'),
secondary_color=data.get('secondary_color'),
promotional_text=data.get('promotional_text'),
call_to_action=data.get('call_to_action'),
disclaimer=data.get('disclaimer'),
theme=ThemeStyle(data.get('theme', 'modern'))
)
# 添加社交媒體
for sm_data in data.get('social_media', []):
platform = SocialPlatform(sm_data['platform'])
signature.add_social_media(platform, sm_data['url'], sm_data.get('username'))
return signature
4.2 數(shù)據(jù)驗證器
創(chuàng)建數(shù)據(jù)驗證器確保輸入數(shù)據(jù)的有效性:
# src/models/validators.py
import re
from typing import Optional, Tuple
from urllib.parse import urlparse
from datetime import datetime
class SignatureValidator:
"""簽名數(shù)據(jù)驗證器"""
@staticmethod
def validate_email(email: str) -> Tuple[bool, str]:
"""驗證電子郵件地址"""
if not email:
return True, "" # 空值視為有效
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if re.match(pattern, email):
return True, ""
return False, "無效的電子郵件格式"
@staticmethod
def validate_phone(phone: str) -> Tuple[bool, str]:
"""驗證電話號碼"""
if not phone:
return True, "" # 空值視為有效
# 移除所有非數(shù)字字符
cleaned = re.sub(r'\D', '', phone)
if 7 <= len(cleaned) <= 15:
return True, ""
return False, "電話號碼長度應在7-15位數(shù)字之間"
@staticmethod
def validate_url(url: str) -> Tuple[bool, str]:
"""驗證URL格式"""
if not url:
return True, "" # 空值視為有效
try:
result = urlparse(url)
if all([result.scheme, result.netloc]):
return True, ""
return False, "無效的URL格式"
except:
return False, "無效的URL格式"
@staticmethod
def validate_hex_color(color: str) -> Tuple[bool, str]:
"""驗證十六進制顏色值"""
if not color:
return True, "" # 空值視為有效
pattern = r'^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$'
if re.match(pattern, color):
return True, ""
return False, "顏色格式應為 #RGB 或 #RRGGBB"
@staticmethod
def validate_full_name(name: str) -> Tuple[bool, str]:
"""驗證全名"""
if not name or len(name.strip()) < 2:
return False, "姓名不能少于2個字符"
if len(name) > 100:
return False, "姓名不能超過100個字符"
return True, ""
@staticmethod
def validate_company(company: str) -> Tuple[bool, str]:
"""驗證公司名稱"""
if not company or len(company.strip()) < 1:
return False, "公司名稱不能為空"
if len(company) > 200:
return False, "公司名稱不能超過200個字符"
return True, ""
@staticmethod
def validate_job_title(title: str) -> Tuple[bool, str]:
"""驗證職位名稱"""
if not title or len(title.strip()) < 1:
return False, "職位名稱不能為空"
if len(title) > 100:
return False, "職位名稱不能超過100個字符"
return True, ""
def validate_signature_data(self, data: 'SignatureData') -> Tuple[bool, Dict[str, str]]:
"""驗證完整的簽名數(shù)據(jù)"""
errors = {}
# 驗證基本信息
is_valid, message = self.validate_full_name(data.full_name)
if not is_valid:
errors['full_name'] = message
is_valid, message = self.validate_job_title(data.job_title)
if not is_valid:
errors['job_title'] = message
is_valid, message = self.validate_company(data.company)
if not is_valid:
errors['company'] = message
# 驗證聯(lián)系信息
if data.contact.email:
is_valid, message = self.validate_email(data.contact.email)
if not is_valid:
errors['email'] = message
if data.contact.phone:
is_valid, message = self.validate_phone(data.contact.phone)
if not is_valid:
errors['phone'] = message
if data.contact.website:
is_valid, message = self.validate_url(data.contact.website)
if not is_valid:
errors['website'] = message
# 驗證社交媒體鏈接
for social in data.social_media:
is_valid, message = self.validate_url(social.url)
if not is_valid:
errors[f'social_{social.platform.value}'] = f"{social.display_name}: {message}"
# 驗證品牌顏色
if data.brand_color:
is_valid, message = self.validate_hex_color(data.brand_color)
if not is_valid:
errors['brand_color'] = message
if data.secondary_color:
is_valid, message = self.validate_hex_color(data.secondary_color)
if not is_valid:
errors['secondary_color'] = message
return len(errors) == 0, errors
5. HTML簽名生成器
5.1 基礎HTML生成器
創(chuàng)建核心的HTML簽名生成器:
# src/generators/html_generator.py
import os
import json
from typing import Dict, Any, Optional
from jinja2 import Environment, FileSystemLoader, Template
from ..models.signature_data import SignatureData, ThemeStyle
from .image_processor import ImageProcessor
class HTMLSignatureGenerator:
"""HTML電子郵件簽名生成器"""
def __init__(self, templates_dir: str = None):
"""
初始化生成器
參數(shù):
templates_dir: 模板目錄路徑
"""
if templates_dir is None:
# 默認模板目錄
current_dir = os.path.dirname(os.path.abspath(__file__))
templates_dir = os.path.join(current_dir, '..', 'templates', 'html')
self.templates_dir = templates_dir
self.env = Environment(
loader=FileSystemLoader(templates_dir),
trim_blocks=True,
lstrip_blocks=True
)
self.image_processor = ImageProcessor()
# 注冊自定義過濾器
self.env.filters['escape_html'] = self._escape_html
self.env.filters['format_phone'] = self._format_phone
def _escape_html(self, text: str) -> str:
"""轉義HTML特殊字符"""
if not text:
return ""
return (text.replace('&', '&')
.replace('<', '<')
.replace('>', '>')
.replace('"', '"')
.replace("'", '''))
def _format_phone(self, phone: str) -> str:
"""格式化電話號碼顯示"""
if not phone:
return ""
# 移除所有非數(shù)字字符
cleaned = ''.join(filter(str.isdigit, phone))
if len(cleaned) == 10:
return f"({cleaned[:3]}) {cleaned[3:6]}-{cleaned[6:]}"
return phone
def _get_template_context(self, data: SignatureData) -> Dict[str, Any]:
"""構建模板上下文數(shù)據(jù)"""
# 處理品牌顏色
brand_color = data.brand_color or '#2c5aa0'
secondary_color = data.secondary_color or '#666666'
# 處理社交媒體圖標
social_icons = self._prepare_social_media(data.social_media)
# 準備聯(lián)系信息
contact_info = {
'phone': data.contact.get_display_phone(),
'mobile': data.contact.mobile,
'email': data.contact.email,
'address': data.contact.address,
'website': data.contact.website
}
return {
'data': data,
'brand_color': brand_color,
'secondary_color': secondary_color,
'social_icons': social_icons,
'contact_info': contact_info,
'has_logo': bool(data.logo_url),
'has_profile_picture': bool(data.profile_picture_url),
'has_promotion': bool(data.promotional_text),
'has_disclaimer': bool(data.disclaimer)
}
def _prepare_social_media(self, social_media_list) -> list:
"""準備社交媒體數(shù)據(jù)"""
social_data = []
for social in social_media_list:
# 社交媒體平臺對應的圖標類和顏色
platform_config = {
'linkedin': {'icon': 'fab fa-linkedin', 'color': '#0077b5'},
'twitter': {'icon': 'fab fa-twitter', 'color': '#1da1f2'},
'facebook': {'icon': 'fab fa-facebook', 'color': '#1877f2'},
'instagram': {'icon': 'fab fa-instagram', 'color': '#e4405f'},
'github': {'icon': 'fab fa-github', 'color': '#333333'},
'website': {'icon': 'fas fa-globe', 'color': '#666666'},
'youtube': {'icon': 'fab fa-youtube', 'color': '#ff0000'}
}
config = platform_config.get(social.platform.value, {})
social_data.append({
'platform': social.platform.value,
'display_name': social.display_name,
'url': social.url,
'username': social.username,
'icon_class': config.get('icon', 'fas fa-link'),
'color': config.get('color', '#666666')
})
return social_data
def generate_signature(self, data: SignatureData,
template_name: Optional[str] = None) -> str:
"""
生成HTML簽名
參數(shù):
data: 簽名數(shù)據(jù)
template_name: 模板名稱
返回:
HTML簽名字符串
"""
if template_name is None:
template_name = f"{data.theme.value}.html"
# 驗證模板是否存在
template_path = os.path.join(self.templates_dir, template_name)
if not os.path.exists(template_path):
raise FileNotFoundError(f"模板文件不存在: {template_path}")
# 加載模板
template = self.env.get_template(template_name)
# 準備上下文數(shù)據(jù)
context = self._get_template_context(data)
# 渲染模板
html_content = template.render(**context)
# 清理和優(yōu)化HTML
html_content = self._clean_html(html_content)
return html_content
def _clean_html(self, html: str) -> str:
"""清理和優(yōu)化HTML輸出"""
# 移除多余的空格和換行
html = ' '.join(html.split())
# 確保HTML格式正確
html = html.replace('> <', '><') # 移除標簽間的空格
return html
def generate_with_css(self, data: SignatureData,
template_name: Optional[str] = None) -> Dict[str, str]:
"""
生成包含CSS的完整簽名
參數(shù):
data: 簽名數(shù)據(jù)
template_name: 模板名稱
返回:
包含HTML和CSS的字典
"""
html_content = self.generate_signature(data, template_name)
# 加載對應的CSS文件
css_content = self._load_css_template(data.theme)
return {
'html': html_content,
'css': css_content,
'inline_css': self._generate_inline_css(css_content)
}
def _load_css_template(self, theme: ThemeStyle) -> str:
"""加載CSS模板"""
css_filename = f"{theme.value}.css"
css_path = os.path.join(self.templates_dir, '..', 'css', css_filename)
if os.path.exists(css_path):
with open(css_path, 'r', encoding='utf-8') as f:
return f.read()
return ""
def _generate_inline_css(self, css_content: str) -> str:
"""生成內聯(lián)CSS樣式"""
# 這里可以添加CSS內聯(lián)化邏輯
# 簡化版本,直接返回CSS內容
return f"<style>{css_content}</style>"
def save_signature(self, data: SignatureData, output_path: str,
template_name: Optional[str] = None,
include_css: bool = True):
"""
保存簽名到文件
參數(shù):
data: 簽名數(shù)據(jù)
output_path: 輸出文件路徑
template_name: 模板名稱
include_css: 是否包含CSS
"""
if include_css:
result = self.generate_with_css(data, template_name)
content = result['html'] + '\n' + result['inline_css']
else:
content = self.generate_signature(data, template_name)
# 確保輸出目錄存在
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"簽名已保存到: {output_path}")
5.2 圖像處理器
創(chuàng)建圖像處理模塊:
# src/generators/image_processor.py
import os
import requests
from PIL import Image, ImageDraw, ImageFont
import base64
from io import BytesIO
from typing import Optional, Tuple
import qrcode
class ImageProcessor:
"""圖像處理器"""
def __init__(self, cache_dir: str = "image_cache"):
"""
初始化圖像處理器
參數(shù):
cache_dir: 圖像緩存目錄
"""
self.cache_dir = cache_dir
os.makedirs(cache_dir, exist_ok=True)
def download_image(self, url: str, max_size: Tuple[int, int] = (200, 200)) -> Optional[str]:
"""
下載并調整圖像大小
參數(shù):
url: 圖像URL
max_size: 最大尺寸 (寬, 高)
返回:
本地文件路徑或None
"""
try:
# 檢查緩存
filename = self._get_filename_from_url(url)
cache_path = os.path.join(self.cache_dir, filename)
if os.path.exists(cache_path):
return cache_path
# 下載圖像
response = requests.get(url, timeout=10)
response.raise_for_status()
# 打開并調整圖像
image = Image.open(BytesIO(response.content))
image.thumbnail(max_size, Image.Resampling.LANCZOS)
# 保存為PNG格式
image.save(cache_path, 'PNG')
return cache_path
except Exception as e:
print(f"下載圖像失敗: {e}")
return None
def _get_filename_from_url(self, url: str) -> str:
"""從URL生成文件名"""
import hashlib
return hashlib.md5(url.encode()).hexdigest() + '.png'
def image_to_base64(self, image_path: str) -> Optional[str]:
"""
將圖像轉換為Base64編碼
參數(shù):
image_path: 圖像文件路徑
返回:
Base64編碼的字符串或None
"""
try:
with open(image_path, 'rb') as f:
image_data = f.read()
base64_encoded = base64.b64encode(image_data).decode('utf-8')
return f"data:image/png;base64,{base64_encoded}"
except Exception as e:
print(f"圖像Base64編碼失敗: {e}")
return None
def create_qr_code(self, data: str, size: int = 100) -> Optional[str]:
"""
生成QR碼
參數(shù):
data: QR碼數(shù)據(jù)
size: 圖像尺寸
返回:
Base64編碼的QR碼圖像
"""
try:
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(data)
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")
qr_image = qr_image.resize((size, size))
# 轉換為Base64
buffer = BytesIO()
qr_image.save(buffer, format='PNG')
base64_encoded = base64.b64encode(buffer.getvalue()).decode('utf-8')
return f"data:image/png;base64,{base64_encoded}"
except Exception as e:
print(f"生成QR碼失敗: {e}")
return None
def validate_image(self, image_path: str, max_size_mb: int = 2) -> Tuple[bool, str]:
"""
驗證圖像文件
參數(shù):
image_path: 圖像文件路徑
max_size_mb: 最大文件大小(MB)
返回:
(是否有效, 錯誤信息)
"""
try:
# 檢查文件大小
file_size = os.path.getsize(image_path) / (1024 * 1024) # MB
if file_size > max_size_mb:
return False, f"圖像文件過大 ({file_size:.1f}MB > {max_size_mb}MB)"
# 檢查圖像格式
with Image.open(image_path) as img:
if img.format not in ['JPEG', 'PNG', 'GIF']:
return False, f"不支持的圖像格式: {img.format}"
# 檢查圖像尺寸
width, height = img.size
if width > 1000 or height > 1000:
return False, f"圖像尺寸過大: {width}x{height}"
return True, ""
except Exception as e:
return False, f"圖像驗證失敗: {str(e)}"
6. 模板系統(tǒng)設計
6.1 企業(yè)風格模板
創(chuàng)建企業(yè)風格HTML模板:
<!-- src/templates/html/corporate.html -->
<table cellpadding="0" cellspacing="0" border="0" width="600" style="border-collapse: collapse; font-family: Arial, sans-serif; font-size: 12px; line-height: 1.4; color: #333333; border: {% if data.include_border %}1px solid #dddddd{% else %}none{% endif %};">
<tr>
{% if data.logo_url %}
<td width="100" valign="top" style="padding: 15px;">
<img src="{{ data.logo_url }}" alt="{{ data.company }} Logo" width="80" style="display: block; border: none;" />
</td>
{% endif %}
<td valign="top" style="padding: 15px; {% if not data.logo_url %}padding-left: 0;{% endif %}">
<!-- 姓名和職位 -->
<table cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding-bottom: 5px;">
<strong style="font-size: 14px; color: {{ brand_color }};">{{ data.full_name|escape_html }}</strong>
</td>
</tr>
<tr>
<td style="padding-bottom: 8px;">
<span style="font-size: 12px; color: {{ secondary_color }};">{{ data.job_title|escape_html }}</span>
{% if data.department %}
<span style="color: #999999;"> | </span>
<span style="font-size: 12px; color: #999999;">{{ data.department|escape_html }}</span>
{% endif %}
</td>
</tr>
</table>
<!-- 公司信息 -->
<table cellpadding="0" cellspacing="0" border="0" style="margin-bottom: 10px;">
<tr>
<td style="font-size: 12px; color: {{ brand_color }}; font-weight: bold;">
{{ data.company|escape_html }}
</td>
</tr>
</table>
<!-- 聯(lián)系信息 -->
<table cellpadding="0" cellspacing="0" border="0" style="margin-bottom: 10px;">
{% if contact_info.phone %}
<tr>
<td width="20" valign="top">??</td>
<td style="padding-bottom: 2px;">
<span style="font-size: 11px;">電話: {{ contact_info.phone|format_phone }}</span>
</td>
</tr>
{% endif %}
{% if contact_info.mobile %}
<tr>
<td width="20" valign="top">??</td>
<td style="padding-bottom: 2px;">
<span style="font-size: 11px;">手機: {{ contact_info.mobile|format_phone }}</span>
</td>
</tr>
{% endif %}
{% if contact_info.email %}
<tr>
<td width="20" valign="top">??</td>
<td style="padding-bottom: 2px;">
<a href="mailto:{{ contact_info.email }}" rel="external nofollow" rel="external nofollow" style="font-size: 11px; color: {{ brand_color }}; text-decoration: none;">
{{ contact_info.email|escape_html }}
</a>
</td>
</tr>
{% endif %}
{% if contact_info.website %}
<tr>
<td width="20" valign="top">??</td>
<td style="padding-bottom: 2px;">
<a href="{{ contact_info.website }}" rel="external nofollow" rel="external nofollow" style="font-size: 11px; color: {{ brand_color }}; text-decoration: none;">
{{ contact_info.website|escape_html }}
</a>
</td>
</tr>
{% endif %}
</table>
<!-- 社交媒體 -->
{% if social_icons %}
<table cellpadding="0" cellspacing="0" border="0" style="margin-bottom: 10px;">
<tr>
<td style="padding-bottom: 5px;">
<span style="font-size: 11px; color: #999999;">關注我們:</span>
</td>
</tr>
<tr>
<td>
{% for social in social_icons %}
<a href="{{ social.url }}" rel="external nofollow" rel="external nofollow" style="text-decoration: none; margin-right: 8px; display: inline-block;">
<span style="color: {{ social.color }}; font-size: 14px;">[{{ social.display_name }}]</span>
</a>
{% endfor %}
</td>
</tr>
</table>
{% endif %}
<!-- 推廣信息 -->
{% if data.promotional_text %}
<table cellpadding="0" cellspacing="0" border="0" style="margin-bottom: 10px; background-color: #f8f9fa; padding: 8px; border-left: 3px solid {{ brand_color }};">
<tr>
<td>
<span style="font-size: 11px; color: #666666; font-style: italic;">
{{ data.promotional_text|escape_html }}
</span>
</td>
</tr>
</table>
{% endif %}
<!-- 免責聲明 -->
{% if data.disclaimer %}
<table cellpadding="0" cellspacing="0" border="0">
<tr>
<td>
<span style="font-size: 9px; color: #999999; line-height: 1.2;">
{{ data.disclaimer|escape_html }}
</span>
</td>
</tr>
</table>
{% endif %}
</td>
<!-- QR碼 -->
{% if data.include_qr_code and data.qr_code_url %}
<td width="80" valign="middle" align="center" style="padding: 15px;">
<img src="{{ data.qr_code_url }}" alt="QR Code" width="60" style="display: block; border: none;" />
<span style="font-size: 9px; color: #999999;">掃描聯(lián)系我</span>
</td>
{% endif %}
</tr>
</table>
6.2 現(xiàn)代風格模板
創(chuàng)建現(xiàn)代風格HTML模板:
<!-- src/templates/html/modern.html -->
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="max-width: 550px; border-collapse: collapse; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 13px; line-height: 1.5; color: #444444; background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); border-radius: 8px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
<tr>
<!-- 左側品牌區(qū)域 -->
<td width="30%" valign="top" style="background: {{ brand_color }}; padding: 20px; color: white;">
{% if data.logo_url %}
<div style="margin-bottom: 15px;">
<img src="{{ data.logo_url }}" alt="{{ data.company }} Logo" width="60" style="display: block; border: none; background: white; padding: 5px; border-radius: 4px;" />
</div>
{% endif %}
<div style="font-size: 16px; font-weight: bold; margin-bottom: 5px;">
{{ data.full_name|escape_html }}
</div>
<div style="font-size: 12px; opacity: 0.9;">
{{ data.job_title|escape_html }}
</div>
{% if data.department %}
<div style="font-size: 11px; opacity: 0.8; margin-top: 3px;">
{{ data.department|escape_html }}
</div>
{% endif %}
</td>
<!-- 右側內容區(qū)域 -->
<td valign="top" style="padding: 20px;">
<!-- 公司名稱 -->
<div style="font-size: 14px; font-weight: bold; color: {{ brand_color }}; margin-bottom: 15px;">
{{ data.company|escape_html }}
</div>
<!-- 聯(lián)系信息 -->
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-bottom: 15px;">
{% if contact_info.phone %}
<tr>
<td width="20" valign="top" style="padding-bottom: 6px;">
<span style="color: {{ brand_color }};">●</span>
</td>
<td style="padding-bottom: 6px;">
<span style="font-size: 12px;">{{ contact_info.phone|format_phone }}</span>
</td>
</tr>
{% endif %}
{% if contact_info.email %}
<tr>
<td width="20" valign="top" style="padding-bottom: 6px;">
<span style="color: {{ brand_color }};">●</span>
</td>
<td style="padding-bottom: 6px;">
<a href="mailto:{{ contact_info.email }}" rel="external nofollow" rel="external nofollow" style="font-size: 12px; color: {{ brand_color }}; text-decoration: none;">
{{ contact_info.email|escape_html }}
</a>
</td>
</tr>
{% endif %}
{% if contact_info.website %}
<tr>
<td width="20" valign="top" style="padding-bottom: 6px;">
<span style="color: {{ brand_color }};">●</span>
</td>
<td style="padding-bottom: 6px;">
<a href="{{ contact_info.website }}" rel="external nofollow" rel="external nofollow" style="font-size: 12px; color: {{ brand_color }}; text-decoration: none;">
{{ contact_info.website|escape_html }}
</a>
</td>
</tr>
{% endif %}
</table>
<!-- 社交媒體 -->
{% if social_icons %}
<div style="margin-bottom: 15px;">
<table cellpadding="0" cellspacing="0" border="0">
<tr>
{% for social in social_icons %}
<td style="padding-right: 8px;">
<a href="{{ social.url }}" rel="external nofollow" rel="external nofollow" style="display: inline-block; width: 24px; height: 24px; background-color: {{ social.color }}; border-radius: 50%; text-align: center; line-height: 24px; text-decoration: none; color: white; font-size: 12px;">
{{ social.display_name|first|upper }}
</a>
</td>
{% endfor %}
</tr>
</table>
</div>
{% endif %}
<!-- 行動號召 -->
{% if data.call_to_action %}
<div style="background-color: {{ brand_color }}; color: white; padding: 8px 12px; border-radius: 4px; text-align: center; margin-bottom: 10px;">
<span style="font-size: 11px; font-weight: bold;">
{{ data.call_to_action|escape_html }}
</span>
</div>
{% endif %}
</td>
</tr>
<!-- 底部區(qū)域 -->
{% if data.promotional_text or data.disclaimer %}
<tr>
<td colspan="2" style="background-color: #f8f9fa; padding: 15px 20px; border-top: 1px solid #e9ecef;">
{% if data.promotional_text %}
<div style="font-size: 11px; color: #666666; margin-bottom: 8px;">
? {{ data.promotional_text|escape_html }}
</div>
{% endif %}
{% if data.disclaimer %}
<div style="font-size: 9px; color: #999999; line-height: 1.3;">
{{ data.disclaimer|escape_html }}
</div>
{% endif %}
</td>
</tr>
{% endif %}
</table>
6.3 CSS樣式文件
創(chuàng)建對應的CSS樣式文件:
/* src/templates/css/corporate.css */
.corporate-signature {
font-family: Arial, sans-serif;
font-size: 12px;
line-height: 1.4;
color: #333333;
border-collapse: collapse;
}
.corporate-signature a {
color: #2c5aa0;
text-decoration: none;
}
.corporate-signature a:hover {
text-decoration: underline;
}
.corporate-signature .brand-color {
color: #2c5aa0;
}
.corporate-signature .secondary-color {
color: #666666;
}
.corporate-signature .promotional-box {
background-color: #f8f9fa;
padding: 8px;
border-left: 3px solid #2c5aa0;
font-style: italic;
}
.corporate-signature .disclaimer {
font-size: 9px;
color: #999999;
line-height: 1.2;
}
/* src/templates/css/modern.css */
.modern-signature {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 13px;
line-height: 1.5;
color: #444444;
border-collapse: collapse;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.modern-signature a {
text-decoration: none;
}
.modern-signature .brand-section {
background: linear-gradient(135deg, #2c5aa0 0%, #1e3a8a 100%);
color: white;
}
.modern-signature .social-icon {
display: inline-block;
width: 24px;
height: 24px;
border-radius: 50%;
text-align: center;
line-height: 24px;
color: white;
font-size: 12px;
}
.modern-signature .cta-button {
background-color: #2c5aa0;
color: white;
padding: 8px 12px;
border-radius: 4px;
text-align: center;
font-weight: bold;
}
.modern-signature .footer-section {
background-color: #f8f9fa;
border-top: 1px solid #e9ecef;
}
7. 純文本簽名生成器
創(chuàng)建純文本簽名生成器,確保在不支持HTML的客戶端中正常顯示:
# src/generators/text_generator.py
from ..models.signature_data import SignatureData
from typing import List
class TextSignatureGenerator:
"""純文本電子郵件簽名生成器"""
def __init__(self, line_width: int = 70):
"""
初始化生成器
參數(shù):
line_width: 行寬(字符數(shù))
"""
self.line_width = line_width
def generate_signature(self, data: SignatureData) -> str:
"""
生成純文本簽名
參數(shù):
data: 簽名數(shù)據(jù)
返回:
純文本簽名字符串
"""
lines = []
# 分隔線
lines.append("=" * self.line_width)
# 姓名和職位
name_line = data.full_name
if data.job_title:
name_line += f" | {data.job_title}"
lines.append(name_line)
# 公司和部門
company_line = data.company
if data.department:
company_line += f" | {data.department}"
lines.append(company_line)
lines.append("") # 空行
# 聯(lián)系信息
if data.contact.phone:
lines.append(f"電話: {data.contact.get_display_phone()}")
if data.contact.mobile:
lines.append(f"手機: {data.contact.mobile}")
if data.contact.email:
lines.append(f"郵箱: {data.contact.email}")
if data.contact.website:
lines.append(f"網站: {data.contact.website}")
if data.contact.address:
# 地址可能較長,需要換行處理
address_lines = self._wrap_text(f"地址: {data.contact.address}", self.line_width)
lines.extend(address_lines)
# 社交媒體
if data.social_media:
lines.append("") # 空行
lines.append("關注我:")
for social in data.social_media:
lines.append(f" {social.display_name}: {social.url}")
# 推廣信息
if data.promotional_text:
lines.append("") # 空行
promo_lines = self._wrap_text(f"? {data.promotional_text}", self.line_width)
lines.extend(promo_lines)
# 行動號召
if data.call_to_action:
lines.append("") # 空行
cta_lines = self._wrap_text(f"?? {data.call_to_action}", self.line_width)
lines.extend(cta_lines)
# 免責聲明
if data.disclaimer:
lines.append("") # 空行
disclaimer_lines = self._wrap_text(data.disclaimer, self.line_width)
lines.extend(disclaimer_lines)
# 結束分隔線
lines.append("=" * self.line_width)
return "\n".join(lines)
def _wrap_text(self, text: str, width: int) -> List[str]:
"""
文本換行處理
參數(shù):
text: 原始文本
width: 行寬
返回:
換行后的文本列表
"""
words = text.split()
lines = []
current_line = []
current_length = 0
for word in words:
# 計算添加這個詞后的行長度
word_length = len(word)
if current_line:
# 加上空格的長度
word_length += 1
if current_length + word_length <= width:
current_line.append(word)
current_length += word_length
else:
# 開始新行
if current_line:
lines.append(' '.join(current_line))
current_line = [word]
current_length = len(word)
# 添加最后一行
if current_line:
lines.append(' '.join(current_line))
return lines
def save_signature(self, data: SignatureData, output_path: str):
"""
保存純文本簽名到文件
參數(shù):
data: 簽名數(shù)據(jù)
output_path: 輸出文件路徑
"""
text_content = self.generate_signature(data)
# 確保輸出目錄存在
import os
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, 'w', encoding='utf-8') as f:
f.write(text_content)
print(f"純文本簽名已保存到: {output_path}")
8. 命令行界面
創(chuàng)建用戶友好的命令行界面:
# src/cli.py
import click
import json
import os
from typing import Dict, Any
from .models.signature_data import SignatureData, ThemeStyle, SocialPlatform, ContactInfo
from .models.validators import SignatureValidator
from .generators.html_generator import HTMLSignatureGenerator
from .generators.text_generator import TextSignatureGenerator
@click.group()
def cli():
"""個性化電子郵件簽名生成工具"""
pass
@cli.command()
@click.option('--name', prompt='姓名', help='您的全名')
@click.option('--title', prompt='職位', help='您的職位名稱')
@click.option('--company', prompt='公司', help='公司名稱')
@click.option('--department', help='部門名稱')
@click.option('--email', prompt='電子郵件', help='電子郵件地址')
@click.option('--phone', help='電話號碼')
@click.option('--website', help='個人或公司網站')
@click.option('--theme', type=click.Choice(['corporate', 'modern', 'minimal']),
default='modern', help='簽名主題風格')
@click.option('--output', '-o', default='signature.html', help='輸出文件路徑')
@click.option('--format', '-f', type=click.Choice(['html', 'text', 'both']),
default='html', help='輸出格式')
def create(name, title, company, department, email, phone, website, theme, output, format):
"""創(chuàng)建新的電子郵件簽名"""
# 創(chuàng)建聯(lián)系信息
contact = ContactInfo(
email=email,
phone=phone,
website=website
)
# 創(chuàng)建簽名數(shù)據(jù)
signature_data = SignatureData(
full_name=name,
job_title=title,
company=company,
department=department,
contact=contact,
theme=ThemeStyle(theme)
)
# 驗證數(shù)據(jù)
validator = SignatureValidator()
is_valid, errors = validator.validate_signature_data(signature_data)
if not is_valid:
click.echo("數(shù)據(jù)驗證失敗:")
for field, error in errors.items():
click.echo(f" {field}: {error}")
return
# 生成簽名
if format in ['html', 'both']:
html_generator = HTMLSignatureGenerator()
html_output = output if format == 'html' else output.replace('.html', '_html.html')
html_generator.save_signature(signature_data, html_output)
if format in ['text', 'both']:
text_generator = TextSignatureGenerator()
text_output = output if format == 'text' else output.replace('.html', '_text.txt')
text_generator.save_signature(signature_data, text_output)
click.echo("簽名創(chuàng)建成功!")
@cli.command()
@click.argument('config_file', type=click.File('r'))
@click.option('--output', '-o', required=True, help='輸出文件路徑')
@click.option('--format', '-f', type=click.Choice(['html', 'text', 'both']),
default='html', help='輸出格式')
def from_config(config_file, output, format):
"""從配置文件創(chuàng)建簽名"""
try:
config_data = json.load(config_file)
signature_data = SignatureData.from_dict(config_data)
# 驗證數(shù)據(jù)
validator = SignatureValidator()
is_valid, errors = validator.validate_signature_data(signature_data)
if not is_valid:
click.echo("配置數(shù)據(jù)驗證失敗:")
for field, error in errors.items():
click.echo(f" {field}: {error}")
return
# 生成簽名
if format in ['html', 'both']:
html_generator = HTMLSignatureGenerator()
html_output = output if format == 'html' else output.replace('.html', '_html.html')
html_generator.save_signature(signature_data, html_output)
if format in ['text', 'both']:
text_generator = TextSignatureGenerator()
text_output = output if format == 'text' else output.replace('.html', '_text.txt')
text_generator.save_signature(signature_data, text_output)
click.echo("簽名創(chuàng)建成功!")
except json.JSONDecodeError:
click.echo("配置文件格式錯誤,請檢查JSON格式")
except Exception as e:
click.echo(f"處理配置文件時出錯: {e}")
@cli.command()
@click.option('--name', help='姓名')
@click.option('--title', help='職位')
@click.option('--company', help='公司')
@click.option('--output', default='signature_config.json', help='輸出配置文件路徑')
def create_config(name, title, company, output):
"""創(chuàng)建簽名配置文件模板"""
config_template = {
"full_name": name or "張三",
"job_title": title or "軟件工程師",
"company": company or "示例公司",
"department": "技術部",
"contact": {
"phone": "+86-10-12345678",
"mobile": "+86-138-0000-0000",
"email": "zhangsan@example.com",
"address": "北京市朝陽區(qū)示例街道123號",
"website": "https://www.example.com"
},
"social_media": [
{
"platform": "linkedin",
"url": "https://linkedin.com/in/zhangsan",
"username": "zhangsan"
},
{
"platform": "github",
"url": "https://github.com/zhangsan",
"username": "zhangsan"
}
],
"theme": "modern",
"brand_color": "#2c5aa0",
"secondary_color": "#666666",
"promotional_text": "歡迎了解我們的最新產品和服務!",
"call_to_action": "立即預約演示",
"disclaimer": "本郵件及其附件包含保密信息,僅限指定收件人使用。"
}
with open(output, 'w', encoding='utf-8') as f:
json.dump(config_template, f, ensure_ascii=False, indent=2)
click.echo(f"配置文件模板已創(chuàng)建: {output}")
click.echo("請編輯此文件后使用 'from-config' 命令生成簽名")
@cli.command()
@click.argument('input_file')
@click.option('--theme', type=click.Choice(['corporate', 'modern', 'minimal']),
help='主題風格')
@click.option('--output', '-o', help='輸出文件路徑')
def preview(input_file, theme, output):
"""預覽簽名效果"""
try:
with open(input_file, 'r', encoding='utf-8') as f:
if input_file.endswith('.json'):
config_data = json.load(f)
signature_data = SignatureData.from_dict(config_data)
else:
# 這里可以添加其他格式的支持
raise click.ClickException("不支持的輸入文件格式")
if theme:
signature_data.theme = ThemeStyle(theme)
# 生成HTML預覽
html_generator = HTMLSignatureGenerator()
html_content = html_generator.generate_signature(signature_data)
if output:
html_generator.save_signature(signature_data, output)
click.echo(f"預覽文件已保存: {output}")
else:
# 在控制臺顯示HTML代碼
click.echo("生成的HTML簽名:")
click.echo("=" * 80)
click.echo(html_content)
click.echo("=" * 80)
click.echo("請將上述代碼復制到您的電子郵件客戶端中使用")
except Exception as e:
click.echo(f"預覽失敗: {e}")
if __name__ == '__main__':
cli()
9. 完整示例和使用方法
9.1 基本使用示例
創(chuàng)建使用示例文件:
# examples/basic_usage.py
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from src.models.signature_data import SignatureData, ContactInfo, SocialPlatform, ThemeStyle
from src.generators.html_generator import HTMLSignatureGenerator
from src.generators.text_generator import TextSignatureGenerator
def create_basic_signature():
"""創(chuàng)建基礎簽名示例"""
# 創(chuàng)建聯(lián)系信息
contact = ContactInfo(
phone="+86-10-12345678",
mobile="+86-138-0000-0000",
email="zhangsan@example.com",
website="https://www.example.com",
address="北京市朝陽區(qū)示例街道123號"
)
# 創(chuàng)建簽名數(shù)據(jù)
signature_data = SignatureData(
full_name="張三",
job_title="高級軟件工程師",
company="創(chuàng)新科技有限公司",
department="技術研發(fā)部",
contact=contact,
brand_color="#2c5aa0",
secondary_color="#666666",
theme=ThemeStyle.MODERN,
promotional_text="專注于人工智能和云計算解決方案",
call_to_action="查看我們的最新產品",
disclaimer="本郵件及其附件包含保密信息,僅限指定收件人使用。"
)
# 添加社交媒體
signature_data.add_social_media(
SocialPlatform.LINKEDIN,
"https://linkedin.com/in/zhangsan",
"zhangsan"
)
signature_data.add_social_media(
SocialPlatform.GITHUB,
"https://github.com/zhangsan",
"zhangsan"
)
signature_data.add_social_media(
SocialPlatform.WEBSITE,
"https://www.zhangsan.com"
)
return signature_data
def generate_all_formats():
"""生成所有格式的簽名"""
signature_data = create_basic_signature()
# 生成HTML簽名
html_generator = HTMLSignatureGenerator()
html_signature = html_generator.generate_signature(signature_data)
# 生成純文本簽名
text_generator = TextSignatureGenerator()
text_signature = text_generator.generate_signature(signature_data)
# 保存文件
with open('examples/output/signature.html', 'w', encoding='utf-8') as f:
f.write(html_signature)
with open('examples/output/signature.txt', 'w', encoding='utf-8') as f:
f.write(text_signature)
print("HTML簽名已保存: examples/output/signature.html")
print("純文本簽名已保存: examples/output/signature.txt")
return html_signature, text_signature
if __name__ == '__main__':
# 確保輸出目錄存在
os.makedirs('examples/output', exist_ok=True)
html, text = generate_all_formats()
print("\n生成的HTML簽名預覽:")
print("=" * 50)
print(html[:500] + "..." if len(html) > 500 else html)
print("\n生成的純文本簽名:")
print("=" * 50)
print(text)
9.2 配置文件示例
創(chuàng)建配置文件示例:
{
"full_name": "李四",
"job_title": "產品經理",
"company": "數(shù)字創(chuàng)新有限公司",
"department": "產品部",
"contact": {
"phone": "+86-21-87654321",
"mobile": "+86-139-1111-2222",
"email": "lisi@digital-innovations.com",
"address": "上海市浦東新區(qū)創(chuàng)新路456號",
"website": "https://www.digital-innovations.com"
},
"social_media": [
{
"platform": "linkedin",
"url": "https://linkedin.com/in/lisi",
"username": "lisi"
},
{
"platform": "twitter",
"url": "https://twitter.com/lisi",
"username": "lisi"
},
{
"platform": "website",
"url": "https://www.lisi.blog"
}
],
"theme": "corporate",
"brand_color": "#d35400",
"secondary_color": "#7f8c8d",
"promotional_text": "我們致力于打造用戶體驗卓越的數(shù)字產品",
"call_to_action": "立即體驗我們的產品演示",
"disclaimer": "本郵件內容僅供參考,不構成任何承諾或保證。",
"include_border": true,
"include_qr_code": true,
"qr_code_url": "https://www.digital-innovations.com/contact"
}
10. 測試和驗證
單元測試
創(chuàng)建基礎測試用例:
# tests/test_signature_generator.py
import unittest
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from src.models.signature_data import SignatureData, ContactInfo, SocialPlatform
from src.models.validators import SignatureValidator
from src.generators.html_generator import HTMLSignatureGenerator
from src.generators.text_generator import TextSignatureGenerator
class TestSignatureGenerator(unittest.TestCase):
def setUp(self):
"""測試前置設置"""
self.contact = ContactInfo(
phone="+86-10-12345678",
email="test@example.com",
website="https://www.example.com"
)
self.signature_data = SignatureData(
full_name="測試用戶",
job_title="測試工程師",
company="測試公司",
contact=self.contact
)
self.validator = SignatureValidator()
self.html_generator = HTMLSignatureGenerator()
self.text_generator = TextSignatureGenerator()
def test_valid_signature_data(self):
"""測試有效簽名數(shù)據(jù)驗證"""
is_valid, errors = self.validator.validate_signature_data(self.signature_data)
self.assertTrue(is_valid)
self.assertEqual(len(errors), 0)
def test_invalid_email(self):
"""測試無效電子郵件驗證"""
self.signature_data.contact.email = "invalid-email"
is_valid, errors = self.validator.validate_signature_data(self.signature_data)
self.assertFalse(is_valid)
self.assertIn('email', errors)
def test_html_generation(self):
"""測試HTML生成"""
html_content = self.html_generator.generate_signature(self.signature_data)
self.assertIsInstance(html_content, str)
self.assertGreater(len(html_content), 0)
self.assertIn('測試用戶', html_content)
self.assertIn('測試公司', html_content)
def test_text_generation(self):
"""測試純文本生成"""
text_content = self.text_generator.generate_signature(self.signature_data)
self.assertIsInstance(text_content, str)
self.assertGreater(len(text_content), 0)
self.assertIn('測試用戶', text_content)
self.assertIn('測試公司', text_content)
def test_social_media_addition(self):
"""測試社交媒體添加"""
initial_count = len(self.signature_data.social_media)
self.signature_data.add_social_media(SocialPlatform.LINKEDIN, "https://linkedin.com/in/test")
self.assertEqual(len(self.signature_data.social_media), initial_count + 1)
def test_phone_formatting(self):
"""測試電話號碼格式化"""
formatted = self.signature_data.contact.get_display_phone()
self.assertIsNotNone(formatted)
# 檢查是否包含格式化的電話號碼元素
if __name__ == '__main__':
unittest.main()
11. 部署和使用說明
11.1 安裝和使用
安裝依賴:
pip install -r requirements.txt
基本使用:
# 交互式創(chuàng)建簽名 python -m src.cli create # 使用配置文件創(chuàng)建簽名 python -m src.cli from-config config.json --output signature.html # 創(chuàng)建配置文件模板 python -m src.cli create-config --output my_config.json # 預覽簽名 python -m src.cli preview my_config.json
在Python代碼中使用:
from src.models.signature_data import SignatureData, ContactInfo
from src.generators.html_generator import HTMLSignatureGenerator
# 創(chuàng)建簽名數(shù)據(jù)
contact = ContactInfo(email="user@example.com", phone="+1234567890")
data = SignatureData("張三", "工程師", "科技公司", contact=contact)
# 生成HTML簽名
generator = HTMLSignatureGenerator()
signature = generator.generate_signature(data)
11.2 最佳實踐
圖像優(yōu)化:
- 使用小于200KB的圖像文件
- 推薦使用PNG格式以獲得更好的透明度支持
- 確保圖像尺寸適當(建議100-200像素寬度)
顏色選擇:
- 使用品牌顏色保持一致
- 確保足夠的顏色對比度以便閱讀
- 考慮色盲用戶的體驗
響應式設計:
- 測試在不同郵件客戶端中的顯示效果
- 使用表格布局確保兼容性
- 提供純文本備用版本
可訪問性:
- 為圖像提供alt文本
- 使用語義化的HTML結構
- 確保鍵盤導航友好
12. 總結
本文詳細介紹了一個完整的個性化電子郵件簽名生成系統(tǒng)的設計和實現(xiàn)。通過這個系統(tǒng),用戶可以:
- 快速創(chuàng)建專業(yè)簽名:通過簡單的命令行界面或配置文件快速生成簽名
- 多格式支持:同時生成HTML和純文本格式,確保兼容性
- 高度可定制:支持多種主題風格、顏色配置和布局選項
- 數(shù)據(jù)驗證:確保輸入數(shù)據(jù)的有效性和一致性
- 社交媒體集成:輕松添加和管理社交媒體鏈接
這個系統(tǒng)的核心優(yōu)勢在于其靈活性和易用性。無論是個人用戶還是企業(yè)管理員,都可以通過這個工具快速創(chuàng)建和維護專業(yè)的電子郵件簽名,提升溝通的專業(yè)性和效率。
通過模塊化的設計和良好的代碼結構,這個系統(tǒng)還具有良好的可擴展性,可以輕松添加新的模板主題、輸出格式或集成其他功能。
以上就是使用Python生成個性化的電子郵件簽名的詳細內容,更多關于Python生成電子郵件簽名的資料請關注腳本之家其它相關文章!
相關文章
關于Python?中IndexError:list?assignment?index?out?of?rang
這篇文章主要介紹了Python?中IndexError:list?assignment?index?out?of?range?錯誤解決,概述了兩個常見的列表函數(shù),它們可以幫助我們在替換兩個列表時幫助我們處理?Python?中的索引錯誤,需要的朋友可以參考下2023-05-05
Python3實現(xiàn)的字典、列表和json對象互轉功能示例
這篇文章主要介紹了Python3實現(xiàn)的字典、列表和json對象互轉功能,結合實例形式分析了Python使用json模塊針對json格式數(shù)據(jù)編碼轉換相關操作技巧,需要的朋友可以參考下2018-05-05
python列表推導式實現(xiàn)找出列表中長度大于5的名字
這篇文章主要介紹了python列表推導式實現(xiàn)找出列表中長度大于5的名字,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
基于Python+ECharts實現(xiàn)實時數(shù)據(jù)大屏
文章介紹了如何使用Python爬蟲和ECharts構建實時數(shù)據(jù)大屏,通過自動化數(shù)據(jù)采集和動態(tài)數(shù)據(jù)展示,提高決策效率,關鍵步驟包括爬蟲開發(fā)、ECharts可視化和系統(tǒng)集成,同時提供了常見問題的解決方案和進階方向,需要的朋友可以參考下2026-01-01

