跳转至

代码实战项目3:待办事项清单应用

项目概述

待办事项清单(Todo List)是一个经典的项目管理工具,帮助用户记录、跟踪和管理日常任务。本应用将实现任务的增删改查、优先级设置、截止日期、完成状态管理等功能。

学习目标

  • 掌握面向对象编程的核心概念
  • 学会使用 datetime 模块处理日期时间
  • 理解优先级队列和排序算法的应用
  • 掌握用户界面的设计和实现

功能需求

核心功能

  1. 任务管理
  2. 添加新任务
  3. 编辑任务信息
  4. 删除任务
  5. 标记任务完成/未完成

  6. 任务属性

  7. 任务标题
  8. 任务描述
  9. 优先级(高、中、低)
  10. 截止日期
  11. 创建时间
  12. 完成状态

  13. 查询和筛选

  14. 查看所有任务
  15. 按优先级筛选
  16. 按完成状态筛选
  17. 按截止日期筛选

  18. 统计功能

  19. 统计已完成/未完成任务数
  20. 统计过期任务数
  21. 统计各优先级任务数

  22. 数据持久化

  23. 保存任务到文件
  24. 从文件加载任务

系统设计

数据结构设计

from datetime import datetime
from enum import Enum

class Priority(Enum):
    """优先级枚举"""
    HIGH = 3
    MEDIUM = 2
    LOW = 1

class Task:
    """任务类"""
    def __init__(self, title, description="", priority=Priority.MEDIUM, due_date=None):
        self.id = None                # 任务ID
        self.title = title            # 任务标题
        self.description = description  # 任务描述
        self.priority = priority      # 优先级
        self.due_date = due_date      # 截止日期
        self.created_at = datetime.now()  # 创建时间
        self.completed = False        # 完成状态
        self.completed_at = None      # 完成时间

class TodoManager:
    """待办事项管理类"""
    def __init__(self):
        self.tasks = {}      # 任务字典 {id: Task}
        self.next_id = 1     # 下一个任务ID

文件结构

todo_list/
├── main.py           # 主程序
├── task.py           # 任务类
├── manager.py        # 管理类
├── data.json         # 数据文件
└── README.md         # 说明文档

代码实现

1. 任务类(task.py)

from datetime import datetime
from enum import Enum

class Priority(Enum):
    """优先级枚举"""
    HIGH = 3
    MEDIUM = 2
    LOW = 1

    def __str__(self):
        """优先级字符串表示"""
        return self.name

    @classmethod
    def from_string(cls, priority_str):
        """从字符串创建优先级"""
        priority_map = {
            'high': cls.HIGH,
            'medium': cls.MEDIUM,
            'low': cls.LOW,
            '高': cls.HIGH,
            '中': cls.MEDIUM,
            '低': cls.LOW
        }
        return priority_map.get(priority_str.lower(), cls.MEDIUM)

class Task:
    """任务类"""

    def __init__(self, title, description="", priority=Priority.MEDIUM, due_date=None):
        """
        初始化任务

        参数:
            title: 任务标题
            description: 任务描述
            priority: 优先级
            due_date: 截止日期(datetime对象或字符串)
        """
        self.id = None
        self.title = title
        self.description = description
        self.priority = priority
        self.created_at = datetime.now()
        self.completed = False
        self.completed_at = None

        # 处理截止日期
        if due_date:
            if isinstance(due_date, str):
                self.due_date = datetime.strptime(due_date, "%Y-%m-%d")
            else:
                self.due_date = due_date
        else:
            self.due_date = None

    def complete(self):
        """标记任务为完成"""
        self.completed = True
        self.completed_at = datetime.now()

    def uncomplete(self):
        """标记任务为未完成"""
        self.completed = False
        self.completed_at = None

    def is_overdue(self):
        """检查任务是否过期"""
        if self.due_date and not self.completed:
            return datetime.now() > self.due_date
        return False

    def days_until_due(self):
        """计算距离截止日期的天数"""
        if not self.due_date:
            return None

        delta = self.due_date - datetime.now()
        return delta.days

    def __str__(self):
        """字符串表示"""
        status = "✓" if self.completed else "○"
        priority_icon = {Priority.HIGH: "🔴", Priority.MEDIUM: "🟡", Priority.LOW: "🟢"}

        due_str = ""
        if self.due_date:
            if self.is_overdue():
                due_str = f" ⚠️ 过期 {abs(self.days_until_due())} 天"
            else:
                due_str = f" 📅 {self.due_date.strftime('%Y-%m-%d')} ({self.days_until_due()} 天)"

        return f"{status} [{priority_icon[self.priority]}] {self.title}{due_str}"

    def to_dict(self):
        """转换为字典"""
        return {
            'id': self.id,
            'title': self.title,
            'description': self.description,
            'priority': self.priority.value,
            'due_date': self.due_date.isoformat() if self.due_date else None,
            'created_at': self.created_at.isoformat(),
            'completed': self.completed,
            'completed_at': self.completed_at.isoformat() if self.completed_at else None
        }

    @classmethod
    def from_dict(cls, data):
        """从字典创建对象"""
        task = cls(
            title=data['title'],
            description=data.get('description', ''),
            priority=Priority(data['priority']),
            due_date=data['due_date']
        )

        task.id = data['id']
        task.created_at = datetime.fromisoformat(data['created_at'])
        task.completed = data['completed']

        if data['completed_at']:
            task.completed_at = datetime.fromisoformat(data['completed_at'])

        return task

2. 管理类(manager.py)

import json
from datetime import datetime
from task import Task, Priority

class TodoManager:
    """待办事项管理类"""

    def __init__(self):
        """初始化管理器"""
        self.tasks = {}
        self.next_id = 1

    def add_task(self, title, description="", priority=Priority.MEDIUM, due_date=None):
        """
        添加任务

        参数:
            title: 任务标题
            description: 任务描述
            priority: 优先级
            due_date: 截止日期

        返回:
            Task: 创建的任务对象
        """
        task = Task(title, description, priority, due_date)
        task.id = self.next_id
        self.tasks[task.id] = task
        self.next_id += 1
        return task

    def get_task(self, task_id):
        """
        获取任务

        参数:
            task_id: 任务ID

        返回:
            Task: 任务对象,不存在返回None
        """
        return self.tasks.get(task_id)

    def update_task(self, task_id, title=None, description=None, priority=None, due_date=None):
        """
        更新任务

        参数:
            task_id: 任务ID
            title: 新标题
            description: 新描述
            priority: 新优先级
            due_date: 新截止日期

        返回:
            bool: 更新成功返回True
        """
        task = self.get_task(task_id)
        if not task:
            return False

        if title is not None:
            task.title = title
        if description is not None:
            task.description = description
        if priority is not None:
            task.priority = priority
        if due_date is not None:
            if isinstance(due_date, str):
                task.due_date = datetime.strptime(due_date, "%Y-%m-%d")
            else:
                task.due_date = due_date

        return True

    def delete_task(self, task_id):
        """
        删除任务

        参数:
            task_id: 任务ID

        返回:
            bool: 删除成功返回True
        """
        if task_id in self.tasks:
            del self.tasks[task_id]
            return True
        return False

    def complete_task(self, task_id):
        """
        完成任务

        参数:
            task_id: 任务ID

        返回:
            bool: 操作成功返回True
        """
        task = self.get_task(task_id)
        if task:
            task.complete()
            return True
        return False

    def uncomplete_task(self, task_id):
        """
        取消任务完成

        参数:
            task_id: 任务ID

        返回:
            bool: 操作成功返回True
        """
        task = self.get_task(task_id)
        if task:
            task.uncomplete()
            return True
        return False

    def get_all_tasks(self, sort_by='priority'):
        """
        获取所有任务

        参数:
            sort_by: 排序方式 ('priority', 'due_date', 'created_at')

        返回:
            list: 任务列表
        """
        tasks = list(self.tasks.values())

        if sort_by == 'priority':
            tasks.sort(key=lambda t: (-t.priority.value, t.id))
        elif sort_by == 'due_date':
            tasks.sort(key=lambda t: (t.due_date or datetime.max, t.id))
        elif sort_by == 'created_at':
            tasks.sort(key=lambda t: t.created_at)

        return tasks

    def get_tasks_by_status(self, completed=None):
        """
        按状态获取任务

        参数:
            completed: 完成状态(True/False/None)

        返回:
            list: 任务列表
        """
        if completed is None:
            return list(self.tasks.values())
        return [t for t in self.tasks.values() if t.completed == completed]

    def get_tasks_by_priority(self, priority):
        """
        按优先级获取任务

        参数:
            priority: 优先级

        返回:
            list: 任务列表
        """
        return [t for t in self.tasks.values() if t.priority == priority]

    def get_overdue_tasks(self):
        """
        获取过期任务

        返回:
            list: 任务列表
        """
        return [t for t in self.tasks.values() if t.is_overdue()]

    def get_statistics(self):
        """
        获取统计信息

        返回:
            dict: 统计信息
        """
        total = len(self.tasks)
        completed = len(self.get_tasks_by_status(True))
        uncompleted = total - completed
        overdue = len(self.get_overdue_tasks())

        high_priority = len(self.get_tasks_by_priority(Priority.HIGH))
        medium_priority = len(self.get_tasks_by_priority(Priority.MEDIUM))
        low_priority = len(self.get_tasks_by_priority(Priority.LOW))

        return {
            'total': total,
            'completed': completed,
            'uncompleted': uncompleted,
            'overdue': overdue,
            'high_priority': high_priority,
            'medium_priority': medium_priority,
            'low_priority': low_priority
        }

    def search_tasks(self, keyword):
        """
        搜索任务

        参数:
            keyword: 关键词

        返回:
            list: 任务列表
        """
        keyword = keyword.lower()
        return [t for t in self.tasks.values()
                if keyword in t.title.lower() or keyword in t.description.lower()]

    def save_to_file(self, filename='todo_data.json'):
        """
        保存数据到文件

        参数:
            filename: 文件名
        """
        data = {
            'tasks': [task.to_dict() for task in self.tasks.values()],
            'next_id': self.next_id
        }

        try:
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            print(f"数据已保存到 {filename}")
        except Exception as e:
            print(f"保存失败: {e}")

    def load_from_file(self, filename='todo_data.json'):
        """
        从文件加载数据

        参数:
            filename: 文件名
        """
        try:
            with open(filename, 'r', encoding='utf-8') as f:
                data = json.load(f)

            self.tasks = {
                task_data['id']: Task.from_dict(task_data)
                for task_data in data.get('tasks', [])
            }
            self.next_id = data.get('next_id', 1)

            print(f"数据已从 {filename} 加载")
        except FileNotFoundError:
            print(f"文件 {filename} 不存在")
        except Exception as e:
            print(f"加载失败: {e}")

3. 主程序(main.py)

from datetime import datetime
from manager import TodoManager
from task import Priority

def display_menu():
    """显示菜单"""
    print("\n" + "="*50)
    print("待办事项清单")
    print("="*50)
    print("1. 添加任务")
    print("2. 查看所有任务")
    print("3. 查看未完成任务")
    print("4. 查看已完成任务")
    print("5. 查看过期任务")
    print("6. 搜索任务")
    print("7. 编辑任务")
    print("8. 标记完成/未完成")
    print("9. 删除任务")
    print("10. 统计信息")
    print("11. 保存数据")
    print("12. 加载数据")
    print("0. 退出")
    print("="*50)

def display_tasks(tasks, title="任务列表"):
    """显示任务列表"""
    if not tasks:
        print(f"\n{title}: 没有任务")
        return

    print(f"\n{title} ({len(tasks)} 个任务):")
    print("-" * 60)

    for task in tasks:
        print(f"{task}")

        if task.description:
            print(f"   描述: {task.description}")

        if task.completed and task.completed_at:
            print(f"   完成时间: {task.completed_at.strftime('%Y-%m-%d %H:%M')}")

        print()

def main():
    """主函数"""
    manager = TodoManager()

    while True:
        display_menu()
        choice = input("请选择操作: ").strip()

        if choice == '0':
            print("感谢使用,再见!")
            break

        elif choice == '1':
            # 添加任务
            print("\n--- 添加新任务 ---")
            title = input("任务标题: ").strip()
            if not title:
                print("标题不能为空")
                continue

            description = input("任务描述(可选): ").strip()
            priority_str = input("优先级(高/中/低,默认中): ").strip() or "中"
            priority = Priority.from_string(priority_str)

            due_date_str = input("截止日期(YYYY-MM-DD,可选): ").strip()
            due_date = None
            if due_date_str:
                try:
                    due_date = datetime.strptime(due_date_str, "%Y-%m-%d")
                except ValueError:
                    print("日期格式错误,请使用 YYYY-MM-DD 格式")

            task = manager.add_task(title, description, priority, due_date)
            print(f"✓ 任务已添加(ID: {task.id})")

        elif choice == '2':
            # 查看所有任务
            tasks = manager.get_all_tasks()
            display_tasks(tasks, "所有任务")

        elif choice == '3':
            # 查看未完成任务
            tasks = manager.get_tasks_by_status(False)
            display_tasks(tasks, "未完成任务")

        elif choice == '4':
            # 查看已完成任务
            tasks = manager.get_tasks_by_status(True)
            display_tasks(tasks, "已完成任务")

        elif choice == '5':
            # 查看过期任务
            tasks = manager.get_overdue_tasks()
            display_tasks(tasks, "过期任务")

        elif choice == '6':
            # 搜索任务
            keyword = input("搜索关键词: ").strip()
            if keyword:
                tasks = manager.search_tasks(keyword)
                display_tasks(tasks, f"搜索结果: '{keyword}'")

        elif choice == '7':
            # 编辑任务
            task_id = int(input("任务ID: ").strip())
            task = manager.get_task(task_id)

            if not task:
                print("任务不存在")
                continue

            print(f"\n当前任务: {task}")
            print(f"描述: {task.description}")
            print(f"优先级: {task.priority}")
            print(f"截止日期: {task.due_date.strftime('%Y-%m-%d') if task.due_date else '无'}")

            print("\n--- 编辑任务(留空保持原值) ---")
            title = input(f"新标题(当前: {task.title}): ").strip()
            description = input(f"新描述(当前: {task.description}): ").strip()
            priority_str = input(f"新优先级(当前: {task.priority}): ").strip()
            due_date_str = input(f"新截止日期(当前: {task.due_date.strftime('%Y-%m-%d') if task.due_date else '无'}): ").strip()

            priority = Priority.from_string(priority_str) if priority_str else None
            due_date = None
            if due_date_str:
                try:
                    due_date = datetime.strptime(due_date_str, "%Y-%m-%d")
                except ValueError:
                    print("日期格式错误")

            manager.update_task(task_id, title or None, description or None, priority, due_date)
            print("✓ 任务已更新")

        elif choice == '8':
            # 标记完成/未完成
            task_id = int(input("任务ID: ").strip())
            task = manager.get_task(task_id)

            if not task:
                print("任务不存在")
                continue

            if task.completed:
                manager.uncomplete_task(task_id)
                print("✓ 任务已标记为未完成")
            else:
                manager.complete_task(task_id)
                print("✓ 任务已标记为完成")

        elif choice == '9':
            # 删除任务
            task_id = int(input("任务ID: ").strip())
            if manager.delete_task(task_id):
                print("✓ 任务已删除")
            else:
                print("任务不存在")

        elif choice == '10':
            # 统计信息
            stats = manager.get_statistics()
            print("\n--- 统计信息 ---")
            print(f"总任务数: {stats['total']}")
            print(f"已完成: {stats['completed']} ({stats['completed']/stats['total']*100:.1f}%)" if stats['total'] > 0 else "已完成: 0")
            print(f"未完成: {stats['uncompleted']} ({stats['uncompleted']/stats['total']*100:.1f}%)" if stats['total'] > 0 else "未完成: 0")
            print(f"过期: {stats['overdue']}")
            print(f"\n按优先级:")
            print(f"  高: {stats['high_priority']}")
            print(f"  中: {stats['medium_priority']}")
            print(f"  低: {stats['low_priority']}")

        elif choice == '11':
            # 保存数据
            filename = input("文件名(默认todo_data.json): ").strip() or 'todo_data.json'
            manager.save_to_file(filename)

        elif choice == '12':
            # 加载数据
            filename = input("文件名(默认todo_data.json): ").strip() or 'todo_data.json'
            manager.load_from_file(filename)

        else:
            print("无效的选择,请重新输入")

if __name__ == '__main__':
    main()

运行指南

1. 安装依赖

本项目只使用 Python 标准库,无需安装额外依赖。

2. 运行程序

python main.py

3. 使用说明

  1. 首先添加一些任务
  2. 可以设置优先级和截止日期
  3. 定期查看任务列表
  4. 完成任务后标记为完成
  5. 定期保存数据

扩展功能建议

基础扩展

  1. 任务分类
  2. 添加任务类别(工作、学习、生活等)
  3. 按类别筛选任务

  4. 提醒功能

  5. 设置任务提醒时间
  6. 提前提醒过期任务

  7. 批量操作

  8. 批量删除任务
  9. 批量标记完成

进阶扩展

  1. 子任务
  2. 支持将任务分解为子任务
  3. 子任务进度跟踪

  4. 标签系统

  5. 为任务添加多个标签
  6. 按标签筛选任务

  7. 任务模板

  8. 创建常用任务模板
  9. 快速创建重复性任务

高级扩展

  1. 多用户支持
  2. 支持多用户登录
  3. 每个用户独立的任务列表

  4. 同步功能

  5. 与云服务同步
  6. 跨设备访问

  7. 图形界面

  8. 使用 PyQt/Tkinter 开发桌面应用
  9. 使用 Web 技术开发在线应用

常见问题

Q: 如何查看任务的详细信息?

A: 选择"编辑任务"选项,输入任务ID即可查看当前任务的所有信息。

Q: 过期任务会自动提醒吗?

A: 不会,但你可以随时选择"查看过期任务"来查看所有过期的任务。

Q: 优先级有什么用?

A: 优先级决定了任务在列表中的显示顺序(高优先级排在前面)。

Q: 数据保存在哪里?

A: 默认保存在程序目录下的 todo_data.json 文件中。

项目总结

本项目实现了一个功能完整的待办事项清单应用,涵盖了:

  1. 任务管理:完整的 CRUD 操作
  2. 优先级系统:支持高、中、低三个优先级
  3. 日期管理:支持创建时间、截止日期、完成时间
  4. 状态管理:完成/未完成状态
  5. 数据持久化:使用 JSON 保存数据
  6. 统计分析:多种统计和查询功能

通过本项目,你将掌握: - Python 面向对象编程 - 枚举类型的使用 - datetime 模块的应用 - 数据序列化和持久化 - 用户界面设计

下一步

尝试添加更多功能,如: - 任务分类和标签 - 子任务支持 - 图形界面(PyQt/Tkinter) - Web 版本(Flask/Django) - 单元测试


祝你使用愉快!