折腾侠
项目实战

从零打造个人任务管理系统:一个终端里的效率利器

折腾侠
2026/03/28 发布
17约 10 分钟1369 字 / 1297 词00

从零打造个人任务管理系统:一个终端里的效率利器

项目概述

在这个信息爆炸的时代,我们每天都要面对大量的任务和待办事项。虽然市面上有众多任务管理应用,但很多时候我们只需要一个轻量级、快速响应的工具来帮助自己记录和管理日常任务。本项目将带你从零开始,使用 Python 打造一个运行在终端里的个人任务管理系统(Task Manager CLI)。

这个系统不仅能够帮助你理解命令行应用开发的核心概念,还能让你掌握数据存储、用户交互、状态管理等关键技能。完成这个项目后,你将拥有一个真正可用的效率工具,同时积累宝贵的实战经验。

项目功能说明

核心功能

  1. 任务创建:支持快速添加新任务,可设置优先级和截止日期
  2. 任务列表:查看所有任务,支持按状态、优先级筛选
  3. 任务更新:修改任务内容、优先级、状态等信息
  4. 任务完成:标记任务为已完成状态
  5. 任务删除:移除不需要的任务
  6. 数据统计:展示任务完成情况和统计信息

扩展功能

  1. 任务分类:支持为任务添加标签进行分类
  2. 搜索功能:根据关键词快速查找任务
  3. 数据导出:将任务数据导出为 JSON 或 CSV 格式
  4. 自动备份:定期备份任务数据防止丢失

技术栈选择

编程语言:Python 3.9+

选择 Python 的原因:

  • 语法简洁,易于上手
  • 丰富的标准库和第三方库支持
  • 跨平台兼容性好
  • 适合快速原型开发

核心依赖

# requirements.txt
click==8.1.7        # 命令行界面框架
rich==13.7.0        # 终端美化输出
pydantic==2.5.0     # 数据验证和序列化
python-dateutil==2.8.2  # 日期处理

项目结构

task-manager-cli/
├── README.md              # 项目说明文档
├── requirements.txt       # 依赖列表
├── setup.py              # 安装配置
├── taskmanager/
│   ├── __init__.py       # 包初始化
│   ├── cli.py            # 命令行入口
│   ├── models.py         # 数据模型定义
│   ├── storage.py        # 数据存储模块
│   ├── commands/         # 命令模块
│   │   ├── __init__.py
│   │   ├── add.py        # 添加任务命令
│   │   ├── list.py       # 列表命令
│   │   ├── update.py     # 更新命令
│   │   ├── complete.py   # 完成命令
│   │   └── stats.py      # 统计命令
│   └── utils/
│       ├── __init__.py
│       ├── formatters.py # 格式化输出
│       └── validators.py # 输入验证
└── tests/
    ├── __init__.py
    ├── test_models.py
    └── test_storage.py

核心代码实现

1. 数据模型定义(models.py)

Python
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional, List
import uuid


class Priority(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    URGENT = "urgent"


class TaskStatus(Enum):
    PENDING = "pending"
    IN_PROGRESS = "in_progress"
    COMPLETED = "completed"
    CANCELLED = "cancelled"


@dataclass
class Task:
    """任务数据模型"""
    title: str
    description: str = ""
    priority: Priority = Priority.MEDIUM
    status: TaskStatus = TaskStatus.PENDING
    tags: List[str] = field(default_factory=list)
    due_date: Optional[datetime] = None
    created_at: datetime = field(default_factory=datetime.now)
    updated_at: datetime = field(default_factory=datetime.now)
    id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
    
    def to_dict(self) -> dict:
        """转换为字典格式"""
        return {
            "id": self.id,
            "title": self.title,
            "description": self.description,
            "priority": self.priority.value,
            "status": self.status.value,
            "tags": self.tags,
            "due_date": self.due_date.isoformat() if self.due_date else None,
            "created_at": self.created_at.isoformat(),
            "updated_at": self.updated_at.isoformat(),
        }
    
    @classmethod
    def from_dict(cls, data: dict) -> "Task":
        """从字典创建任务"""
        return cls(
            id=data["id"],
            title=data["title"],
            description=data.get("description", ""),
            priority=Priority(data.get("priority", "medium")),
            status=TaskStatus(data.get("status", "pending")),
            tags=data.get("tags", []),
            due_date=datetime.fromisoformat(data["due_date"]) if data.get("due_date") else None,
            created_at=datetime.fromisoformat(data["created_at"]),
            updated_at=datetime.fromisoformat(data["updated_at"]),
        )

2. 数据存储模块(storage.py)

Python
import json
import os
from pathlib import Path
from typing import List, Optional
from datetime import datetime

from .models import Task


class TaskStorage:
    """任务数据存储管理类"""
    
    def __init__(self, data_dir: Optional[str] = None):
        if data_dir is None:
            data_dir = Path.home() / ".taskmanager"
        self.data_dir = Path(data_dir)
        self.data_file = self.data_dir / "tasks.json"
        self._ensure_data_dir()
        self._tasks: List[Task] = []
        self._load()
    
    def _ensure_data_dir(self):
        """确保数据目录存在"""
        self.data_dir.mkdir(parents=True, exist_ok=True)
    
    def _load(self):
        """从文件加载任务数据"""
        if self.data_file.exists():
            with open(self.data_file, "r", encoding="utf-8") as f:
                data = json.load(f)
                self._tasks = [Task.from_dict(t) for t in data.get("tasks", [])]
    
    def _save(self):
        """保存任务数据到文件"""
        data = {
            "tasks": [t.to_dict() for t in self._tasks],
            "last_updated": datetime.now().isoformat(),
        }
        with open(self.data_file, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
    
    def add(self, task: Task) -> Task:
        """添加新任务"""
        self._tasks.append(task)
        self._save()
        return task
    
    def get_all(self) -> List[Task]:
        """获取所有任务"""
        return self._tasks.copy()
    
    def get_by_id(self, task_id: str) -> Optional[Task]:
        """根据 ID 获取任务"""
        for task in self._tasks:
            if task.id == task_id:
                return task
        return None
    
    def update(self, task: Task) -> bool:
        """更新任务"""
        for i, t in enumerate(self._tasks):
            if t.id == task.id:
                task.updated_at = datetime.now()
                self._tasks[i] = task
                self._save()
                return True
        return False
    
    def delete(self, task_id: str) -> bool:
        """删除任务"""
        for i, task in enumerate(self._tasks):
            if task.id == task_id:
                self._tasks.pop(i)
                self._save()
                return True
        return False
    
    def backup(self, backup_dir: Optional[str] = None) -> str:
        """创建数据备份"""
        if backup_dir is None:
            backup_dir = self.data_dir / "backups"
        backup_path = Path(backup_dir)
        backup_path.mkdir(parents=True, exist_ok=True)
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_file = backup_path / f"tasks_backup_{timestamp}.json"
        
        with open(backup_file, "w", encoding="utf-8") as f:
            json.dump(
                {"tasks": [t.to_dict() for t in self._tasks]},
                f,
                indent=2,
                ensure_ascii=False,
            )
        return str(backup_file)

3. 命令行入口(cli.py)

Python
import click
from rich.console import Console
from rich.table import Table
from rich.panel import Panel

from .models import Task, Priority, TaskStatus
from .storage import TaskStorage
from .utils.formatters import format_priority, format_status, format_due_date


console = Console()
storage = TaskStorage()


@click.group()
@click.version_option(version="1.0.0")
def cli():
    """📋 Task Manager CLI - 个人任务管理系统
    
    一个轻量级的终端任务管理工具,帮助你高效管理日常任务。
    """
    pass


@cli.command()
@click.argument("title")
@click.option("--description", "-d", default="", help="任务描述")
@click.option("--priority", "-p", type=click.Choice(["low", "medium", "high", "urgent"]), 
              default="medium", help="任务优先级")
@click.option("--due", help="截止日期 (格式:YYYY-MM-DD)")
@click.option("--tags", "-t", multiple=True, help="任务标签")
def add(title, description, priority, due, tags):
    """添加新任务"""
    from datetime import datetime
    
    due_date = None
    if due:
        try:
            due_date = datetime.strptime(due, "%Y-%m-%d")
        except ValueError:
            console.print("[red]❌ 日期格式错误,请使用 YYYY-MM-DD 格式[/red]")
            return
    
    task = Task(
        title=title,
        description=description,
        priority=Priority(priority),
        due_date=due_date,
        tags=list(tags),
    )
    
    storage.add(task)
    console.print(f"[green]✅ 任务已添加:{task.id} - {task.title}[/green]")


@cli.command()
@click.option("--status", "-s", type=click.Choice(["pending", "in_progress", "completed", "cancelled"]),
              help="按状态筛选")
@click.option("--priority", "-p", type=click.Choice(["low", "medium", "high", "urgent"]),
              help="按优先级筛选")
@click.option("--tag", "-t", help="按标签筛选")
def list(status, priority, tag):
    """查看任务列表"""
    tasks = storage.get_all()
    
    # 筛选
    if status:
        tasks = [t for t in tasks if t.status.value == status]
    if priority:
        tasks = [t for t in tasks if t.priority.value == priority]
    if tag:
        tasks = [t for t in tasks if tag in t.tags]
    
    if not tasks:
        console.print("[yellow]📭 暂无任务[/yellow]")
        return
    
    # 创建表格
    table = Table(title="📋 任务列表")
    table.add_column("ID", style="cyan")
    table.add_column("标题", style="bold")
    table.add_column("优先级", justify="center")
    table.add_column("状态", justify="center")
    table.add_column("截止日期", justify="center")
    table.add_column("标签")
    
    for task in tasks:
        table.add_row(
            task.id,
            task.title[:30] + "..." if len(task.title) > 30 else task.title,
            format_priority(task.priority),
            format_status(task.status),
            format_due_date(task.due_date),
            ", ".join(task.tags) if task.tags else "-",
        )
    
    console.print(table)


@cli.command()
@click.argument("task_id")
def complete(task_id):
    """标记任务为已完成"""
    task = storage.get_by_id(task_id)
    if not task:
        console.print(f"[red]❌ 未找到任务:{task_id}[/red]")
        return
    
    task.status = TaskStatus.COMPLETED
    storage.update(task)
    console.print(f"[green]✅ 任务已完成:{task.title}[/green]")


@cli.command()
@click.argument("task_id")
@click.option("--title", "-t", help="新标题")
@click.option("--description", "-d", help="新描述")
@click.option("--priority", "-p", type=click.Choice(["low", "medium", "high", "urgent"]),
              help="新优先级")
def update(task_id, title, description, priority):
    """更新任务信息"""
    task = storage.get_by_id(task_id)
    if not task:
        console.print(f"[red]❌ 未找到任务:{task_id}[/red]")
        return
    
    if title:
        task.title = title
    if description:
        task.description = description
    if priority:
        task.priority = Priority(priority)
    
    storage.update(task)
    console.print(f"[green]✅ 任务已更新:{task.title}[/green]")


@cli.command()
@click.argument("task_id")
@click.confirmation_option(prompt="确定要删除这个任务吗?")
def delete(task_id):
    """删除任务"""
    if storage.delete(task_id):
        console.print(f"[green]✅ 任务已删除:{task_id}[/green]")
    else:
        console.print(f"[red]❌ 未找到任务:{task_id}[/red]")


@cli.command()
def stats():
    """查看任务统计信息"""
    tasks = storage.get_all()
    
    total = len(tasks)
    completed = len([t for t in tasks if t.status == TaskStatus.COMPLETED])
    pending = len([t for t in tasks if t.status == TaskStatus.PENDING])
    in_progress = len([t for t in tasks if t.status == TaskStatus.IN_PROGRESS])
    
    # 按优先级统计
    urgent = len([t for t in tasks if t.priority == Priority.URGENT])
    high = len([t for t in tasks if t.priority == Priority.HIGH])
    
    panel = Panel(
        f"""[bold]📊 任务统计[/bold]

总任务数:[cyan]{total}[/cyan]
已完成:[green]{completed}[/green]
进行中:[yellow]{in_progress}[/yellow]
待处理:[blue]{pending}[/blue]

紧急任务:[red]{urgent}[/red]
高优先级:[orange]{high}[/orange]

完成率:[green]{completed/total*100:.1f}%[/green] (如有任务)
""",
        title="📈 统计面板",
    )
    
    console.print(panel)


if __name__ == "__main__":
    cli()

4. 格式化工具(utils/formatters.py)

Python
from rich.text import Text
from datetime import datetime
from typing import Optional

from ..models import Priority, TaskStatus


def format_priority(priority: Priority) -> Text:
    """格式化优先级显示"""
    icons = {
        Priority.LOW: "⚪",
        Priority.MEDIUM: "🔵",
        Priority.HIGH: "🟠",
        Priority.URGENT: "🔴",
    }
    return Text(f"{icons[priority]} {priority.value.upper()}")


def format_status(status: TaskStatus) -> Text:
    """格式化状态显示"""
    icons = {
        TaskStatus.PENDING: "⏳",
        TaskStatus.IN_PROGRESS: "🔄",
        TaskStatus.COMPLETED: "✅",
        TaskStatus.CANCELLED: "❌",
    }
    status_text = {
        TaskStatus.PENDING: "待处理",
        TaskStatus.IN_PROGRESS: "进行中",
        TaskStatus.COMPLETED: "已完成",
        TaskStatus.CANCELLED: "已取消",
    }
    return Text(f"{icons[status]} {status_text[status]}")


def format_due_date(due_date: Optional[datetime]) -> str:
    """格式化截止日期显示"""
    if not due_date:
        return "-"
    
    today = datetime.now()
    days_left = (due_date - today).days
    
    if days_left < 0:
        return f"[red]过期 {abs(days_left)} 天[/red]"
    elif days_left == 0:
        return "[red]今天[/red]"
    elif days_left == 1:
        return "[orange]明天[/orange]"
    elif days_left <= 7:
        return f"[yellow]{days_left} 天[/yellow]"
    else:
        return due_date.strftime("%Y-%m-%d")

运行步骤

1. 环境准备

确保已安装 Python 3.9 或更高版本:

Bash
python --version

2. 克隆项目

Bash
git clone https://github.com/yourusername/task-manager-cli.git
cd task-manager-cli

3. 创建虚拟环境

Bash
# macOS/Linux
python -m venv venv
source venv/bin/activate

# Windows
python -m venv venv
venv\Scripts\activate

4. 安装依赖

Bash
pip install -r requirements.txt

5. 安装为命令行工具

Bash
pip install -e .

6. 开始使用

Bash
# 查看帮助
task --help

# 添加任务
task add "完成项目报告" -d "撰写 Q4 项目总结报告" -p high --due 2026-03-30 -t 工作 -t 报告

# 查看任务列表
task list

# 按状态筛选
task list -s pending

# 标记任务完成
task complete abc123

# 更新任务
task update abc123 -p urgent

# 查看统计
task stats

# 删除任务
task delete abc123

7. 数据位置

任务数据默认存储在用户主目录下:

  • macOS/Linux: INLINE_CODE_0
  • Windows: INLINE_CODE_1

备份文件存储在:INLINE_CODE_2

项目扩展建议

完成基础版本后,你可以考虑以下扩展方向:

  1. 任务依赖:支持设置任务之间的依赖关系
  2. 重复任务:支持每日/每周/每月重复的任务
  3. 时间追踪:记录每个任务的实际耗时
  4. 云同步:使用 Git 或云存储同步任务数据
  5. GUI 界面:使用 tkinter 或 PyQt 添加图形界面
  6. 移动端:使用 Kivy 或 BeeWare 开发移动应用
  7. API 服务:将核心功能封装为 REST API
  8. 插件系统:支持第三方插件扩展功能

总结

通过这个项目,你不仅获得了一个实用的个人任务管理工具,还掌握了以下核心技能:

  • Python 命令行应用开发
  • 数据模型设计与序列化
  • 文件存储与数据持久化
  • 用户交互与输出美化
  • 项目结构与代码组织

这个项目可以作为你学习 Python 开发的起点,也可以作为实际使用的效率工具。更重要的是,它展示了如何将一个想法转化为可运行的软件产品。

现在就开始动手吧,打造属于你自己的效率利器!

分享到:

如果这篇文章对你有帮助,欢迎请作者喝杯咖啡 ☕

加载评论中...