使用VSCode开发少儿编程项目:自动化邮件发送器 - 生日祝福邮件项目
项目摘要:自动化生日祝福邮件发送器 这是一个使用Python开发的自动化邮件发送系统,专为家庭生日祝福设计。项目包含以下核心功能: 联系人管理:可添加、编辑和删除家人信息,包括姓名、邮箱、生日和关系备注 邮件模板:提供多种HTML格式的生日祝福模板(基础版、幽默版和深情版) 定时发送:支持设置每日检查时间和自动发送功能 配置管理:支持多邮箱服务商配置(QQ、163、Gmail等) 数据持久化:使用
·
使用VSCode开发少儿编程项目:自动化邮件发送器 - 生日祝福邮件项目
我将为您创建一个自动化邮件发送器,用于自动给家人发送生日祝福邮件。这个项目适合在VSCode中使用Python开发,包含邮件模板、联系人管理和定时发送功能。
项目概述
这个自动化邮件发送器将包含以下核心功能:
- 联系人管理(添加、编辑、删除家人信息)
- 生日提醒和自动检测
- 多种邮件模板选择
- 支持HTML格式的生日祝福邮件
- 定时发送和立即发送功能
- 发送历史记录
- 简单的图形界面
项目结构
email-sender/
│
├── main.py
├── email_manager.py
├── contact_manager.py
├── template_manager.py
├── config.py
├── requirements.txt
├── templates/
│ ├── birthday_basic.html
│ ├── birthday_funny.html
│ └── birthday_heartfelt.html
└── contacts.json
代码实现
1. requirements.txt
smtplib
email
ssl
schedule
tkinter
json
datetime
2. config.py - 配置文件
"""
邮件发送器配置
请根据您的邮箱服务商修改以下配置
"""
# 邮箱服务器配置
EMAIL_CONFIG = {
'smtp_server': 'smtp.qq.com', # QQ邮箱SMTP服务器
'smtp_port': 587, # QQ邮箱TLS端口
# 'smtp_server': 'smtp.163.com', # 163邮箱SMTP服务器
# 'smtp_port': 25, # 163邮箱端口
# 'smtp_server': 'smtp.gmail.com', # Gmail SMTP服务器
# 'smtp_port': 587, # Gmail TLS端口
}
# 发件人邮箱配置(需要填写您的真实邮箱和授权码)
SENDER_CONFIG = {
'email': 'your_email@qq.com', # 发件人邮箱
'password': 'your_authorization_code', # 邮箱授权码(不是登录密码)
'name': '家庭生日祝福系统' # 发件人名称
}
# 应用配置
APP_CONFIG = {
'check_interval': 1, # 检查间隔(小时)
'send_time': '08:00', # 每日发送时间
'backup_enabled': True, # 启用备份
'language': 'zh-CN' # 语言设置
}
def validate_config():
"""验证配置是否已填写"""
if SENDER_CONFIG['email'] == 'your_email@qq.com':
return False, "请先配置发件人邮箱信息"
if SENDER_CONFIG['password'] == 'your_authorization_code':
return False, "请先配置邮箱授权码"
return True, "配置验证通过"
3. contact_manager.py - 联系人管理
import json
import os
from datetime import datetime, timedelta
class ContactManager:
def __init__(self, contacts_file='contacts.json'):
self.contacts_file = contacts_file
self.contacts = self._load_contacts()
def _load_contacts(self):
"""加载联系人数据"""
try:
if os.path.exists(self.contacts_file):
with open(self.contacts_file, 'r', encoding='utf-8') as f:
return json.load(f)
else:
# 创建示例数据
sample_contacts = [
{
"id": 1,
"name": "爸爸",
"email": "father@example.com",
"birthday": "01-15",
"relationship": "父亲",
"notes": "喜欢钓鱼和读书",
"created_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
},
{
"id": 2,
"name": "妈妈",
"email": "mother@example.com",
"birthday": "03-22",
"relationship": "母亲",
"notes": "热爱园艺和烹饪",
"created_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
]
self._save_contacts(sample_contacts)
return sample_contacts
except Exception as e:
print(f"加载联系人失败: {e}")
return []
def _save_contacts(self, contacts=None):
"""保存联系人数据"""
if contacts is None:
contacts = self.contacts
try:
with open(self.contacts_file, 'w', encoding='utf-8') as f:
json.dump(contacts, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"保存联系人失败: {e}")
return False
def get_next_id(self):
"""获取下一个可用的ID"""
if not self.contacts:
return 1
return max(contact['id'] for contact in self.contacts) + 1
def add_contact(self, name, email, birthday, relationship, notes=""):
"""添加联系人"""
try:
# 验证生日格式
datetime.strptime(birthday, '%m-%d')
new_contact = {
"id": self.get_next_id(),
"name": name,
"email": email,
"birthday": birthday,
"relationship": relationship,
"notes": notes,
"created_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
self.contacts.append(new_contact)
return self._save_contacts()
except ValueError:
raise ValueError("生日格式错误,请使用 MM-DD 格式(如:01-15)")
except Exception as e:
raise Exception(f"添加联系人失败: {e}")
def update_contact(self, contact_id, **kwargs):
"""更新联系人信息"""
try:
for contact in self.contacts:
if contact['id'] == contact_id:
# 验证生日格式
if 'birthday' in kwargs:
datetime.strptime(kwargs['birthday'], '%m-%d')
contact.update(kwargs)
contact['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
return self._save_contacts()
raise ValueError(f"未找到ID为 {contact_id} 的联系人")
except ValueError as e:
if "birthday" in str(e):
raise ValueError("生日格式错误,请使用 MM-DD 格式(如:01-15)")
raise e
except Exception as e:
raise Exception(f"更新联系人失败: {e}")
def delete_contact(self, contact_id):
"""删除联系人"""
try:
self.contacts = [c for c in self.contacts if c['id'] != contact_id]
return self._save_contacts()
except Exception as e:
raise Exception(f"删除联系人失败: {e}")
def get_contact(self, contact_id):
"""获取指定联系人"""
for contact in self.contacts:
if contact['id'] == contact_id:
return contact
return None
def get_all_contacts(self):
"""获取所有联系人"""
return self.contacts
def get_today_birthdays(self):
"""获取今天过生日的联系人"""
today = datetime.now().strftime('%m-%d')
return [contact for contact in self.contacts if contact['birthday'] == today]
def get_upcoming_birthdays(self, days=7):
"""获取未来几天内过生日的联系人"""
upcoming = []
today = datetime.now()
for contact in self.contacts:
# 解析生日日期(使用当前年份)
birthday_str = f"{datetime.now().year}-{contact['birthday']}"
try:
birthday = datetime.strptime(birthday_str, '%Y-%m-%d')
# 如果生日已经过去,计算下一年的生日
if birthday < today:
birthday = datetime.strptime(f"{datetime.now().year + 1}-{contact['birthday']}", '%Y-%m-%d')
# 计算距离生日的天数
days_until = (birthday - today).days
if 0 <= days_until <= days:
contact_copy = contact.copy()
contact_copy['days_until'] = days_until
contact_copy['birthday_date'] = birthday.strftime('%Y年%m月%d日')
upcoming.append(contact_copy)
except ValueError:
continue # 跳过无效的生日格式
# 按距离生日的天数排序
upcoming.sort(key=lambda x: x['days_until'])
return upcoming
def search_contacts(self, keyword):
"""搜索联系人"""
keyword = keyword.lower()
results = []
for contact in self.contacts:
if (keyword in contact['name'].lower() or
keyword in contact['relationship'].lower() or
keyword in contact['email'].lower() or
keyword in contact.get('notes', '').lower()):
results.append(contact)
return results
# 测试联系人管理功能
if __name__ == "__main__":
manager = ContactManager()
print("所有联系人:")
for contact in manager.get_all_contacts():
print(f"- {contact['name']} ({contact['relationship']}): {contact['birthday']}")
print("\n今天过生日的联系人:")
today_birthdays = manager.get_today_birthdays()
for contact in today_birthdays:
print(f"- {contact['name']}")
print("\n未来7天内过生日的联系人:")
upcoming = manager.get_upcoming_birthdays(7)
for contact in upcoming:
print(f"- {contact['name']}: 还有{contact['days_until']}天")
4. template_manager.py - 邮件模板管理
import os
import json
from datetime import datetime
class TemplateManager:
def __init__(self, templates_dir='templates'):
self.templates_dir = templates_dir
self.templates_file = os.path.join(templates_dir, 'templates.json')
self._ensure_templates_dir()
self.templates = self._load_templates()
def _ensure_templates_dir(self):
"""确保模板目录存在"""
if not os.path.exists(self.templates_dir):
os.makedirs(self.templates_dir)
self._create_default_templates()
def _create_default_templates(self):
"""创建默认模板"""
default_templates = [
{
"id": 1,
"name": "基础生日祝福",
"subject": "生日快乐!🎂",
"type": "html",
"content": self._get_basic_template(),
"description": "简洁温馨的生日祝福",
"created_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
},
{
"id": 2,
"name": "温馨家庭祝福",
"subject": "祝您生日快乐!🎉",
"type": "html",
"content": self._get_heartfelt_template(),
"description": "充满家庭温暖的祝福",
"created_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
},
{
"id": 3,
"name": "幽默生日祝福",
"subject": "又长大一岁啦!😄",
"type": "html",
"content": self._get_funny_template(),
"description": "轻松幽默的生日祝福",
"created_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
]
# 保存模板配置
with open(self.templates_file, 'w', encoding='utf-8') as f:
json.dump(default_templates, f, indent=2, ensure_ascii=False)
# 创建HTML模板文件
self._create_html_template_files()
def _create_html_template_files(self):
"""创建HTML模板文件"""
templates = {
'birthday_basic.html': self._get_basic_template(),
'birthday_heartfelt.html': self._get_heartfelt_template(),
'birthday_funny.html': self._get_funny_template()
}
for filename, content in templates.items():
filepath = os.path.join(self.templates_dir, filename)
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
def _get_basic_template(self):
"""基础生日祝福模板"""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>生日快乐</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px 10px 0 0; }
.content { background: #f9f9f9; padding: 30px; border-radius: 0 0 10px 10px; }
.birthday-cake { font-size: 48px; text-align: center; margin: 20px 0; }
.signature { text-align: right; margin-top: 30px; font-style: italic; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎂 生日快乐! 🎂</h1>
</div>
<div class="content">
<div class="birthday-cake">🎉</div>
<p>亲爱的 {{name}},</p>
<p>在这个特别的日子里,我想送上最诚挚的祝福!</p>
<p>愿您的生日充满欢笑和快乐,愿新的一年带给您健康、幸福和成功!</p>
<p>希望您度过美好的一天!</p>
<div class="signature">
<p>祝好,<br>您的家人</p>
</div>
</div>
</div>
</body>
</html>"""
def _get_heartfelt_template(self):
"""温馨家庭祝福模板"""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>温馨生日祝福</title>
<style>
body { font-family: 'Microsoft YaHei', sans-serif; line-height: 1.8; color: #444; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { text-align: center; background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%); color: white; padding: 40px; border-radius: 15px 15px 0 0; }
.content { background: #fffaf0; padding: 40px; border-radius: 0 0 15px 15px; border: 2px solid #ffe4e1; }
.hearts { text-align: center; font-size: 36px; margin: 20px 0; color: #ff6b6b; }
.family-message { background: #fff0f5; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 4px solid #ff69b4; }
.signature { text-align: center; margin-top: 40px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>❤️ 生日快乐,亲爱的家人! ❤️</h1>
</div>
<div class="content">
<div class="hearts">💖 🎂 💖</div>
<p>亲爱的 {{name}},</p>
<div class="family-message">
<p>在这个属于您的特别日子里,我们全家都想对您说:</p>
<p><strong>感谢您一直以来的关爱和付出!</strong></p>
<p>您是我们家庭的支柱,您的笑容是我们最大的幸福。</p>
</div>
<p>愿岁月温柔待您,愿健康常伴您左右。</p>
<p>愿快乐如影随形,愿幸福永驻心间。</p>
<p>在这个美好的日子里,请接受我们最真挚的祝福!</p>
<div class="signature">
<p>永远爱您的<br><strong>家人</strong> 💕</p>
<p>{{current_date}}</p>
</div>
</div>
</div>
</body>
</html>"""
def _get_funny_template(self):
"""幽默生日祝福模板"""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>幽默生日祝福</title>
<style>
body { font-family: 'Comic Sans MS', cursive, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { text-align: center; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); padding: 30px; border-radius: 20px 20px 0 0; }
.content { background: #fffff0; padding: 30px; border-radius: 0 0 20px 20px; border: 3px dashed #ffd700; }
.emoji-party { text-align: center; font-size: 40px; margin: 20px 0; }
.joke { background: #e6f7ff; padding: 15px; border-radius: 10px; margin: 15px 0; border: 2px solid #91d5ff; }
.fun-fact { background: #f6ffed; padding: 15px; border-radius: 10px; margin: 15px 0; border: 2px solid #b7eb8f; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎊 恭喜您成功升级新版本! 🎊</h1>
<p>(其实就是又长大一岁啦~)</p>
</div>
<div class="content">
<div class="emoji-party">
🎂 🎈 🎁 🥳 🎉
</div>
<p>嘿 {{name}}!</p>
<p>听说今天是个大日子?没错,就是您又"年轻"了一岁的纪念日!</p>
<div class="joke">
<p>📢 <strong>生日冷知识:</strong></p>
<p>年龄只是数字,而您...嗯,数字正在稳步增长! 😄</p>
<p>但别担心,您就像美酒一样,越陈越香!</p>
</div>
<div class="fun-fact">
<p>🌟 <strong>生日特权:</strong></p>
<p>今天您可以:</p>
<ul>
<li>理直气壮地吃蛋糕(不用考虑卡路里!)</li>
<li>要求家人为您唱生日歌(哪怕跑调也要忍着!)</li>
<li>许下三个愿望(我们假装都会实现)</li>
</ul>
</div>
<p>祝您今天笑得像孩子一样开心,玩得比孩子还疯!</p>
<p>生日快乐,永远年轻,永远热泪盈眶!</p>
<div style="text-align: center; margin-top: 30px;">
<p>您的最强后援团 💪<br>全家敬上</p>
<p style="font-size: 12px; color: #666;">PS: 蛋糕记得留一块给我们!</p>
</div>
</div>
</div>
</body>
</html>"""
def _load_templates(self):
"""加载模板数据"""
try:
if os.path.exists(self.templates_file):
with open(self.templates_file, 'r', encoding='utf-8') as f:
return json.load(f)
else:
return []
except Exception as e:
print(f"加载模板失败: {e}")
return []
def _save_templates(self):
"""保存模板数据"""
try:
with open(self.templates_file, 'w', encoding='utf-8') as f:
json.dump(self.templates, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"保存模板失败: {e}")
return False
def get_all_templates(self):
"""获取所有模板"""
return self.templates
def get_template(self, template_id):
"""获取指定模板"""
for template in self.templates:
if template['id'] == template_id:
return template
return None
def get_next_id(self):
"""获取下一个模板ID"""
if not self.templates:
return 1
return max(template['id'] for template in self.templates) + 1
def add_template(self, name, subject, content, description="", template_type="html"):
"""添加新模板"""
new_template = {
"id": self.get_next_id(),
"name": name,
"subject": subject,
"type": template_type,
"content": content,
"description": description,
"created_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
self.templates.append(new_template)
return self._save_templates()
def render_template(self, template_id, contact_name, current_date=None):
"""渲染模板(替换变量)"""
template = self.get_template(template_id)
if not template:
raise ValueError("模板不存在")
if current_date is None:
current_date = datetime.now().strftime('%Y年%m月%d日')
content = template['content']
subject = template['subject']
# 替换变量
content = content.replace('{{name}}', contact_name)
content = content.replace('{{current_date}}', current_date)
subject = subject.replace('{{name}}', contact_name)
return subject, content
# 测试模板管理功能
if __name__ == "__main__":
manager = TemplateManager()
print("可用模板:")
for template in manager.get_all_templates():
print(f"- {template['name']}: {template['description']}")
# 测试模板渲染
if manager.get_all_templates():
subject, content = manager.render_template(1, "小明")
print(f"\n渲染后的主题: {subject}")
print(f"内容预览: {content[:100]}...")
5. email_manager.py - 邮件发送管理
import smtplib
import ssl
from email.mime.text import MimeText
from email.mime.multipart import MimeMultipart
import json
import os
from datetime import datetime
from config import EMAIL_CONFIG, SENDER_CONFIG
class EmailManager:
def __init__(self, history_file='email_history.json'):
self.history_file = history_file
self.smtp_server = EMAIL_CONFIG['smtp_server']
self.smtp_port = EMAIL_CONFIG['smtp_port']
self.sender_email = SENDER_CONFIG['email']
self.sender_password = SENDER_CONFIG['password']
self.sender_name = SENDER_CONFIG['name']
self.history = self._load_history()
def _load_history(self):
"""加载发送历史"""
try:
if os.path.exists(self.history_file):
with open(self.history_file, 'r', encoding='utf-8') as f:
return json.load(f)
else:
return []
except Exception as e:
print(f"加载发送历史失败: {e}")
return []
def _save_history(self):
"""保存发送历史"""
try:
with open(self.history_file, 'w', encoding='utf-8') as f:
json.dump(self.history, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"保存发送历史失败: {e}")
return False
def _create_message(self, receiver_email, receiver_name, subject, html_content):
"""创建邮件消息"""
message = MimeMultipart("alternative")
message["Subject"] = subject
message["From"] = f"{self.sender_name} <{self.sender_email}>"
message["To"] = f"{receiver_name} <{receiver_email}>"
# 创建HTML版本
html_part = MimeText(html_content, "html", "utf-8")
message.attach(html_part)
return message
def send_email(self, receiver_email, receiver_name, subject, html_content):
"""发送邮件"""
try:
# 创建安全SSL上下文
context = ssl.create_default_context()
# 连接到SMTP服务器并发送邮件
with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
server.ehlo() # 向服务器标识身份
server.starttls(context=context) # 启用安全传输
server.ehlo() # 再次向服务器标识身份
server.login(self.sender_email, self.sender_password)
# 创建并发送邮件
message = self._create_message(receiver_email, receiver_name, subject, html_content)
server.sendmail(self.sender_email, receiver_email, message.as_string())
# 记录发送历史
history_entry = {
"timestamp": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
"receiver_name": receiver_name,
"receiver_email": receiver_email,
"subject": subject,
"status": "成功"
}
self.history.append(history_entry)
self._save_history()
return True, "邮件发送成功!"
except smtplib.SMTPAuthenticationError:
error_msg = "认证失败,请检查邮箱和授权码是否正确"
return False, error_msg
except smtplib.SMTPException as e:
error_msg = f"邮件发送失败: {str(e)}"
return False, error_msg
except Exception as e:
error_msg = f"发送过程中出现错误: {str(e)}"
return False, error_msg
def send_birthday_email(self, contact, template_id, template_manager):
"""发送生日祝福邮件"""
try:
# 渲染模板
subject, html_content = template_manager.render_template(
template_id,
contact['name'],
datetime.now().strftime('%Y年%m月%d日')
)
# 发送邮件
success, message = self.send_email(
contact['email'],
contact['name'],
subject,
html_content
)
return success, message
except Exception as e:
return False, f"发送生日邮件失败: {str(e)}"
def get_send_history(self, limit=10):
"""获取发送历史"""
return self.history[-limit:] if self.history else []
def get_today_sent_count(self):
"""获取今天已发送的邮件数量"""
today = datetime.now().strftime('%Y-%m-%d')
return len([h for h in self.history if h['timestamp'].startswith(today)])
def test_connection(self):
"""测试邮箱连接"""
try:
context = ssl.create_default_context()
with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
server.ehlo()
server.starttls(context=context)
server.ehlo()
server.login(self.sender_email, self.sender_password)
return True, "连接测试成功!"
except smtplib.SMTPAuthenticationError:
return False, "认证失败,请检查邮箱和授权码"
except Exception as e:
return False, f"连接测试失败: {str(e)}"
# 测试邮件发送功能
if __name__ == "__main__":
from config import validate_config
# 验证配置
config_valid, config_message = validate_config()
if not config_valid:
print(f"配置错误: {config_message}")
exit(1)
email_manager = EmailManager()
# 测试连接
success, message = email_manager.test_connection()
print(f"连接测试: {message}")
if success:
# 测试发送邮件(需要真实的收件人邮箱)
test_success, test_message = email_manager.send_email(
"test@example.com",
"测试用户",
"测试邮件",
"<h1>这是一封测试邮件</h1><p>如果您收到这封邮件,说明配置正确!</p>"
)
print(f"测试发送: {test_message}")
6. main.py - 主程序(图形界面)
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import threading
import schedule
import time
from datetime import datetime
from contact_manager import ContactManager
from template_manager import TemplateManager
from email_manager import EmailManager
from config import validate_config, SENDER_CONFIG, APP_CONFIG
class BirthdayEmailSender:
def __init__(self, root):
self.root = root
self.root.title("自动化生日邮件发送器")
self.root.geometry("900x700")
self.root.configure(bg='#f0f8ff')
# 初始化管理器
self.contact_manager = ContactManager()
self.template_manager = TemplateManager()
# 验证邮箱配置
self.email_manager = None
self.setup_email_manager()
# 设置样式
self.setup_styles()
# 创建界面
self.create_main_interface()
# 启动定时检查
self.schedule_checker()
def setup_styles(self):
"""设置界面样式"""
style = ttk.Style()
style.configure('Title.TLabel', font=('Arial', 16, 'bold'), background='#f0f8ff')
style.configure('Header.TLabel', font=('Arial', 12, 'bold'), background='#f0f8ff')
style.configure('Success.TLabel', foreground='green', background='#f0f8ff')
style.configure('Warning.TLabel', foreground='orange', background='#f0f8ff')
style.configure('Error.TLabel', foreground='red', background='#f0f8ff')
def setup_email_manager(self):
"""设置邮件管理器"""
config_valid, config_message = validate_config()
if config_valid:
try:
self.email_manager = EmailManager()
# 测试连接
success, message = self.email_manager.test_connection()
if not success:
messagebox.showwarning("邮箱配置警告",
f"邮箱连接测试失败: {message}\n\n请检查 config.py 中的邮箱配置")
except Exception as e:
messagebox.showerror("错误", f"初始化邮件管理器失败: {e}")
else:
messagebox.showwarning("配置提醒",
f"{config_message}\n\n请先编辑 config.py 文件配置您的邮箱信息")
def create_main_interface(self):
"""创建主界面"""
# 创建笔记本(选项卡)
self.notebook = ttk.Notebook(self.root)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 创建各个页面
self.create_dashboard_tab()
self.create_contacts_tab()
self.create_templates_tab()
self.create_send_tab()
self.create_history_tab()
self.create_settings_tab()
def create_dashboard_tab(self):
"""创建仪表板页面"""
dashboard_frame = ttk.Frame(self.notebook)
self.notebook.add(dashboard_frame, text="仪表板")
# 标题
title_label = ttk.Label(dashboard_frame, text="🎉 生日邮件自动发送器", style='Title.TLabel')
title_label.pack(pady=20)
# 统计信息框架
stats_frame = ttk.LabelFrame(dashboard_frame, text="统计信息", padding="15")
stats_frame.pack(fill=tk.X, padx=20, pady=10)
# 今日生日
today_birthdays = self.contact_manager.get_today_birthdays()
today_count = len(today_birthdays)
ttk.Label(stats_frame, text=f"今天过生日的家人: {today_count}人",
style='Header.TLabel' if today_count > 0 else 'Warning.TLabel').pack(anchor=tk.W)
if today_birthdays:
for contact in today_birthdays:
ttk.Label(stats_frame, text=f"🎂 {contact['name']} ({contact['relationship']})").pack(anchor=tk.W, padx=20)
# 即将到来的生日
upcoming_frame = ttk.LabelFrame(dashboard_frame, text="即将到来的生日", padding="15")
upcoming_frame.pack(fill=tk.X, padx=20, pady=10)
upcoming = self.contact_manager.get_upcoming_birthdays(30)
if upcoming:
for contact in upcoming[:5]: # 显示前5个
days_text = "今天" if contact['days_until'] == 0 else f"{contact['days_until']}天后"
ttk.Label(upcoming_frame,
text=f"📅 {contact['name']}: {contact['birthday_date']} ({days_text})").pack(anchor=tk.W)
else:
ttk.Label(upcoming_frame, text="未来30天内没有家人生日").pack(anchor=tk.W)
# 快速操作框架
quick_actions_frame = ttk.LabelFrame(dashboard_frame, text="快速操作", padding="15")
quick_actions_frame.pack(fill=tk.X, padx=20, pady=10)
ttk.Button(quick_actions_frame, text="发送今日生日祝福",
command=self.send_today_birthdays).pack(side=tk.LEFT, padx=5)
ttk.Button(quick_actions_frame, text="检查邮箱连接",
command=self.test_email_connection).pack(side=tk.LEFT, padx=5)
ttk.Button(quick_actions_frame, text="刷新数据",
command=self.refresh_dashboard).pack(side=tk.LEFT, padx=5)
# 系统状态
status_frame = ttk.LabelFrame(dashboard_frame, text="系统状态", padding="15")
status_frame.pack(fill=tk.X, padx=20, pady=10)
email_status = "✅ 已配置" if self.email_manager else "❌ 未配置"
ttk.Label(status_frame, text=f"邮箱配置: {email_status}").pack(anchor=tk.W)
contact_count = len(self.contact_manager.get_all_contacts())
ttk.Label(status_frame, text=f"联系人数量: {contact_count}人").pack(anchor=tk.W)
template_count = len(self.template_manager.get_all_templates())
ttk.Label(status_frame, text=f"邮件模板: {template_count}个").pack(anchor=tk.W)
if self.email_manager:
today_sent = self.email_manager.get_today_sent_count()
ttk.Label(status_frame, text=f"今日已发送: {today_sent}封邮件").pack(anchor=tk.W)
def create_contacts_tab(self):
"""创建联系人管理页面"""
contacts_frame = ttk.Frame(self.notebook)
self.notebook.add(contacts_frame, text="联系人")
# 工具栏
toolbar = ttk.Frame(contacts_frame)
toolbar.pack(fill=tk.X, padx=10, pady=10)
ttk.Button(toolbar, text="添加联系人", command=self.show_add_contact_dialog).pack(side=tk.LEFT, padx=5)
ttk.Button(toolbar, text="编辑选中", command=self.show_edit_contact_dialog).pack(side=tk.LEFT, padx=5)
ttk.Button(toolbar, text="删除选中", command=self.delete_selected_contact).pack(side=tk.LEFT, padx=5)
# 搜索框
search_frame = ttk.Frame(contacts_frame)
search_frame.pack(fill=tk.X, padx=10, pady=5)
ttk.Label(search_frame, text="搜索:").pack(side=tk.LEFT)
self.search_var = tk.StringVar()
search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=30)
search_entry.pack(side=tk.LEFT, padx=5)
search_entry.bind('<KeyRelease>', self.search_contacts)
# 联系人列表
list_frame = ttk.Frame(contacts_frame)
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
columns = ('id', 'name', 'relationship', 'birthday', 'email')
self.contacts_tree = ttk.Treeview(list_frame, columns=columns, show='headings')
# 定义列
self.contacts_tree.heading('id', text='ID')
self.contacts_tree.heading('name', text='姓名')
self.contacts_tree.heading('relationship', text='关系')
self.contacts_tree.heading('birthday', text='生日')
self.contacts_tree.heading('email', text='邮箱')
self.contacts_tree.column('id', width=50)
self.contacts_tree.column('name', width=100)
self.contacts_tree.column('relationship', width=80)
self.contacts_tree.column('birthday', width=80)
self.contacts_tree.column('email', width=200)
# 滚动条
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.contacts_tree.yview)
self.contacts_tree.configure(yscrollcommand=scrollbar.set)
self.contacts_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 绑定双击事件
self.contacts_tree.bind('<Double-1>', self.on_contact_double_click)
# 加载联系人数据
self.refresh_contacts_list()
def create_templates_tab(self):
"""创建模板管理页面"""
templates_frame = ttk.Frame(self.notebook)
self.notebook.add(templates_frame, text="邮件模板")
# 模板列表
list_frame = ttk.Frame(templates_frame)
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self.template_listbox = tk.Listbox(list_frame, font=('Arial', 11))
self.template_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 滚动条
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.template_listbox.yview)
self.template_listbox.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 模板预览
preview_frame = ttk.LabelFrame(templates_frame, text="模板预览", padding="10")
preview_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self.preview_text = scrolledtext.ScrolledText(preview_frame, wrap=tk.WORD, width=80, height=20)
self.preview_text.pack(fill=tk.BOTH, expand=True)
self.preview_text.config(state=tk.DISABLED)
# 绑定选择事件
self.template_listbox.bind('<<ListboxSelect>>', self.on_template_select)
# 加载模板数据
self.refresh_templates_list()
def create_send_tab(self):
"""创建发送邮件页面"""
send_frame = ttk.Frame(self.notebook)
self.notebook.add(send_frame, text="发送邮件")
# 左侧:联系人选择
left_frame = ttk.Frame(send_frame)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
ttk.Label(left_frame, text="选择联系人:", style='Header.TLabel').pack(anchor=tk.W, pady=5)
self.send_contacts_tree = ttk.Treeview(left_frame, columns=('name', 'birthday'), show='headings', height=15)
self.send_contacts_tree.heading('name', text='姓名')
self.send_contacts_tree.heading('birthday', text='生日')
self.send_contacts_tree.column('name', width=120)
self.send_contacts_tree.column('birthday', width=80)
self.send_contacts_tree.pack(fill=tk.BOTH, expand=True)
# 右侧:模板选择和发送
right_frame = ttk.Frame(send_frame)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10)
ttk.Label(right_frame, text="选择模板:", style='Header.TLabel').pack(anchor=tk.W, pady=5)
self.send_template_combobox = ttk.Combobox(right_frame, state="readonly", width=30)
self.send_template_combobox.pack(fill=tk.X, pady=5)
# 预览按钮
ttk.Button(right_frame, text="预览模板", command=self.preview_selected_template).pack(pady=5)
# 发送选项
options_frame = ttk.LabelFrame(right_frame, text="发送选项", padding="10")
options_frame.pack(fill=tk.X, pady=10)
self.immediate_send_var = tk.BooleanVar(value=True)
ttk.Checkbutton(options_frame, text="立即发送", variable=self.immediate_send_var).pack(anchor=tk.W)
ttk.Checkbutton(options_frame, text="仅发送给今天过生日的人",
command=self.filter_today_birthdays).pack(anchor=tk.W, pady=5)
# 发送按钮
ttk.Button(right_frame, text="🚀 发送生日祝福",
command=self.send_selected_emails, style='Accent.TButton').pack(pady=20)
# 状态显示
self.send_status_label = ttk.Label(right_frame, text="", style='Success.TLabel')
self.send_status_label.pack()
# 加载数据
self.refresh_send_contacts()
self.refresh_send_templates()
def create_history_tab(self):
"""创建发送历史页面"""
history_frame = ttk.Frame(self.notebook)
self.notebook.add(history_frame, text="发送历史")
# 历史记录表格
columns = ('timestamp', 'receiver_name', 'receiver_email', 'subject', 'status')
self.history_tree = ttk.Treeview(history_frame, columns=columns, show='headings')
self.history_tree.heading('timestamp', text='发送时间')
self.history_tree.heading('receiver_name', text='收件人')
self.history_tree.heading('receiver_email', text='邮箱')
self.history_tree.heading('subject', text='主题')
self.history_tree.heading('status', text='状态')
self.history_tree.column('timestamp', width=150)
self.history_tree.column('receiver_name', width=100)
self.history_tree.column('receiver_email', width=150)
self.history_tree.column('subject', width=200)
self.history_tree.column('status', width=80)
# 滚动条
scrollbar = ttk.Scrollbar(history_frame, orient=tk.VERTICAL, command=self.history_tree.yview)
self.history_tree.configure(yscrollcommand=scrollbar.set)
self.history_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=10)
# 刷新按钮
ttk.Button(history_frame, text="刷新历史",
command=self.refresh_history).pack(side=tk.BOTTOM, pady=10)
# 加载历史数据
self.refresh_history()
def create_settings_tab(self):
"""创建设置页面"""
settings_frame = ttk.Frame(self.notebook)
self.notebook.add(settings_frame, text="设置")
# 邮箱配置
email_frame = ttk.LabelFrame(settings_frame, text="邮箱配置", padding="15")
email_frame.pack(fill=tk.X, padx=20, pady=10)
ttk.Label(email_frame, text="发件人邮箱:").grid(row=0, column=0, sticky=tk.W, pady=5)
ttk.Label(email_frame, text=SENDER_CONFIG['email']).grid(row=0, column=1, sticky=tk.W, pady=5)
ttk.Label(email_frame, text="发件人名称:").grid(row=1, column=0, sticky=tk.W, pady=5)
ttk.Label(email_frame, text=SENDER_CONFIG['name']).grid(row=1, column=1, sticky=tk.W, pady=5)
ttk.Label(email_frame, text="SMTP服务器:").grid(row=2, column=0, sticky=tk.W, pady=5)
ttk.Label(email_frame, text=EMAIL_CONFIG['smtp_server']).grid(row=2, column=1, sticky=tk.W, pady=5)
ttk.Button(email_frame, text="测试邮箱连接",
command=self.test_email_connection).grid(row=3, column=0, columnspan=2, pady=10)
# 应用设置
app_frame = ttk.LabelFrame(settings_frame, text="应用设置", padding="15")
app_frame.pack(fill=tk.X, padx=20, pady=10)
ttk.Label(app_frame, text="每日发送时间:").grid(row=0, column=0, sticky=tk.W, pady=5)
ttk.Label(app_frame, text=APP_CONFIG['send_time']).grid(row=0, column=1, sticky=tk.W, pady=5)
ttk.Label(app_frame, text="检查间隔:").grid(row=1, column=0, sticky=tk.W, pady=5)
ttk.Label(app_frame, text=f"{APP_CONFIG['check_interval']}小时").grid(row=1, column=1, sticky=tk.W, pady=5)
# 配置说明
help_frame = ttk.LabelFrame(settings_frame, text="配置说明", padding="15")
help_frame.pack(fill=tk.X, padx=20, pady=10)
help_text = """使用说明:
1. 编辑 config.py 文件配置您的邮箱信息
2. 在"联系人"页面添加家人信息
3. 在"发送邮件"页面选择联系人和模板发送祝福
4. 系统会自动在设定的时间发送生日祝福邮件
注意: 邮箱密码应使用授权码,而不是登录密码"""
help_label = ttk.Label(help_frame, text=help_text, justify=tk.LEFT)
help_label.pack(anchor=tk.W)
def refresh_dashboard(self):
"""刷新仪表板"""
# 这里可以添加刷新逻辑
current_tab = self.notebook.index(self.notebook.select())
if current_tab == 0: # 仪表板标签
self.notebook.forget(0)
self.create_dashboard_tab()
self.notebook.insert(0, self.notebook.winfo_children()[0], text="仪表板")
self.notebook.select(0)
def refresh_contacts_list(self):
"""刷新联系人列表"""
for item in self.contacts_tree.get_children():
self.contacts_tree.delete(item)
contacts = self.contact_manager.get_all_contacts()
for contact in contacts:
self.contacts_tree.insert('', tk.END, values=(
contact['id'],
contact['name'],
contact['relationship'],
contact['birthday'],
contact['email']
))
def refresh_templates_list(self):
"""刷新模板列表"""
self.template_listbox.delete(0, tk.END)
templates = self.template_manager.get_all_templates()
for template in templates:
self.template_listbox.insert(tk.END, f"{template['name']} - {template['description']}")
def refresh_send_contacts(self):
"""刷新发送页面的联系人列表"""
for item in self.send_contacts_tree.get_children():
self.send_contacts_tree.delete(item)
contacts = self.contact_manager.get_all_contacts()
for contact in contacts:
self.send_contacts_tree.insert('', tk.END, values=(
contact['name'],
contact['birthday']
), tags=(contact['id'],))
def refresh_send_templates(self):
"""刷新发送页面的模板列表"""
templates = self.template_manager.get_all_templates()
template_names = [f"{t['name']} (ID: {t['id']})" for t in templates]
self.send_template_combobox['values'] = template_names
if template_names:
self.send_template_combobox.current(0)
def refresh_history(self):
"""刷新发送历史"""
for item in self.history_tree.get_children():
self.history_tree.delete(item)
if self.email_manager:
history = self.email_manager.get_send_history(50) # 最近50条记录
for record in reversed(history): # 最新的显示在最上面
self.history_tree.insert('', 0, values=(
record['timestamp'],
record['receiver_name'],
record['receiver_email'],
record['subject'],
record['status']
))
def search_contacts(self, event=None):
"""搜索联系人"""
keyword = self.search_var.get()
if not keyword:
self.refresh_contacts_list()
return
for item in self.contacts_tree.get_children():
self.contacts_tree.delete(item)
results = self.contact_manager.search_contacts(keyword)
for contact in results:
self.contacts_tree.insert('', tk.END, values=(
contact['id'],
contact['name'],
contact['relationship'],
contact['birthday'],
contact['email']
))
def on_contact_double_click(self, event):
"""双击联系人事件"""
self.show_edit_contact_dialog()
def on_template_select(self, event):
"""选择模板事件"""
selection = self.template_listbox.curselection()
if selection:
template_index = selection[0]
templates = self.template_manager.get_all_templates()
if template_index < len(templates):
template = templates[template_index]
self.preview_text.config(state=tk.NORMAL)
self.preview_text.delete(1.0, tk.END)
self.preview_text.insert(1.0, template['content'])
self.preview_text.config(state=tk.DISABLED)
def show_add_contact_dialog(self):
"""显示添加联系人对话框"""
self._show_contact_editor()
def show_edit_contact_dialog(self):
"""显示编辑联系人对话框"""
selection = self.contacts_tree.selection()
if not selection:
messagebox.showwarning("警告", "请先选择一个联系人")
return
item = selection[0]
contact_id = self.contacts_tree.item(item, 'values')[0]
contact = self.contact_manager.get_contact(contact_id)
if contact:
self._show_contact_editor(contact)
def _show_contact_editor(self, contact=None):
"""显示联系人编辑器"""
editor = tk.Toplevel(self.root)
editor.title("编辑联系人" if contact else "添加联系人")
editor.geometry("400x300")
editor.transient(self.root)
editor.grab_set()
# 创建表单
form_frame = ttk.Frame(editor, padding="20")
form_frame.pack(fill=tk.BOTH, expand=True)
# 姓名
ttk.Label(form_frame, text="姓名:*").grid(row=0, column=0, sticky=tk.W, pady=5)
name_var = tk.StringVar(value=contact['name'] if contact else "")
name_entry = ttk.Entry(form_frame, textvariable=name_var, width=30)
name_entry.grid(row=0, column=1, sticky=tk.W, pady=5)
# 关系
ttk.Label(form_frame, text="关系:*").grid(row=1, column=0, sticky=tk.W, pady=5)
relationship_var = tk.StringVar(value=contact['relationship'] if contact else "")
relationship_entry = ttk.Entry(form_frame, textvariable=relationship_var, width=30)
relationship_entry.grid(row=1, column=1, sticky=tk.W, pady=5)
# 邮箱
ttk.Label(form_frame, text="邮箱:*").grid(row=2, column=0, sticky=tk.W, pady=5)
email_var = tk.StringVar(value=contact['email'] if contact else "")
email_entry = ttk.Entry(form_frame, textvariable=email_var, width=30)
email_entry.grid(row=2, column=1, sticky=tk.W, pady=5)
# 生日
ttk.Label(form_frame, text="生日:*").grid(row=3, column=0, sticky=tk.W, pady=5)
birthday_var = tk.StringVar(value=contact['birthday'] if contact else "")
birthday_entry = ttk.Entry(form_frame, textvariable=birthday_var, width=30)
birthday_entry.grid(row=3, column=1, sticky=tk.W, pady=5)
ttk.Label(form_frame, text="格式: MM-DD (如: 01-15)").grid(row=4, column=1, sticky=tk.W)
# 备注
ttk.Label(form_frame, text="备注:").grid(row=5, column=0, sticky=tk.W+tk.N, pady=5)
notes_text = tk.Text(form_frame, height=4, width=30)
notes_text.grid(row=5, column=1, sticky=tk.W+tk.E, pady=5)
if contact and 'notes' in contact:
notes_text.insert('1.0', contact['notes'])
# 按钮
button_frame = ttk.Frame(form_frame)
button_frame.grid(row=6, column=0, columnspan=2, pady=20)
def save_contact():
name = name_var.get().strip()
relationship = relationship_var.get().strip()
email = email_var.get().strip()
birthday = birthday_var.get().strip()
notes = notes_text.get('1.0', tk.END).strip()
if not all([name, relationship, email, birthday]):
messagebox.showerror("错误", "请填写所有必填字段(带*号)")
return
try:
if contact:
# 更新联系人
self.contact_manager.update_contact(
contact['id'],
name=name,
relationship=relationship,
email=email,
birthday=birthday,
notes=notes
)
messagebox.showinfo("成功", "联系人更新成功!")
else:
# 添加新联系人
self.contact_manager.add_contact(
name, email, birthday, relationship, notes
)
messagebox.showinfo("成功", "联系人添加成功!")
editor.destroy()
self.refresh_contacts_list()
self.refresh_send_contacts()
self.refresh_dashboard()
except Exception as e:
messagebox.showerror("错误", str(e))
ttk.Button(button_frame, text="保存",
command=save_contact).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="取消",
command=editor.destroy).pack(side=tk.LEFT, padx=5)
def delete_selected_contact(self):
"""删除选中的联系人"""
selection = self.contacts_tree.selection()
if not selection:
messagebox.showwarning("警告", "请先选择一个联系人")
return
item = selection[0]
contact_id = self.contacts_tree.item(item, 'values')[0]
contact_name = self.contacts_tree.item(item, 'values')[1]
if messagebox.askyesno("确认删除", f"确定要删除联系人 '{contact_name}' 吗?"):
try:
self.contact_manager.delete_contact(contact_id)
messagebox.showinfo("成功", "联系人删除成功!")
self.refresh_contacts_list()
self.refresh_send_contacts()
self.refresh_dashboard()
except Exception as e:
messagebox.showerror("错误", f"删除失败: {e}")
def preview_selected_template(self):
"""预览选中的模板"""
template_text = self.send_template_combobox.get()
if not template_text:
messagebox.showwarning("警告", "请先选择一个模板")
return
# 提取模板ID
try:
template_id = int(template_text.split("ID: ")[1].rstrip(')'))
template = self.template_manager.get_template(template_id)
if template:
preview_window = tk.Toplevel(self.root)
preview_window.title(f"模板预览 - {template['name']}")
preview_window.geometry("600x500")
preview_text = scrolledtext.ScrolledText(preview_window, wrap=tk.WORD)
preview_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
preview_text.insert('1.0', template['content'])
preview_text.config(state=tk.DISABLED)
except (IndexError, ValueError):
messagebox.showerror("错误", "无法解析模板ID")
def filter_today_birthdays(self):
"""筛选今天过生日的人"""
# 这个功能需要实现
pass
def send_selected_emails(self):
"""发送选中的邮件"""
if not self.email_manager:
messagebox.showerror("错误", "邮箱未配置,无法发送邮件")
return
# 获取选中的联系人
selected_items = self.send_contacts_tree.selection()
if not selected_items:
messagebox.showwarning("警告", "请至少选择一个联系人")
return
# 获取选中的模板
template_text = self.send_template_combobox.get()
if not template_text:
messagebox.showwarning("警告", "请选择一个邮件模板")
return
# 提取模板ID
try:
template_id = int(template_text.split("ID: ")[1].rstrip(')'))
except (IndexError, ValueError):
messagebox.showerror("错误", "无法解析模板ID")
return
# 确认发送
if not messagebox.askyesno("确认发送", f"确定要给 {len(selected_items)} 个联系人发送生日祝福吗?"):
return
# 发送邮件
success_count = 0
fail_count = 0
failed_contacts = []
for item in selected_items:
contact_id = self.send_contacts_tree.item(item, 'tags')[0]
contact = self.contact_manager.get_contact(contact_id)
if contact:
success, message = self.email_manager.send_birthday_email(
contact, template_id, self.template_manager
)
if success:
success_count += 1
else:
fail_count += 1
failed_contacts.append(f"{contact['name']}: {message}")
# 显示发送结果
result_message = f"发送完成!\n成功: {success_count} 封\n失败: {fail_count} 封"
if failed_contacts:
result_message += f"\n\n失败详情:\n" + "\n".join(failed_contacts)
messagebox.showinfo("发送结果", result_message)
self.refresh_history()
self.send_status_label.config(text=f"最近发送: {datetime.now().strftime('%H:%M:%S')}")
def send_today_birthdays(self):
"""发送今日生日祝福"""
today_birthdays = self.contact_manager.get_today_birthdays()
if not today_birthdays:
messagebox.showinfo("提示", "今天没有家人生日")
return
if not self.email_manager:
messagebox.showerror("错误", "邮箱未配置,无法发送邮件")
return
# 使用第一个模板
templates = self.template_manager.get_all_templates()
if not templates:
messagebox.showerror("错误", "没有可用的邮件模板")
return
template_id = templates[0]['id']
# 确认发送
contact_names = ", ".join([c['name'] for c in today_birthdays])
if not messagebox.askyesno("确认发送", f"确定要给今天过生日的 {contact_names} 发送祝福吗?"):
return
# 发送邮件
success_count = 0
for contact in today_birthdays:
success, message = self.email_manager.send_birthday_email(
contact, template_id, self.template_manager
)
if success:
success_count += 1
messagebox.showinfo("发送完成", f"已向 {success_count}/{len(today_birthdays)} 位家人发送生日祝福")
self.refresh_history()
self.refresh_dashboard()
def test_email_connection(self):
"""测试邮箱连接"""
if not self.email_manager:
messagebox.showerror("错误", "邮箱未配置")
return
def test_in_thread():
success, message = self.email_manager.test_connection()
self.root.after(0, lambda: messagebox.showinfo(
"连接测试",
f"结果: {'成功' if success else '失败'}\n信息: {message}"
))
threading.Thread(target=test_in_thread, daemon=True).start()
def schedule_checker(self):
"""定时检查生日"""
# 设置定时任务
schedule.every(APP_CONFIG['check_interval']).hours.do(self.check_birthdays)
# 在后台运行定时任务
def run_scheduler():
while True:
schedule.run_pending()
time.sleep(60) # 每分钟检查一次
threading.Thread(target=run_scheduler, daemon=True).start()
def check_birthdays(self):
"""检查生日并发送邮件"""
today_birthdays = self.contact_manager.get_today_birthdays()
if today_birthdays and self.email_manager:
# 发送生日祝福
templates = self.template_manager.get_all_templates()
if templates:
template_id = templates[0]['id']
for contact in today_birthdays:
self.email_manager.send_birthday_email(
contact, template_id, self.template_manager
)
# 更新界面(在主线程中执行)
self.root.after(0, lambda: self.send_status_label.config(
text=f"自动发送: {datetime.now().strftime('%H:%M:%S')}"
))
self.root.after(0, self.refresh_history)
self.root.after(0, self.refresh_dashboard)
def main():
"""主函数"""
root = tk.Tk()
app = BirthdayEmailSender(root)
root.mainloop()
if __name__ == "__main__":
main()
如何在VSCode中运行
1. 环境设置
-
创建项目文件夹:
mkdir email-sender cd email-sender
-
创建虚拟环境(推荐):
python -m venv email_env # Windows email_env\Scripts\activate # Mac/Linux source email_env/bin/activate
-
安装依赖:
pip install schedule
2. 配置邮箱
-
编辑 config.py 文件:
- 修改
SENDER_CONFIG
中的邮箱和授权码 - 根据您的邮箱服务商调整
EMAIL_CONFIG
- 修改
-
获取邮箱授权码:
- QQ邮箱:设置 → 账户 → 开启POP3/SMTP服务 → 生成授权码
- 163邮箱:设置 → POP3/SMTP/IMAP → 开启SMTP服务 → 设置授权码
- Gmail:设置 → 转发和POP/IMAP → 启用IMAP → 使用应用专用密码
3. 运行程序
在VSCode中运行:
方法一:运行主程序
python main.py
方法二:在VSCode中使用运行按钮
- 打开
main.py
文件 - 点击右上角的运行按钮 ▶️
- 或按 F5 键
使用说明
1. 添加联系人
- 在"联系人"页面点击"添加联系人"
- 填写姓名、关系、邮箱和生日(MM-DD格式)
- 保存后联系人会出现在列表中
2. 发送生日祝福
- 在"发送邮件"页面选择联系人和模板
- 点击"发送生日祝福"按钮
- 系统会立即发送邮件
3. 自动发送
- 程序会定时检查今天过生日的家人
- 自动发送生日祝福邮件
- 发送记录可以在"发送历史"中查看
4. 邮件模板
- 系统提供3种默认模板:基础、温馨、幽默
- 可以在"邮件模板"页面预览模板内容
功能特点
1. 安全可靠
- 使用SSL加密连接
- 授权码而非密码验证
- 本地存储联系人数据
2. 用户友好
- 图形化界面操作简单
- 实时生日提醒
- 发送状态反馈
3. 灵活定制
- 支持自定义邮件模板
- 可调整发送时间
- 多种祝福风格选择
4. 自动化
- 定时检查生日
- 自动发送祝福
- 发送记录追踪
扩展建议
学生可以进一步扩展这个项目:
- 更多邮件模板:添加节日祝福、日常问候等模板
- 附件支持:支持发送生日照片或电子贺卡
- 多语言支持:添加英文等其他语言的祝福模板
- 云端同步:将联系人数据同步到云端
- 手机提醒:集成短信或消息推送提醒
- 批量导入:支持从Excel或CSV导入联系人
这个项目是一个很好的实践,可以帮助学生学习邮件协议、GUI编程、定时任务和数据处理等知识。
更多推荐
所有评论(0)