This commit is contained in:
2025-08-24 13:01:09 +08:00
parent 61e51ad014
commit f028913eb8
36 changed files with 10420 additions and 70 deletions

View File

@@ -1,31 +1,117 @@
# 待办事项列表项目
一个命令行待办事项管理程序,支持添加、删除、标记完成等功能
这是一个命令行待办事项管理程序,演示了 Go 语言在数据持久化、文件操作和用户交互方面的应用
## 功能特性
- 添加新的待办事项
- 查看所有待办事项
- 标记事项为完成
- 删除待办事项
- 数据持久化(保存到文件
## 项目特性
- 添加、删除、修改待办事项
- 标记任务完成状态
- 按优先级和状态筛选任务
- 数据持久化到 JSON 文件
- 彩色命令行界面
- 任务搜索功能
- 统计信息显示
## 项目结构
```
02-todo-list/
├── README.md # 项目说明文档
├── main.go # 主程序入口
├── todo/ # 待办事项核心包
│ ├── todo.go # 待办事项数据结构
│ ├── manager.go # 任务管理器
│ └── storage.go # 数据存储
├── ui/ # 用户界面包
│ ├── cli.go # 命令行界面
│ └── colors.go # 颜色输出
├── data/ # 数据文件目录
│ └── todos.json # 任务数据文件
└── todo_test.go # 测试文件
```
## 运行方法
```bash
cd 02-todo-list
# 进入项目目录
cd 10-projects/02-todo-list
# 运行程序
go run main.go
# 或者编译后运行
go build -o todo main.go
./todo
```
## 使用示例
```bash
# 添加待办事项
go run main.go add "学习 Go 语言"
# 查看所有事项
go run main.go list
```
=== 待办事项管理器 ===
# 标记完成
go run main.go complete 1
命令列表:
add <任务> - 添加新任务
list - 显示所有任务
done <ID> - 标记任务完成
delete <ID> - 删除任务
edit <ID> <新内容> - 编辑任务
search <关键词> - 搜索任务
stats - 显示统计信息
help - 显示帮助
quit - 退出程序
# 删除事项
go run main.go delete 1
```
> add 学习Go语言
✅ 任务已添加: 学习Go语言 (ID: 1)
> add 完成项目文档
✅ 任务已添加: 完成项目文档 (ID: 2)
> list
📋 待办事项列表:
[1] ⏳ 学习Go语言 (优先级: 中)
[2] ⏳ 完成项目文档 (优先级: 中)
> done 1
✅ 任务已完成: 学习Go语言
> list
📋 待办事项列表:
[1] ✅ 学习Go语言 (优先级: 中)
[2] ⏳ 完成项目文档 (优先级: 中)
> stats
📊 统计信息:
总任务数: 2
已完成: 1
待完成: 1
完成率: 50.0%
```
## 学习要点
这个项目综合运用了以下 Go 语言特性:
1. **结构体和方法**: 定义任务和管理器结构体
2. **接口设计**: 定义存储和界面接口
3. **JSON 处理**: 数据序列化和反序列化
4. **文件操作**: 读写 JSON 文件进行数据持久化
5. **错误处理**: 完善的错误处理机制
6. **字符串处理**: 命令解析和文本处理
7. **切片操作**: 任务列表的增删改查
8. **时间处理**: 任务创建和修改时间
9. **包管理**: 多包项目结构
10. **用户交互**: 命令行输入输出
11. **测试**: 单元测试和集成测试
## 扩展建议
1. 添加任务分类和标签功能
2. 支持任务截止日期和提醒
3. 实现任务导入导出功能
4. 添加任务优先级排序
5. 支持批量操作
6. 实现 Web 界面
7. 添加任务统计图表
8. 支持多用户管理
9. 集成云端同步
10. 添加任务模板功能

View File

@@ -0,0 +1,2 @@
# 这个文件用于保持 data 目录在 git 中被跟踪
# 实际的 todos.json 文件会在程序运行时自动创建

View File

@@ -0,0 +1,275 @@
/*
main.go - 待办事项列表主程序
这是一个命令行待办事项管理程序,演示了 Go 语言的综合应用
*/
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"./todo"
"./ui"
)
func main() {
// 创建任务管理器
manager, err := todo.NewManager("data/todos.json")
if err != nil {
fmt.Printf("❌ 初始化失败: %v\n", err)
os.Exit(1)
}
// 加载现有数据
if err := manager.Load(); err != nil {
fmt.Printf("⚠️ 加载数据失败: %v\n", err)
}
// 创建命令行界面
cli := ui.NewCLI()
scanner := bufio.NewScanner(os.Stdin)
// 显示欢迎信息
cli.ShowWelcome()
// 主循环
for {
fmt.Print("> ")
// 读取用户输入
if !scanner.Scan() {
break
}
input := strings.TrimSpace(scanner.Text())
if input == "" {
continue
}
// 解析命令
parts := strings.Fields(input)
command := strings.ToLower(parts[0])
// 执行命令
switch command {
case "add":
handleAdd(manager, cli, parts[1:])
case "list", "ls":
handleList(manager, cli, parts[1:])
case "done", "complete":
handleDone(manager, cli, parts[1:])
case "delete", "del", "rm":
handleDelete(manager, cli, parts[1:])
case "edit", "update":
handleEdit(manager, cli, parts[1:])
case "search", "find":
handleSearch(manager, cli, parts[1:])
case "stats", "statistics":
handleStats(manager, cli)
case "help", "h":
cli.ShowHelp()
case "quit", "exit", "q":
handleQuit(manager, cli)
return
case "clear":
cli.Clear()
default:
cli.ShowError(fmt.Sprintf("未知命令: %s", command))
cli.ShowInfo("输入 'help' 查看可用命令")
}
}
// 检查扫描器错误
if err := scanner.Err(); err != nil {
fmt.Printf("❌ 读取输入时发生错误: %v\n", err)
}
}
// handleAdd 处理添加任务命令
func handleAdd(manager *todo.Manager, cli *ui.CLI, args []string) {
if len(args) == 0 {
cli.ShowError("请提供任务内容")
cli.ShowInfo("用法: add <任务内容>")
return
}
content := strings.Join(args, " ")
task, err := manager.AddTask(content)
if err != nil {
cli.ShowError(fmt.Sprintf("添加任务失败: %v", err))
return
}
cli.ShowSuccess(fmt.Sprintf("任务已添加: %s (ID: %d)", task.Content, task.ID))
// 保存数据
if err := manager.Save(); err != nil {
cli.ShowError(fmt.Sprintf("保存数据失败: %v", err))
}
}
// handleList 处理列出任务命令
func handleList(manager *todo.Manager, cli *ui.CLI, args []string) {
var filter string
if len(args) > 0 {
filter = strings.ToLower(args[0])
}
tasks := manager.GetTasks()
// 应用过滤器
var filteredTasks []todo.Task
switch filter {
case "done", "completed":
for _, task := range tasks {
if task.Completed {
filteredTasks = append(filteredTasks, task)
}
}
case "pending", "todo":
for _, task := range tasks {
if !task.Completed {
filteredTasks = append(filteredTasks, task)
}
}
default:
filteredTasks = tasks
}
if len(filteredTasks) == 0 {
if filter != "" {
cli.ShowInfo(fmt.Sprintf("没有找到 %s 状态的任务", filter))
} else {
cli.ShowInfo("暂无任务")
}
return
}
cli.ShowTaskList(filteredTasks)
}
// handleDone 处理完成任务命令
func handleDone(manager *todo.Manager, cli *ui.CLI, args []string) {
if len(args) == 0 {
cli.ShowError("请提供任务ID")
cli.ShowInfo("用法: done <任务ID>")
return
}
id, err := strconv.Atoi(args[0])
if err != nil {
cli.ShowError("无效的任务ID")
return
}
task, err := manager.CompleteTask(id)
if err != nil {
cli.ShowError(fmt.Sprintf("完成任务失败: %v", err))
return
}
cli.ShowSuccess(fmt.Sprintf("任务已完成: %s", task.Content))
// 保存数据
if err := manager.Save(); err != nil {
cli.ShowError(fmt.Sprintf("保存数据失败: %v", err))
}
}
// handleDelete 处理删除任务命令
func handleDelete(manager *todo.Manager, cli *ui.CLI, args []string) {
if len(args) == 0 {
cli.ShowError("请提供任务ID")
cli.ShowInfo("用法: delete <任务ID>")
return
}
id, err := strconv.Atoi(args[0])
if err != nil {
cli.ShowError("无效的任务ID")
return
}
task, err := manager.DeleteTask(id)
if err != nil {
cli.ShowError(fmt.Sprintf("删除任务失败: %v", err))
return
}
cli.ShowSuccess(fmt.Sprintf("任务已删除: %s", task.Content))
// 保存数据
if err := manager.Save(); err != nil {
cli.ShowError(fmt.Sprintf("保存数据失败: %v", err))
}
}
// handleEdit 处理编辑任务命令
func handleEdit(manager *todo.Manager, cli *ui.CLI, args []string) {
if len(args) < 2 {
cli.ShowError("请提供任务ID和新内容")
cli.ShowInfo("用法: edit <任务ID> <新内容>")
return
}
id, err := strconv.Atoi(args[0])
if err != nil {
cli.ShowError("无效的任务ID")
return
}
newContent := strings.Join(args[1:], " ")
task, err := manager.UpdateTask(id, newContent)
if err != nil {
cli.ShowError(fmt.Sprintf("编辑任务失败: %v", err))
return
}
cli.ShowSuccess(fmt.Sprintf("任务已更新: %s", task.Content))
// 保存数据
if err := manager.Save(); err != nil {
cli.ShowError(fmt.Sprintf("保存数据失败: %v", err))
}
}
// handleSearch 处理搜索任务命令
func handleSearch(manager *todo.Manager, cli *ui.CLI, args []string) {
if len(args) == 0 {
cli.ShowError("请提供搜索关键词")
cli.ShowInfo("用法: search <关键词>")
return
}
keyword := strings.Join(args, " ")
tasks := manager.SearchTasks(keyword)
if len(tasks) == 0 {
cli.ShowInfo(fmt.Sprintf("没有找到包含 '%s' 的任务", keyword))
return
}
cli.ShowInfo(fmt.Sprintf("搜索结果 (关键词: %s):", keyword))
cli.ShowTaskList(tasks)
}
// handleStats 处理统计信息命令
func handleStats(manager *todo.Manager, cli *ui.CLI) {
stats := manager.GetStatistics()
cli.ShowStatistics(stats)
}
// handleQuit 处理退出命令
func handleQuit(manager *todo.Manager, cli *ui.CLI) {
// 保存数据
if err := manager.Save(); err != nil {
cli.ShowError(fmt.Sprintf("保存数据失败: %v", err))
}
cli.ShowInfo("数据已保存")
cli.ShowSuccess("再见!")
}

View File

@@ -0,0 +1,427 @@
/*
manager.go - 任务管理器
实现了任务的增删改查和业务逻辑
*/
package todo
import (
"fmt"
"sort"
"strings"
"time"
)
// Statistics 统计信息
type Statistics struct {
Total int `json:"total"` // 总任务数
Completed int `json:"completed"` // 已完成任务数
Pending int `json:"pending"` // 待完成任务数
CompletionRate float64 `json:"completion_rate"` // 完成率
ByPriority map[string]int `json:"by_priority"` // 按优先级统计
ByStatus map[string]int `json:"by_status"` // 按状态统计
AverageCompletionTime time.Duration `json:"average_completion_time"` // 平均完成时间
}
// Manager 任务管理器
type Manager struct {
tasks []Task
nextID int
storage Storage
}
// NewManager 创建新的任务管理器
func NewManager(dataFile string) (*Manager, error) {
storage, err := NewJSONStorage(dataFile)
if err != nil {
return nil, fmt.Errorf("创建存储失败: %v", err)
}
return &Manager{
tasks: make([]Task, 0),
nextID: 1,
storage: storage,
}, nil
}
// Load 从存储加载任务
func (m *Manager) Load() error {
tasks, err := m.storage.Load()
if err != nil {
return fmt.Errorf("加载任务失败: %v", err)
}
m.tasks = tasks
// 更新下一个ID
maxID := 0
for _, task := range m.tasks {
if task.ID > maxID {
maxID = task.ID
}
}
m.nextID = maxID + 1
return nil
}
// Save 保存任务到存储
func (m *Manager) Save() error {
return m.storage.Save(m.tasks)
}
// AddTask 添加新任务
func (m *Manager) AddTask(content string) (*Task, error) {
content = strings.TrimSpace(content)
if content == "" {
return nil, fmt.Errorf("任务内容不能为空")
}
task := NewTask(m.nextID, content)
m.tasks = append(m.tasks, *task)
m.nextID++
return task, nil
}
// GetTasks 获取所有任务
func (m *Manager) GetTasks() []Task {
// 返回副本以防止外部修改
tasks := make([]Task, len(m.tasks))
copy(tasks, m.tasks)
// 排序任务
sort.Sort(TaskList(tasks))
return tasks
}
// GetTask 根据ID获取任务
func (m *Manager) GetTask(id int) (*Task, error) {
for i, task := range m.tasks {
if task.ID == id {
return &m.tasks[i], nil
}
}
return nil, fmt.Errorf("任务不存在: ID %d", id)
}
// UpdateTask 更新任务内容
func (m *Manager) UpdateTask(id int, content string) (*Task, error) {
content = strings.TrimSpace(content)
if content == "" {
return nil, fmt.Errorf("任务内容不能为空")
}
task, err := m.GetTask(id)
if err != nil {
return nil, err
}
task.Update(content)
return task, nil
}
// CompleteTask 标记任务完成
func (m *Manager) CompleteTask(id int) (*Task, error) {
task, err := m.GetTask(id)
if err != nil {
return nil, err
}
if task.Completed {
return nil, fmt.Errorf("任务已经完成")
}
task.Complete()
return task, nil
}
// UncompleteTask 标记任务未完成
func (m *Manager) UncompleteTask(id int) (*Task, error) {
task, err := m.GetTask(id)
if err != nil {
return nil, err
}
if !task.Completed {
return nil, fmt.Errorf("任务尚未完成")
}
task.Uncomplete()
return task, nil
}
// DeleteTask 删除任务
func (m *Manager) DeleteTask(id int) (*Task, error) {
for i, task := range m.tasks {
if task.ID == id {
// 创建任务副本用于返回
deletedTask := task
// 从切片中删除任务
m.tasks = append(m.tasks[:i], m.tasks[i+1:]...)
return &deletedTask, nil
}
}
return nil, fmt.Errorf("任务不存在: ID %d", id)
}
// SearchTasks 搜索任务
func (m *Manager) SearchTasks(keyword string) []Task {
keyword = strings.TrimSpace(keyword)
if keyword == "" {
return []Task{}
}
var results []Task
for _, task := range m.tasks {
if m.matchesKeyword(task, keyword) {
results = append(results, task)
}
}
// 排序结果
sort.Sort(TaskList(results))
return results
}
// matchesKeyword 检查任务是否匹配关键词
func (m *Manager) matchesKeyword(task Task, keyword string) bool {
keyword = strings.ToLower(keyword)
// 检查任务内容
if strings.Contains(strings.ToLower(task.Content), keyword) {
return true
}
// 检查任务描述
if strings.Contains(strings.ToLower(task.Description), keyword) {
return true
}
// 检查标签
for _, tag := range task.Tags {
if strings.Contains(strings.ToLower(tag), keyword) {
return true
}
}
return false
}
// GetTasksByStatus 根据状态获取任务
func (m *Manager) GetTasksByStatus(completed bool) []Task {
var results []Task
for _, task := range m.tasks {
if task.Completed == completed {
results = append(results, task)
}
}
sort.Sort(TaskList(results))
return results
}
// GetTasksByPriority 根据优先级获取任务
func (m *Manager) GetTasksByPriority(priority Priority) []Task {
var results []Task
for _, task := range m.tasks {
if task.Priority == priority {
results = append(results, task)
}
}
sort.Sort(TaskList(results))
return results
}
// SetTaskPriority 设置任务优先级
func (m *Manager) SetTaskPriority(id int, priority Priority) (*Task, error) {
task, err := m.GetTask(id)
if err != nil {
return nil, err
}
task.SetPriority(priority)
return task, nil
}
// AddTaskTag 为任务添加标签
func (m *Manager) AddTaskTag(id int, tag string) (*Task, error) {
task, err := m.GetTask(id)
if err != nil {
return nil, err
}
task.AddTag(tag)
return task, nil
}
// RemoveTaskTag 移除任务标签
func (m *Manager) RemoveTaskTag(id int, tag string) (*Task, error) {
task, err := m.GetTask(id)
if err != nil {
return nil, err
}
task.RemoveTag(tag)
return task, nil
}
// SetTaskDescription 设置任务描述
func (m *Manager) SetTaskDescription(id int, description string) (*Task, error) {
task, err := m.GetTask(id)
if err != nil {
return nil, err
}
task.SetDescription(description)
return task, nil
}
// GetStatistics 获取统计信息
func (m *Manager) GetStatistics() Statistics {
stats := Statistics{
Total: len(m.tasks),
Completed: 0,
Pending: 0,
ByPriority: make(map[string]int),
ByStatus: make(map[string]int),
}
var totalCompletionTime time.Duration
completedCount := 0
for _, task := range m.tasks {
// 统计完成状态
if task.Completed {
stats.Completed++
if task.CompletedAt != nil {
totalCompletionTime += task.CompletionTime()
completedCount++
}
} else {
stats.Pending++
}
// 统计优先级
priorityStr := task.Priority.String()
stats.ByPriority[priorityStr]++
// 统计状态
if task.Completed {
stats.ByStatus["已完成"]++
} else {
stats.ByStatus["待完成"]++
}
}
// 计算完成率
if stats.Total > 0 {
stats.CompletionRate = float64(stats.Completed) / float64(stats.Total) * 100
}
// 计算平均完成时间
if completedCount > 0 {
stats.AverageCompletionTime = totalCompletionTime / time.Duration(completedCount)
}
return stats
}
// ClearCompleted 清除所有已完成的任务
func (m *Manager) ClearCompleted() int {
var remaining []Task
clearedCount := 0
for _, task := range m.tasks {
if task.Completed {
clearedCount++
} else {
remaining = append(remaining, task)
}
}
m.tasks = remaining
return clearedCount
}
// ClearAll 清除所有任务
func (m *Manager) ClearAll() int {
count := len(m.tasks)
m.tasks = make([]Task, 0)
m.nextID = 1
return count
}
// GetTaskCount 获取任务数量
func (m *Manager) GetTaskCount() int {
return len(m.tasks)
}
// GetCompletedCount 获取已完成任务数量
func (m *Manager) GetCompletedCount() int {
count := 0
for _, task := range m.tasks {
if task.Completed {
count++
}
}
return count
}
// GetPendingCount 获取待完成任务数量
func (m *Manager) GetPendingCount() int {
count := 0
for _, task := range m.tasks {
if !task.Completed {
count++
}
}
return count
}
// ValidateAllTasks 验证所有任务的有效性
func (m *Manager) ValidateAllTasks() []error {
var errors []error
for i, task := range m.tasks {
if err := task.Validate(); err != nil {
errors = append(errors, fmt.Errorf("任务 %d: %v", i, err))
}
}
return errors
}
// ExportTasks 导出任务(可以扩展为不同格式)
func (m *Manager) ExportTasks() []Task {
tasks := make([]Task, len(m.tasks))
copy(tasks, m.tasks)
return tasks
}
// ImportTasks 导入任务
func (m *Manager) ImportTasks(tasks []Task) error {
// 验证导入的任务
for i, task := range tasks {
if err := task.Validate(); err != nil {
return fmt.Errorf("导入任务 %d 验证失败: %v", i, err)
}
}
// 更新任务列表
m.tasks = append(m.tasks, tasks...)
// 更新下一个ID
maxID := m.nextID - 1
for _, task := range tasks {
if task.ID > maxID {
maxID = task.ID
}
}
m.nextID = maxID + 1
return nil
}

View File

@@ -0,0 +1,308 @@
/*
storage.go - 数据存储
实现了任务数据的持久化存储
*/
package todo
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
// Storage 存储接口
type Storage interface {
Load() ([]Task, error)
Save(tasks []Task) error
}
// JSONStorage JSON文件存储实现
type JSONStorage struct {
filePath string
}
// NewJSONStorage 创建新的JSON存储
func NewJSONStorage(filePath string) (*JSONStorage, error) {
if filePath == "" {
return nil, fmt.Errorf("文件路径不能为空")
}
// 确保目录存在
dir := filepath.Dir(filePath)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("创建目录失败: %v", err)
}
return &JSONStorage{
filePath: filePath,
}, nil
}
// Load 从JSON文件加载任务
func (s *JSONStorage) Load() ([]Task, error) {
// 检查文件是否存在
if _, err := os.Stat(s.filePath); os.IsNotExist(err) {
// 文件不存在,返回空任务列表
return []Task{}, nil
}
// 读取文件内容
data, err := ioutil.ReadFile(s.filePath)
if err != nil {
return nil, fmt.Errorf("读取文件失败: %v", err)
}
// 如果文件为空,返回空任务列表
if len(data) == 0 {
return []Task{}, nil
}
// 解析JSON数据
var tasks []Task
if err := json.Unmarshal(data, &tasks); err != nil {
return nil, fmt.Errorf("解析JSON数据失败: %v", err)
}
return tasks, nil
}
// Save 保存任务到JSON文件
func (s *JSONStorage) Save(tasks []Task) error {
// 序列化任务数据
data, err := json.MarshalIndent(tasks, "", " ")
if err != nil {
return fmt.Errorf("序列化任务数据失败: %v", err)
}
// 写入文件
if err := ioutil.WriteFile(s.filePath, data, 0644); err != nil {
return fmt.Errorf("写入文件失败: %v", err)
}
return nil
}
// GetFilePath 获取文件路径
func (s *JSONStorage) GetFilePath() string {
return s.filePath
}
// FileExists 检查文件是否存在
func (s *JSONStorage) FileExists() bool {
_, err := os.Stat(s.filePath)
return !os.IsNotExist(err)
}
// GetFileSize 获取文件大小
func (s *JSONStorage) GetFileSize() (int64, error) {
info, err := os.Stat(s.filePath)
if err != nil {
return 0, err
}
return info.Size(), nil
}
// Backup 备份数据文件
func (s *JSONStorage) Backup(backupPath string) error {
if !s.FileExists() {
return fmt.Errorf("源文件不存在")
}
// 读取源文件
data, err := ioutil.ReadFile(s.filePath)
if err != nil {
return fmt.Errorf("读取源文件失败: %v", err)
}
// 确保备份目录存在
dir := filepath.Dir(backupPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("创建备份目录失败: %v", err)
}
// 写入备份文件
if err := ioutil.WriteFile(backupPath, data, 0644); err != nil {
return fmt.Errorf("写入备份文件失败: %v", err)
}
return nil
}
// Restore 从备份恢复数据
func (s *JSONStorage) Restore(backupPath string) error {
// 检查备份文件是否存在
if _, err := os.Stat(backupPath); os.IsNotExist(err) {
return fmt.Errorf("备份文件不存在")
}
// 读取备份文件
data, err := ioutil.ReadFile(backupPath)
if err != nil {
return fmt.Errorf("读取备份文件失败: %v", err)
}
// 验证备份数据
var tasks []Task
if err := json.Unmarshal(data, &tasks); err != nil {
return fmt.Errorf("备份数据格式无效: %v", err)
}
// 写入主文件
if err := ioutil.WriteFile(s.filePath, data, 0644); err != nil {
return fmt.Errorf("恢复数据失败: %v", err)
}
return nil
}
// Clear 清空数据文件
func (s *JSONStorage) Clear() error {
return s.Save([]Task{})
}
// MemoryStorage 内存存储实现(用于测试)
type MemoryStorage struct {
tasks []Task
}
// NewMemoryStorage 创建新的内存存储
func NewMemoryStorage() *MemoryStorage {
return &MemoryStorage{
tasks: make([]Task, 0),
}
}
// Load 从内存加载任务
func (s *MemoryStorage) Load() ([]Task, error) {
// 返回任务副本
tasks := make([]Task, len(s.tasks))
copy(tasks, s.tasks)
return tasks, nil
}
// Save 保存任务到内存
func (s *MemoryStorage) Save(tasks []Task) error {
// 保存任务副本
s.tasks = make([]Task, len(tasks))
copy(s.tasks, tasks)
return nil
}
// Clear 清空内存中的任务
func (s *MemoryStorage) Clear() {
s.tasks = make([]Task, 0)
}
// GetTasks 获取内存中的任务(用于测试)
func (s *MemoryStorage) GetTasks() []Task {
tasks := make([]Task, len(s.tasks))
copy(tasks, s.tasks)
return tasks
}
// CSVStorage CSV文件存储实现扩展功能
type CSVStorage struct {
filePath string
}
// NewCSVStorage 创建新的CSV存储
func NewCSVStorage(filePath string) (*CSVStorage, error) {
if filePath == "" {
return nil, fmt.Errorf("文件路径不能为空")
}
// 确保目录存在
dir := filepath.Dir(filePath)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("创建目录失败: %v", err)
}
return &CSVStorage{
filePath: filePath,
}, nil
}
// Load 从CSV文件加载任务简化实现
func (s *CSVStorage) Load() ([]Task, error) {
// 这里可以实现CSV解析逻辑
// 为了简化,暂时返回空列表
return []Task{}, nil
}
// Save 保存任务到CSV文件简化实现
func (s *CSVStorage) Save(tasks []Task) error {
// 这里可以实现CSV写入逻辑
// 为了简化,暂时不做任何操作
return nil
}
// StorageManager 存储管理器
type StorageManager struct {
storages map[string]Storage
current string
}
// NewStorageManager 创建新的存储管理器
func NewStorageManager() *StorageManager {
return &StorageManager{
storages: make(map[string]Storage),
}
}
// AddStorage 添加存储实现
func (sm *StorageManager) AddStorage(name string, storage Storage) {
sm.storages[name] = storage
}
// SetCurrent 设置当前使用的存储
func (sm *StorageManager) SetCurrent(name string) error {
if _, exists := sm.storages[name]; !exists {
return fmt.Errorf("存储 '%s' 不存在", name)
}
sm.current = name
return nil
}
// GetCurrent 获取当前存储
func (sm *StorageManager) GetCurrent() (Storage, error) {
if sm.current == "" {
return nil, fmt.Errorf("未设置当前存储")
}
storage, exists := sm.storages[sm.current]
if !exists {
return nil, fmt.Errorf("当前存储 '%s' 不存在", sm.current)
}
return storage, nil
}
// ListStorages 列出所有可用的存储
func (sm *StorageManager) ListStorages() []string {
var names []string
for name := range sm.storages {
names = append(names, name)
}
return names
}
// Load 使用当前存储加载数据
func (sm *StorageManager) Load() ([]Task, error) {
storage, err := sm.GetCurrent()
if err != nil {
return nil, err
}
return storage.Load()
}
// Save 使用当前存储保存数据
func (sm *StorageManager) Save(tasks []Task) error {
storage, err := sm.GetCurrent()
if err != nil {
return err
}
return storage.Save(tasks)
}

View File

@@ -0,0 +1,356 @@
/*
todo.go - 待办事项数据结构
定义了任务的基本数据结构和相关方法
*/
package todo
import (
"fmt"
"strings"
"time"
)
// Priority 任务优先级
type Priority int
const (
Low Priority = iota
Medium
High
Urgent
)
// String 返回优先级的字符串表示
func (p Priority) String() string {
switch p {
case Low:
return "低"
case Medium:
return "中"
case High:
return "高"
case Urgent:
return "紧急"
default:
return "未知"
}
}
// Task 表示一个待办事项
type Task struct {
ID int `json:"id"` // 任务ID
Content string `json:"content"` // 任务内容
Completed bool `json:"completed"` // 是否完成
Priority Priority `json:"priority"` // 优先级
CreatedAt time.Time `json:"created_at"` // 创建时间
UpdatedAt time.Time `json:"updated_at"` // 更新时间
CompletedAt *time.Time `json:"completed_at,omitempty"` // 完成时间
Tags []string `json:"tags,omitempty"` // 标签
Description string `json:"description,omitempty"` // 详细描述
}
// NewTask 创建新的任务
func NewTask(id int, content string) *Task {
now := time.Now()
return &Task{
ID: id,
Content: content,
Completed: false,
Priority: Medium,
CreatedAt: now,
UpdatedAt: now,
Tags: make([]string, 0),
}
}
// Complete 标记任务为完成
func (t *Task) Complete() {
if !t.Completed {
t.Completed = true
now := time.Now()
t.CompletedAt = &now
t.UpdatedAt = now
}
}
// Uncomplete 标记任务为未完成
func (t *Task) Uncomplete() {
if t.Completed {
t.Completed = false
t.CompletedAt = nil
t.UpdatedAt = time.Now()
}
}
// Update 更新任务内容
func (t *Task) Update(content string) {
if content != "" && content != t.Content {
t.Content = content
t.UpdatedAt = time.Now()
}
}
// SetPriority 设置任务优先级
func (t *Task) SetPriority(priority Priority) {
if priority != t.Priority {
t.Priority = priority
t.UpdatedAt = time.Now()
}
}
// AddTag 添加标签
func (t *Task) AddTag(tag string) {
if tag == "" {
return
}
// 检查标签是否已存在
for _, existingTag := range t.Tags {
if existingTag == tag {
return
}
}
t.Tags = append(t.Tags, tag)
t.UpdatedAt = time.Now()
}
// RemoveTag 移除标签
func (t *Task) RemoveTag(tag string) {
for i, existingTag := range t.Tags {
if existingTag == tag {
t.Tags = append(t.Tags[:i], t.Tags[i+1:]...)
t.UpdatedAt = time.Now()
break
}
}
}
// HasTag 检查是否包含指定标签
func (t *Task) HasTag(tag string) bool {
for _, existingTag := range t.Tags {
if existingTag == tag {
return true
}
}
return false
}
// SetDescription 设置任务描述
func (t *Task) SetDescription(description string) {
if description != t.Description {
t.Description = description
t.UpdatedAt = time.Now()
}
}
// String 返回任务的字符串表示
func (t *Task) String() string {
status := "⏳"
if t.Completed {
status = "✅"
}
return fmt.Sprintf("[%d] %s %s (优先级: %s)",
t.ID, status, t.Content, t.Priority)
}
// DetailedString 返回任务的详细字符串表示
func (t *Task) DetailedString() string {
status := "待完成"
if t.Completed {
status = "已完成"
}
result := fmt.Sprintf("任务 #%d\n", t.ID)
result += fmt.Sprintf("内容: %s\n", t.Content)
result += fmt.Sprintf("状态: %s\n", status)
result += fmt.Sprintf("优先级: %s\n", t.Priority)
result += fmt.Sprintf("创建时间: %s\n", t.CreatedAt.Format("2006-01-02 15:04:05"))
result += fmt.Sprintf("更新时间: %s\n", t.UpdatedAt.Format("2006-01-02 15:04:05"))
if t.Completed && t.CompletedAt != nil {
result += fmt.Sprintf("完成时间: %s\n", t.CompletedAt.Format("2006-01-02 15:04:05"))
}
if t.Description != "" {
result += fmt.Sprintf("描述: %s\n", t.Description)
}
if len(t.Tags) > 0 {
result += fmt.Sprintf("标签: %v\n", t.Tags)
}
return result
}
// IsOverdue 检查任务是否过期(如果有截止日期的话)
func (t *Task) IsOverdue() bool {
// 这里可以扩展添加截止日期功能
return false
}
// Age 返回任务的存在时间
func (t *Task) Age() time.Duration {
return time.Since(t.CreatedAt)
}
// CompletionTime 返回任务的完成耗时
func (t *Task) CompletionTime() time.Duration {
if !t.Completed || t.CompletedAt == nil {
return 0
}
return t.CompletedAt.Sub(t.CreatedAt)
}
// Clone 创建任务的副本
func (t *Task) Clone() *Task {
clone := *t
// 深拷贝切片
if len(t.Tags) > 0 {
clone.Tags = make([]string, len(t.Tags))
copy(clone.Tags, t.Tags)
}
// 深拷贝时间指针
if t.CompletedAt != nil {
completedAt := *t.CompletedAt
clone.CompletedAt = &completedAt
}
return &clone
}
// Validate 验证任务数据的有效性
func (t *Task) Validate() error {
if t.ID <= 0 {
return fmt.Errorf("任务ID必须大于0")
}
if t.Content == "" {
return fmt.Errorf("任务内容不能为空")
}
if t.Priority < Low || t.Priority > Urgent {
return fmt.Errorf("无效的优先级")
}
if t.CreatedAt.IsZero() {
return fmt.Errorf("创建时间不能为空")
}
if t.UpdatedAt.IsZero() {
return fmt.Errorf("更新时间不能为空")
}
if t.Completed && t.CompletedAt == nil {
return fmt.Errorf("已完成的任务必须有完成时间")
}
if !t.Completed && t.CompletedAt != nil {
return fmt.Errorf("未完成的任务不应该有完成时间")
}
return nil
}
// TaskList 任务列表类型
type TaskList []Task
// Len 返回任务列表长度
func (tl TaskList) Len() int {
return len(tl)
}
// Less 比较两个任务的顺序(用于排序)
func (tl TaskList) Less(i, j int) bool {
// 首先按完成状态排序(未完成的在前)
if tl[i].Completed != tl[j].Completed {
return !tl[i].Completed
}
// 然后按优先级排序(高优先级在前)
if tl[i].Priority != tl[j].Priority {
return tl[i].Priority > tl[j].Priority
}
// 最后按创建时间排序(新创建的在前)
return tl[i].CreatedAt.After(tl[j].CreatedAt)
}
// Swap 交换两个任务的位置
func (tl TaskList) Swap(i, j int) {
tl[i], tl[j] = tl[j], tl[i]
}
// Filter 过滤任务列表
func (tl TaskList) Filter(predicate func(Task) bool) TaskList {
var filtered TaskList
for _, task := range tl {
if predicate(task) {
filtered = append(filtered, task)
}
}
return filtered
}
// FindByID 根据ID查找任务
func (tl TaskList) FindByID(id int) (*Task, bool) {
for i, task := range tl {
if task.ID == id {
return &tl[i], true
}
}
return nil, false
}
// GetCompleted 获取已完成的任务
func (tl TaskList) GetCompleted() TaskList {
return tl.Filter(func(t Task) bool {
return t.Completed
})
}
// GetPending 获取待完成的任务
func (tl TaskList) GetPending() TaskList {
return tl.Filter(func(t Task) bool {
return !t.Completed
})
}
// GetByPriority 根据优先级获取任务
func (tl TaskList) GetByPriority(priority Priority) TaskList {
return tl.Filter(func(t Task) bool {
return t.Priority == priority
})
}
// Search 搜索包含关键词的任务
func (tl TaskList) Search(keyword string) TaskList {
return tl.Filter(func(t Task) bool {
return contains(t.Content, keyword) ||
contains(t.Description, keyword) ||
containsInTags(t.Tags, keyword)
})
}
// contains 检查字符串是否包含子字符串(忽略大小写)
func contains(s, substr string) bool {
if substr == "" {
return true
}
return len(s) >= len(substr) &&
strings.Contains(strings.ToLower(s), strings.ToLower(substr))
}
// containsInTags 检查标签列表是否包含关键词
func containsInTags(tags []string, keyword string) bool {
for _, tag := range tags {
if contains(tag, keyword) {
return true
}
}
return false
}

View File

@@ -0,0 +1,382 @@
/*
todo_test.go - 待办事项测试文件
测试待办事项管理器的各种功能
*/
package main
import (
"fmt"
"testing"
"time"
"./todo"
)
// TestTaskCreation 测试任务创建
func TestTaskCreation(t *testing.T) {
task := todo.NewTask(1, "测试任务")
if task.ID != 1 {
t.Errorf("期望任务ID为1实际为%d", task.ID)
}
if task.Content != "测试任务" {
t.Errorf("期望任务内容为'测试任务',实际为'%s'", task.Content)
}
if task.Completed {
t.Error("新创建的任务不应该是已完成状态")
}
if task.Priority != todo.Medium {
t.Errorf("期望默认优先级为Medium实际为%v", task.Priority)
}
}
// TestTaskCompletion 测试任务完成
func TestTaskCompletion(t *testing.T) {
task := todo.NewTask(1, "测试任务")
// 完成任务
task.Complete()
if !task.Completed {
t.Error("任务应该是已完成状态")
}
if task.CompletedAt == nil {
t.Error("已完成的任务应该有完成时间")
}
// 再次完成(应该没有变化)
oldCompletedAt := *task.CompletedAt
time.Sleep(1 * time.Millisecond)
task.Complete()
if !task.CompletedAt.Equal(oldCompletedAt) {
t.Error("重复完成任务不应该改变完成时间")
}
}
// TestTaskUpdate 测试任务更新
func TestTaskUpdate(t *testing.T) {
task := todo.NewTask(1, "原始内容")
oldUpdatedAt := task.UpdatedAt
time.Sleep(1 * time.Millisecond)
task.Update("新内容")
if task.Content != "新内容" {
t.Errorf("期望任务内容为'新内容',实际为'%s'", task.Content)
}
if !task.UpdatedAt.After(oldUpdatedAt) {
t.Error("更新任务后,更新时间应该改变")
}
}
// TestTaskPriority 测试任务优先级
func TestTaskPriority(t *testing.T) {
task := todo.NewTask(1, "测试任务")
task.SetPriority(todo.High)
if task.Priority != todo.High {
t.Errorf("期望优先级为High实际为%v", task.Priority)
}
}
// TestTaskTags 测试任务标签
func TestTaskTags(t *testing.T) {
task := todo.NewTask(1, "测试任务")
// 添加标签
task.AddTag("工作")
task.AddTag("重要")
if len(task.Tags) != 2 {
t.Errorf("期望标签数量为2实际为%d", len(task.Tags))
}
if !task.HasTag("工作") {
t.Error("任务应该包含'工作'标签")
}
// 添加重复标签
task.AddTag("工作")
if len(task.Tags) != 2 {
t.Error("添加重复标签不应该增加标签数量")
}
// 移除标签
task.RemoveTag("工作")
if task.HasTag("工作") {
t.Error("移除标签后,任务不应该包含该标签")
}
if len(task.Tags) != 1 {
t.Errorf("移除标签后期望标签数量为1实际为%d", len(task.Tags))
}
}
// TestTaskValidation 测试任务验证
func TestTaskValidation(t *testing.T) {
// 有效任务
validTask := todo.NewTask(1, "有效任务")
if err := validTask.Validate(); err != nil {
t.Errorf("有效任务验证失败: %v", err)
}
// 无效ID
invalidTask := &todo.Task{
ID: 0,
Content: "测试",
}
if err := invalidTask.Validate(); err == nil {
t.Error("ID为0的任务应该验证失败")
}
// 空内容
invalidTask = &todo.Task{
ID: 1,
Content: "",
}
if err := invalidTask.Validate(); err == nil {
t.Error("内容为空的任务应该验证失败")
}
}
// TestManager 测试任务管理器
func TestManager(t *testing.T) {
// 使用内存存储进行测试
storage := todo.NewMemoryStorage()
manager := &todo.Manager{}
// 这里需要修改Manager结构以支持注入存储
// 为了简化测试,我们直接测试基本功能
// 创建临时管理器
tempManager, err := todo.NewManager("test_todos.json")
if err != nil {
t.Fatalf("创建管理器失败: %v", err)
}
// 添加任务
task1, err := tempManager.AddTask("任务1")
if err != nil {
t.Errorf("添加任务失败: %v", err)
}
if task1.ID != 1 {
t.Errorf("期望第一个任务ID为1实际为%d", task1.ID)
}
// 添加更多任务
tempManager.AddTask("任务2")
tempManager.AddTask("任务3")
// 获取所有任务
tasks := tempManager.GetTasks()
if len(tasks) != 3 {
t.Errorf("期望任务数量为3实际为%d", len(tasks))
}
// 完成任务
completedTask, err := tempManager.CompleteTask(1)
if err != nil {
t.Errorf("完成任务失败: %v", err)
}
if !completedTask.Completed {
t.Error("任务应该是已完成状态")
}
// 删除任务
deletedTask, err := tempManager.DeleteTask(2)
if err != nil {
t.Errorf("删除任务失败: %v", err)
}
if deletedTask.ID != 2 {
t.Errorf("期望删除的任务ID为2实际为%d", deletedTask.ID)
}
// 检查剩余任务
tasks = tempManager.GetTasks()
if len(tasks) != 2 {
t.Errorf("删除后期望任务数量为2实际为%d", len(tasks))
}
}
// TestManagerSearch 测试搜索功能
func TestManagerSearch(t *testing.T) {
tempManager, err := todo.NewManager("test_search.json")
if err != nil {
t.Fatalf("创建管理器失败: %v", err)
}
// 添加测试任务
tempManager.AddTask("学习Go语言")
tempManager.AddTask("完成项目文档")
tempManager.AddTask("Go语言练习")
// 搜索包含"Go"的任务
results := tempManager.SearchTasks("Go")
if len(results) != 2 {
t.Errorf("期望搜索结果数量为2实际为%d", len(results))
}
// 搜索不存在的关键词
results = tempManager.SearchTasks("Python")
if len(results) != 0 {
t.Errorf("期望搜索结果数量为0实际为%d", len(results))
}
// 空关键词搜索
results = tempManager.SearchTasks("")
if len(results) != 0 {
t.Errorf("空关键词搜索期望结果数量为0实际为%d", len(results))
}
}
// TestManagerStatistics 测试统计功能
func TestManagerStatistics(t *testing.T) {
tempManager, err := todo.NewManager("test_stats.json")
if err != nil {
t.Fatalf("创建管理器失败: %v", err)
}
// 添加测试任务
tempManager.AddTask("任务1")
tempManager.AddTask("任务2")
tempManager.AddTask("任务3")
// 完成一个任务
tempManager.CompleteTask(1)
// 获取统计信息
stats := tempManager.GetStatistics()
if stats.Total != 3 {
t.Errorf("期望总任务数为3实际为%d", stats.Total)
}
if stats.Completed != 1 {
t.Errorf("期望已完成任务数为1实际为%d", stats.Completed)
}
if stats.Pending != 2 {
t.Errorf("期望待完成任务数为2实际为%d", stats.Pending)
}
expectedRate := float64(1) / float64(3) * 100
if stats.CompletionRate != expectedRate {
t.Errorf("期望完成率为%.1f%%,实际为%.1f%%", expectedRate, stats.CompletionRate)
}
}
// TestMemoryStorage 测试内存存储
func TestMemoryStorage(t *testing.T) {
storage := todo.NewMemoryStorage()
// 创建测试任务
tasks := []todo.Task{
*todo.NewTask(1, "任务1"),
*todo.NewTask(2, "任务2"),
}
// 保存任务
err := storage.Save(tasks)
if err != nil {
t.Errorf("保存任务失败: %v", err)
}
// 加载任务
loadedTasks, err := storage.Load()
if err != nil {
t.Errorf("加载任务失败: %v", err)
}
if len(loadedTasks) != 2 {
t.Errorf("期望加载任务数量为2实际为%d", len(loadedTasks))
}
if loadedTasks[0].Content != "任务1" {
t.Errorf("期望第一个任务内容为'任务1',实际为'%s'", loadedTasks[0].Content)
}
}
// TestPriorityString 测试优先级字符串表示
func TestPriorityString(t *testing.T) {
tests := []struct {
priority todo.Priority
expected string
}{
{todo.Low, "低"},
{todo.Medium, "中"},
{todo.High, "高"},
{todo.Urgent, "紧急"},
}
for _, tt := range tests {
t.Run(tt.expected, func(t *testing.T) {
if tt.priority.String() != tt.expected {
t.Errorf("期望优先级字符串为'%s',实际为'%s'", tt.expected, tt.priority.String())
}
})
}
}
// BenchmarkTaskCreation 任务创建基准测试
func BenchmarkTaskCreation(b *testing.B) {
for i := 0; i < b.N; i++ {
todo.NewTask(i, fmt.Sprintf("任务%d", i))
}
}
// BenchmarkManagerAddTask 管理器添加任务基准测试
func BenchmarkManagerAddTask(b *testing.B) {
manager, err := todo.NewManager("bench_test.json")
if err != nil {
b.Fatalf("创建管理器失败: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
manager.AddTask(fmt.Sprintf("基准测试任务%d", i))
}
}
// BenchmarkManagerSearch 搜索基准测试
func BenchmarkManagerSearch(b *testing.B) {
manager, err := todo.NewManager("bench_search.json")
if err != nil {
b.Fatalf("创建管理器失败: %v", err)
}
// 添加测试数据
for i := 0; i < 1000; i++ {
manager.AddTask(fmt.Sprintf("测试任务%d", i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
manager.SearchTasks("测试")
}
}
// ExampleTask_String 任务字符串表示示例
func ExampleTask_String() {
task := todo.NewTask(1, "学习Go语言")
fmt.Println(task.String())
// Output: [1] ⏳ 学习Go语言 (优先级: 中)
}
// ExampleManager_AddTask 添加任务示例
func ExampleManager_AddTask() {
manager, _ := todo.NewManager("example.json")
task, _ := manager.AddTask("完成项目文档")
fmt.Printf("任务已添加: %s (ID: %d)", task.Content, task.ID)
// Output: 任务已添加: 完成项目文档 (ID: 1)
}

View File

@@ -0,0 +1,315 @@
/*
cli.go - 命令行界面
实现了彩色的命令行用户界面
*/
package ui
import (
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"../todo"
)
// CLI 命令行界面
type CLI struct {
colors *Colors
}
// NewCLI 创建新的命令行界面
func NewCLI() *CLI {
return &CLI{
colors: NewColors(),
}
}
// ShowWelcome 显示欢迎信息
func (c *CLI) ShowWelcome() {
c.Clear()
fmt.Println(c.colors.Cyan("=== 待办事项管理器 ==="))
fmt.Println()
fmt.Println("📝 一个简单而强大的待办事项管理工具")
fmt.Println()
c.ShowHelp()
}
// ShowHelp 显示帮助信息
func (c *CLI) ShowHelp() {
fmt.Println(c.colors.Yellow("命令列表:"))
fmt.Println(" " + c.colors.Green("add <任务>") + " - 添加新任务")
fmt.Println(" " + c.colors.Green("list [状态]") + " - 显示任务列表 (all/done/pending)")
fmt.Println(" " + c.colors.Green("done <ID>") + " - 标记任务完成")
fmt.Println(" " + c.colors.Green("delete <ID>") + " - 删除任务")
fmt.Println(" " + c.colors.Green("edit <ID> <新内容>") + " - 编辑任务")
fmt.Println(" " + c.colors.Green("search <关键词>") + " - 搜索任务")
fmt.Println(" " + c.colors.Green("stats") + " - 显示统计信息")
fmt.Println(" " + c.colors.Green("clear") + " - 清屏")
fmt.Println(" " + c.colors.Green("help") + " - 显示帮助")
fmt.Println(" " + c.colors.Green("quit") + " - 退出程序")
fmt.Println()
}
// ShowTaskList 显示任务列表
func (c *CLI) ShowTaskList(tasks []todo.Task) {
if len(tasks) == 0 {
c.ShowInfo("暂无任务")
return
}
fmt.Println(c.colors.Cyan("📋 待办事项列表:"))
for _, task := range tasks {
c.showTask(task)
}
fmt.Println()
}
// showTask 显示单个任务
func (c *CLI) showTask(task todo.Task) {
var status, statusColor string
if task.Completed {
status = "✅"
statusColor = "green"
} else {
status = "⏳"
statusColor = "yellow"
}
// 根据优先级选择颜色
var priorityColor string
switch task.Priority {
case todo.Urgent:
priorityColor = "red"
case todo.High:
priorityColor = "magenta"
case todo.Medium:
priorityColor = "blue"
case todo.Low:
priorityColor = "cyan"
default:
priorityColor = "white"
}
// 格式化任务内容
content := task.Content
if len(content) > 50 {
content = content[:47] + "..."
}
// 显示任务
fmt.Printf(" [%s] %s %s %s\n",
c.colors.Blue(fmt.Sprintf("%d", task.ID)),
c.colorize(status, statusColor),
c.colorize(content, "white"),
c.colorize(fmt.Sprintf("(优先级: %s)", task.Priority), priorityColor))
// 显示标签
if len(task.Tags) > 0 {
fmt.Printf(" %s %s\n",
c.colors.Gray("标签:"),
c.colors.Cyan(strings.Join(task.Tags, ", ")))
}
// 显示描述
if task.Description != "" {
desc := task.Description
if len(desc) > 60 {
desc = desc[:57] + "..."
}
fmt.Printf(" %s %s\n",
c.colors.Gray("描述:"),
c.colors.White(desc))
}
}
// ShowStatistics 显示统计信息
func (c *CLI) ShowStatistics(stats todo.Statistics) {
fmt.Println(c.colors.Cyan("📊 统计信息:"))
fmt.Printf(" 总任务数: %s\n", c.colors.Blue(fmt.Sprintf("%d", stats.Total)))
fmt.Printf(" 已完成: %s\n", c.colors.Green(fmt.Sprintf("%d", stats.Completed)))
fmt.Printf(" 待完成: %s\n", c.colors.Yellow(fmt.Sprintf("%d", stats.Pending)))
fmt.Printf(" 完成率: %s\n", c.colors.Magenta(fmt.Sprintf("%.1f%%", stats.CompletionRate)))
if len(stats.ByPriority) > 0 {
fmt.Println()
fmt.Println(c.colors.Yellow("按优先级统计:"))
for priority, count := range stats.ByPriority {
var color string
switch priority {
case "紧急":
color = "red"
case "高":
color = "magenta"
case "中":
color = "blue"
case "低":
color = "cyan"
default:
color = "white"
}
fmt.Printf(" %s: %s\n",
c.colorize(priority, color),
c.colors.White(fmt.Sprintf("%d", count)))
}
}
if stats.AverageCompletionTime > 0 {
fmt.Printf("\n 平均完成时间: %s\n",
c.colors.Cyan(stats.AverageCompletionTime.String()))
}
fmt.Println()
}
// ShowSuccess 显示成功消息
func (c *CLI) ShowSuccess(message string) {
fmt.Printf("%s %s\n", c.colors.Green("✅"), message)
}
// ShowError 显示错误消息
func (c *CLI) ShowError(message string) {
fmt.Printf("%s %s\n", c.colors.Red("❌"), c.colors.Red(message))
}
// ShowWarning 显示警告消息
func (c *CLI) ShowWarning(message string) {
fmt.Printf("%s %s\n", c.colors.Yellow("⚠️"), c.colors.Yellow(message))
}
// ShowInfo 显示信息消息
func (c *CLI) ShowInfo(message string) {
fmt.Printf("%s %s\n", c.colors.Blue(""), message)
}
// Clear 清屏
func (c *CLI) Clear() {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "cls")
default:
cmd = exec.Command("clear")
}
cmd.Stdout = os.Stdout
cmd.Run()
}
// colorize 根据颜色名称着色文本
func (c *CLI) colorize(text, color string) string {
switch strings.ToLower(color) {
case "red":
return c.colors.Red(text)
case "green":
return c.colors.Green(text)
case "yellow":
return c.colors.Yellow(text)
case "blue":
return c.colors.Blue(text)
case "magenta":
return c.colors.Magenta(text)
case "cyan":
return c.colors.Cyan(text)
case "white":
return c.colors.White(text)
case "gray":
return c.colors.Gray(text)
default:
return text
}
}
// ShowTaskDetail 显示任务详细信息
func (c *CLI) ShowTaskDetail(task todo.Task) {
fmt.Println(c.colors.Cyan("📋 任务详情:"))
fmt.Println(c.colors.Gray("─────────────────────────────────────"))
fmt.Printf("ID: %s\n", c.colors.Blue(fmt.Sprintf("%d", task.ID)))
fmt.Printf("内容: %s\n", c.colors.White(task.Content))
if task.Completed {
fmt.Printf("状态: %s\n", c.colors.Green("✅ 已完成"))
} else {
fmt.Printf("状态: %s\n", c.colors.Yellow("⏳ 待完成"))
}
var priorityColor string
switch task.Priority {
case todo.Urgent:
priorityColor = "red"
case todo.High:
priorityColor = "magenta"
case todo.Medium:
priorityColor = "blue"
case todo.Low:
priorityColor = "cyan"
}
fmt.Printf("优先级: %s\n", c.colorize(task.Priority.String(), priorityColor))
fmt.Printf("创建时间: %s\n", c.colors.Gray(task.CreatedAt.Format("2006-01-02 15:04:05")))
fmt.Printf("更新时间: %s\n", c.colors.Gray(task.UpdatedAt.Format("2006-01-02 15:04:05")))
if task.Completed && task.CompletedAt != nil {
fmt.Printf("完成时间: %s\n", c.colors.Green(task.CompletedAt.Format("2006-01-02 15:04:05")))
fmt.Printf("完成耗时: %s\n", c.colors.Cyan(task.CompletionTime().String()))
}
if task.Description != "" {
fmt.Printf("描述: %s\n", c.colors.White(task.Description))
}
if len(task.Tags) > 0 {
fmt.Printf("标签: %s\n", c.colors.Cyan(strings.Join(task.Tags, ", ")))
}
fmt.Println(c.colors.Gray("─────────────────────────────────────"))
fmt.Println()
}
// ShowProgress 显示进度条
func (c *CLI) ShowProgress(current, total int, label string) {
if total == 0 {
return
}
percentage := float64(current) / float64(total) * 100
barLength := 30
filledLength := int(float64(barLength) * float64(current) / float64(total))
bar := strings.Repeat("█", filledLength) + strings.Repeat("░", barLength-filledLength)
fmt.Printf("\r%s [%s] %.1f%% (%d/%d)",
label,
c.colors.Green(bar),
percentage,
current,
total)
}
// ConfirmAction 确认操作
func (c *CLI) ConfirmAction(message string) bool {
fmt.Printf("%s %s (y/N): ", c.colors.Yellow("❓"), message)
var response string
fmt.Scanln(&response)
response = strings.ToLower(strings.TrimSpace(response))
return response == "y" || response == "yes"
}
// ShowBanner 显示横幅
func (c *CLI) ShowBanner(text string) {
length := len(text) + 4
border := strings.Repeat("=", length)
fmt.Println(c.colors.Cyan(border))
fmt.Printf("%s %s %s\n",
c.colors.Cyan("="),
c.colors.White(text),
c.colors.Cyan("="))
fmt.Println(c.colors.Cyan(border))
fmt.Println()
}

View File

@@ -0,0 +1,382 @@
/*
colors.go - 颜色输出
实现了命令行的彩色文本输出
*/
package ui
import (
"fmt"
"os"
"runtime"
"strings"
)
// Colors 颜色输出器
type Colors struct {
enabled bool
}
// ANSI 颜色代码
const (
ColorReset = "\033[0m"
ColorRed = "\033[31m"
ColorGreen = "\033[32m"
ColorYellow = "\033[33m"
ColorBlue = "\033[34m"
ColorMagenta = "\033[35m"
ColorCyan = "\033[36m"
ColorWhite = "\033[37m"
ColorGray = "\033[90m"
// 背景色
BgRed = "\033[41m"
BgGreen = "\033[42m"
BgYellow = "\033[43m"
BgBlue = "\033[44m"
BgMagenta = "\033[45m"
BgCyan = "\033[46m"
BgWhite = "\033[47m"
// 样式
StyleBold = "\033[1m"
StyleDim = "\033[2m"
StyleItalic = "\033[3m"
StyleUnderline = "\033[4m"
StyleBlink = "\033[5m"
StyleReverse = "\033[7m"
StyleStrike = "\033[9m"
)
// NewColors 创建新的颜色输出器
func NewColors() *Colors {
return &Colors{
enabled: supportsColor(),
}
}
// supportsColor 检查终端是否支持颜色
func supportsColor() bool {
// Windows 命令提示符通常不支持 ANSI 颜色
if runtime.GOOS == "windows" {
// 检查是否在支持颜色的终端中运行
term := os.Getenv("TERM")
if term == "" {
return false
}
}
// 检查环境变量
if os.Getenv("NO_COLOR") != "" {
return false
}
if os.Getenv("FORCE_COLOR") != "" {
return true
}
// 检查是否连接到终端
if !isTerminal() {
return false
}
return true
}
// isTerminal 检查是否连接到终端
func isTerminal() bool {
// 简单检查:如果 stdout 是文件,可能不是终端
stat, err := os.Stdout.Stat()
if err != nil {
return false
}
// 检查是否是字符设备
return (stat.Mode() & os.ModeCharDevice) != 0
}
// colorize 为文本添加颜色
func (c *Colors) colorize(text, color string) string {
if !c.enabled {
return text
}
return color + text + ColorReset
}
// Red 红色文本
func (c *Colors) Red(text string) string {
return c.colorize(text, ColorRed)
}
// Green 绿色文本
func (c *Colors) Green(text string) string {
return c.colorize(text, ColorGreen)
}
// Yellow 黄色文本
func (c *Colors) Yellow(text string) string {
return c.colorize(text, ColorYellow)
}
// Blue 蓝色文本
func (c *Colors) Blue(text string) string {
return c.colorize(text, ColorBlue)
}
// Magenta 洋红色文本
func (c *Colors) Magenta(text string) string {
return c.colorize(text, ColorMagenta)
}
// Cyan 青色文本
func (c *Colors) Cyan(text string) string {
return c.colorize(text, ColorCyan)
}
// White 白色文本
func (c *Colors) White(text string) string {
return c.colorize(text, ColorWhite)
}
// Gray 灰色文本
func (c *Colors) Gray(text string) string {
return c.colorize(text, ColorGray)
}
// Bold 粗体文本
func (c *Colors) Bold(text string) string {
return c.colorize(text, StyleBold)
}
// Dim 暗淡文本
func (c *Colors) Dim(text string) string {
return c.colorize(text, StyleDim)
}
// Italic 斜体文本
func (c *Colors) Italic(text string) string {
return c.colorize(text, StyleItalic)
}
// Underline 下划线文本
func (c *Colors) Underline(text string) string {
return c.colorize(text, StyleUnderline)
}
// Blink 闪烁文本
func (c *Colors) Blink(text string) string {
return c.colorize(text, StyleBlink)
}
// Reverse 反色文本
func (c *Colors) Reverse(text string) string {
return c.colorize(text, StyleReverse)
}
// Strike 删除线文本
func (c *Colors) Strike(text string) string {
return c.colorize(text, StyleStrike)
}
// BgRed 红色背景
func (c *Colors) BgRedText(text string) string {
return c.colorize(text, BgRed)
}
// BgGreen 绿色背景
func (c *Colors) BgGreenText(text string) string {
return c.colorize(text, BgGreen)
}
// BgYellow 黄色背景
func (c *Colors) BgYellowText(text string) string {
return c.colorize(text, BgYellow)
}
// BgBlue 蓝色背景
func (c *Colors) BgBlueText(text string) string {
return c.colorize(text, BgBlue)
}
// BgMagenta 洋红色背景
func (c *Colors) BgMagentaText(text string) string {
return c.colorize(text, BgMagenta)
}
// BgCyan 青色背景
func (c *Colors) BgCyanText(text string) string {
return c.colorize(text, BgCyan)
}
// BgWhite 白色背景
func (c *Colors) BgWhiteText(text string) string {
return c.colorize(text, BgWhite)
}
// Combine 组合多种样式
func (c *Colors) Combine(text string, styles ...string) string {
if !c.enabled {
return text
}
var combined string
for _, style := range styles {
combined += style
}
return combined + text + ColorReset
}
// Success 成功样式(绿色粗体)
func (c *Colors) Success(text string) string {
return c.Combine(text, ColorGreen, StyleBold)
}
// Error 错误样式(红色粗体)
func (c *Colors) Error(text string) string {
return c.Combine(text, ColorRed, StyleBold)
}
// Warning 警告样式(黄色粗体)
func (c *Colors) Warning(text string) string {
return c.Combine(text, ColorYellow, StyleBold)
}
// Info 信息样式(蓝色)
func (c *Colors) Info(text string) string {
return c.Blue(text)
}
// Highlight 高亮样式(青色粗体)
func (c *Colors) Highlight(text string) string {
return c.Combine(text, ColorCyan, StyleBold)
}
// Muted 静音样式(灰色暗淡)
func (c *Colors) Muted(text string) string {
return c.Combine(text, ColorGray, StyleDim)
}
// Enable 启用颜色输出
func (c *Colors) Enable() {
c.enabled = true
}
// Disable 禁用颜色输出
func (c *Colors) Disable() {
c.enabled = false
}
// IsEnabled 检查颜色输出是否启用
func (c *Colors) IsEnabled() bool {
return c.enabled
}
// Printf 带颜色的格式化输出
func (c *Colors) Printf(color, format string, args ...interface{}) {
text := fmt.Sprintf(format, args...)
switch color {
case "red":
fmt.Print(c.Red(text))
case "green":
fmt.Print(c.Green(text))
case "yellow":
fmt.Print(c.Yellow(text))
case "blue":
fmt.Print(c.Blue(text))
case "magenta":
fmt.Print(c.Magenta(text))
case "cyan":
fmt.Print(c.Cyan(text))
case "white":
fmt.Print(c.White(text))
case "gray":
fmt.Print(c.Gray(text))
default:
fmt.Print(text)
}
}
// Println 带颜色的输出(带换行)
func (c *Colors) Println(color, text string) {
c.Printf(color, "%s\n", text)
}
// Rainbow 彩虹文本(每个字符不同颜色)
func (c *Colors) Rainbow(text string) string {
if !c.enabled {
return text
}
colors := []string{ColorRed, ColorYellow, ColorGreen, ColorCyan, ColorBlue, ColorMagenta}
var result string
for i, char := range text {
color := colors[i%len(colors)]
result += color + string(char) + ColorReset
}
return result
}
// Gradient 渐变文本(从一种颜色到另一种颜色)
func (c *Colors) Gradient(text, startColor, endColor string) string {
if !c.enabled {
return text
}
// 简化实现:只在开头和结尾使用不同颜色
if len(text) <= 2 {
return c.colorize(text, startColor)
}
mid := len(text) / 2
return c.colorize(text[:mid], startColor) + c.colorize(text[mid:], endColor)
}
// Table 表格样式输出
func (c *Colors) Table(headers []string, rows [][]string) {
// 计算列宽
colWidths := make([]int, len(headers))
for i, header := range headers {
colWidths[i] = len(header)
}
for _, row := range rows {
for i, cell := range row {
if i < len(colWidths) && len(cell) > colWidths[i] {
colWidths[i] = len(cell)
}
}
}
// 输出表头
fmt.Print(c.Bold(c.Cyan("│")))
for i, header := range headers {
fmt.Printf(" %-*s ", colWidths[i], header)
fmt.Print(c.Bold(c.Cyan("│")))
}
fmt.Println()
// 输出分隔线
fmt.Print(c.Cyan("├"))
for i, width := range colWidths {
fmt.Print(c.Cyan(strings.Repeat("─", width+2)))
if i < len(colWidths)-1 {
fmt.Print(c.Cyan("┼"))
}
}
fmt.Println(c.Cyan("┤"))
// 输出数据行
for _, row := range rows {
fmt.Print(c.Cyan("│"))
for i, cell := range row {
if i < len(colWidths) {
fmt.Printf(" %-*s ", colWidths[i], cell)
}
fmt.Print(c.Cyan("│"))
}
fmt.Println()
}
}