使用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. 环境设置

  1. 创建项目文件夹

    mkdir email-sender
    cd email-sender
    
  2. 创建虚拟环境(推荐):

    python -m venv email_env
    # Windows
    email_env\Scripts\activate
    # Mac/Linux
    source email_env/bin/activate
    
  3. 安装依赖

    pip install schedule
    

2. 配置邮箱

  1. 编辑 config.py 文件

    • 修改 SENDER_CONFIG 中的邮箱和授权码
    • 根据您的邮箱服务商调整 EMAIL_CONFIG
  2. 获取邮箱授权码

    • 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. 自动化

  • 定时检查生日
  • 自动发送祝福
  • 发送记录追踪

扩展建议

学生可以进一步扩展这个项目:

  1. 更多邮件模板:添加节日祝福、日常问候等模板
  2. 附件支持:支持发送生日照片或电子贺卡
  3. 多语言支持:添加英文等其他语言的祝福模板
  4. 云端同步:将联系人数据同步到云端
  5. 手机提醒:集成短信或消息推送提醒
  6. 批量导入:支持从Excel或CSV导入联系人

这个项目是一个很好的实践,可以帮助学生学习邮件协议、GUI编程、定时任务和数据处理等知识。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐