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

@@ -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()
}
}