📊 进度条组件
Bubble 进度条组件提供了丰富的进度显示功能,支持单任务和多任务进度跟踪,适用于各种数据处理和任务执行场景。
🎯 组件概览
功能特性
- 单任务进度 - 简单的进度条显示
- 多任务进度 - 并行任务进度管理
- 实时更新 - 动态进度状态更新
- 自定义样式 - 灵活的视觉定制
- 错误处理 - 失败状态显示和重试机制
📝 基础用法
简单进度条
go
// pkg/bubble/progress_simple.go
package bubble
import (
"fmt"
"strings"
"time"
"github.com/charmbracelet/lipgloss"
)
// 简单进度条模型
type SimpleProgressModel struct {
current int
total int
title string
width int
style ProgressStyle
}
// 进度条样式
type ProgressStyle struct {
Filled lipgloss.Style
Empty lipgloss.Style
Percent lipgloss.Style
Title lipgloss.Style
}
// 默认样式
func DefaultProgressStyle() ProgressStyle {
return ProgressStyle{
Filled: lipgloss.NewStyle().
Foreground(lipgloss.Color("#00FF00")).
Background(lipgloss.Color("#004000")),
Empty: lipgloss.NewStyle().
Foreground(lipgloss.Color("#404040")).
Background(lipgloss.Color("#202020")),
Percent: lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFFFF")).
Bold(true),
Title: lipgloss.NewStyle().
Foreground(lipgloss.Color("#00FFFF")).
Bold(true),
}
}
// 创建简单进度条
func NewSimpleProgress(title string, total int) SimpleProgressModel {
return SimpleProgressModel{
current: 0,
total: total,
title: title,
width: 50,
style: DefaultProgressStyle(),
}
}
// 设置当前进度
func (m SimpleProgressModel) SetProgress(current int) SimpleProgressModel {
if current > m.total {
current = m.total
}
if current < 0 {
current = 0
}
m.current = current
return m
}
// 增加进度
func (m SimpleProgressModel) Increment() SimpleProgressModel {
return m.SetProgress(m.current + 1)
}
// 设置宽度
func (m SimpleProgressModel) WithWidth(width int) SimpleProgressModel {
m.width = width
return m
}
// 设置样式
func (m SimpleProgressModel) WithStyle(style ProgressStyle) SimpleProgressModel {
m.style = style
return m
}
// 计算进度百分比
func (m SimpleProgressModel) Percentage() float64 {
if m.total == 0 {
return 0
}
return float64(m.current) / float64(m.total) * 100
}
// 是否完成
func (m SimpleProgressModel) IsComplete() bool {
return m.current >= m.total
}
// 渲染视图
func (m SimpleProgressModel) View() string {
var b strings.Builder
// 标题
if m.title != "" {
b.WriteString(m.style.Title.Render(m.title))
b.WriteString("\n")
}
// 进度条
filledWidth := int(float64(m.width) * m.Percentage() / 100)
emptyWidth := m.width - filledWidth
filled := strings.Repeat("█", filledWidth)
empty := strings.Repeat("░", emptyWidth)
progressBar := m.style.Filled.Render(filled) + m.style.Empty.Render(empty)
b.WriteString(progressBar)
// 百分比和计数
percent := m.style.Percent.Render(fmt.Sprintf(" %.1f%% (%d/%d)",
m.Percentage(), m.current, m.total))
b.WriteString(percent)
return b.String()
}
// 使用示例
func ExampleSimpleProgress() {
progress := NewSimpleProgress("下载文件", 100)
for i := 0; i <= 100; i++ {
progress = progress.SetProgress(i)
fmt.Print("\r" + progress.View())
time.Sleep(50 * time.Millisecond)
}
fmt.Println()
}多任务进度管理
go
// pkg/bubble/progress_multi.go
package bubble
import (
"fmt"
"sort"
"strings"
"sync"
"time"
)
// 任务状态
type TaskStatus int
const (
StatusPending TaskStatus = iota
StatusRunning
StatusCompleted
StatusFailed
StatusCancelled
)
func (s TaskStatus) String() string {
switch s {
case StatusPending:
return "待执行"
case StatusRunning:
return "执行中"
case StatusCompleted:
return "已完成"
case StatusFailed:
return "失败"
case StatusCancelled:
return "已取消"
default:
return "未知"
}
}
// 任务进度
type TaskProgress struct {
ID string
Name string
Status TaskStatus
Current int
Total int
Error error
StartTime time.Time
EndTime time.Time
metadata map[string]interface{}
mu sync.RWMutex
}
// 创建新任务
func NewTaskProgress(id, name string, total int) *TaskProgress {
return &TaskProgress{
ID: id,
Name: name,
Status: StatusPending,
Current: 0,
Total: total,
metadata: make(map[string]interface{}),
}
}
// 启动任务
func (t *TaskProgress) Start() {
t.mu.Lock()
defer t.mu.Unlock()
t.Status = StatusRunning
t.StartTime = time.Now()
}
// 更新进度
func (t *TaskProgress) UpdateProgress(current int) {
t.mu.Lock()
defer t.mu.Unlock()
if current > t.Total {
current = t.Total
}
if current < 0 {
current = 0
}
t.Current = current
if current >= t.Total && t.Status == StatusRunning {
t.Status = StatusCompleted
t.EndTime = time.Now()
}
}
// 增加进度
func (t *TaskProgress) Increment() {
t.UpdateProgress(t.Current + 1)
}
// 标记失败
func (t *TaskProgress) Fail(err error) {
t.mu.Lock()
defer t.mu.Unlock()
t.Status = StatusFailed
t.Error = err
t.EndTime = time.Now()
}
// 取消任务
func (t *TaskProgress) Cancel() {
t.mu.Lock()
defer t.mu.Unlock()
t.Status = StatusCancelled
t.EndTime = time.Now()
}
// 获取进度百分比
func (t *TaskProgress) Percentage() float64 {
t.mu.RLock()
defer t.mu.RUnlock()
if t.Total == 0 {
return 0
}
return float64(t.Current) / float64(t.Total) * 100
}
// 获取执行时间
func (t *TaskProgress) Duration() time.Duration {
t.mu.RLock()
defer t.mu.RUnlock()
if t.StartTime.IsZero() {
return 0
}
endTime := t.EndTime
if endTime.IsZero() {
endTime = time.Now()
}
return endTime.Sub(t.StartTime)
}
// 设置元数据
func (t *TaskProgress) SetMetadata(key string, value interface{}) {
t.mu.Lock()
defer t.mu.Unlock()
t.metadata[key] = value
}
// 获取元数据
func (t *TaskProgress) GetMetadata(key string) interface{} {
t.mu.RLock()
defer t.mu.RUnlock()
return t.metadata[key]
}
// 多任务进度管理器
type MultiProgressManager struct {
tasks map[string]*TaskProgress
order []string // 任务显示顺序
mu sync.RWMutex
style MultiProgressStyle
maxWidth int
}
// 多任务样式
type MultiProgressStyle struct {
TaskName lipgloss.Style
ProgressBar ProgressStyle
Status map[TaskStatus]lipgloss.Style
Summary lipgloss.Style
}
// 默认多任务样式
func DefaultMultiProgressStyle() MultiProgressStyle {
return MultiProgressStyle{
TaskName: lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFFFF")).
Bold(true),
ProgressBar: DefaultProgressStyle(),
Status: map[TaskStatus]lipgloss.Style{
StatusPending: lipgloss.NewStyle().
Foreground(lipgloss.Color("#808080")),
StatusRunning: lipgloss.NewStyle().
Foreground(lipgloss.Color("#00FF00")),
StatusCompleted: lipgloss.NewStyle().
Foreground(lipgloss.Color("#00FF00")),
StatusFailed: lipgloss.NewStyle().
Foreground(lipgloss.Color("#FF0000")),
StatusCancelled: lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFF00")),
},
Summary: lipgloss.NewStyle().
Foreground(lipgloss.Color("#00FFFF")).
Bold(true),
}
}
// 创建多任务进度管理器
func NewMultiProgressManager() *MultiProgressManager {
return &MultiProgressManager{
tasks: make(map[string]*TaskProgress),
order: make([]string, 0),
style: DefaultMultiProgressStyle(),
maxWidth: 80,
}
}
// 添加任务
func (m *MultiProgressManager) AddTask(task *TaskProgress) {
m.mu.Lock()
defer m.mu.Unlock()
m.tasks[task.ID] = task
m.order = append(m.order, task.ID)
}
// 获取任务
func (m *MultiProgressManager) GetTask(id string) *TaskProgress {
m.mu.RLock()
defer m.mu.RUnlock()
return m.tasks[id]
}
// 移除任务
func (m *MultiProgressManager) RemoveTask(id string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.tasks, id)
for i, taskID := range m.order {
if taskID == id {
m.order = append(m.order[:i], m.order[i+1:]...)
break
}
}
}
// 获取所有任务
func (m *MultiProgressManager) GetAllTasks() []*TaskProgress {
m.mu.RLock()
defer m.mu.RUnlock()
tasks := make([]*TaskProgress, len(m.order))
for i, id := range m.order {
tasks[i] = m.tasks[id]
}
return tasks
}
// 获取统计信息
func (m *MultiProgressManager) GetStats() (completed, failed, running, pending int) {
m.mu.RLock()
defer m.mu.RUnlock()
for _, task := range m.tasks {
switch task.Status {
case StatusCompleted:
completed++
case StatusFailed:
failed++
case StatusRunning:
running++
case StatusPending:
pending++
}
}
return
}
// 是否全部完成
func (m *MultiProgressManager) IsAllCompleted() bool {
completed, failed, running, pending := m.GetStats()
return running == 0 && pending == 0
}
// 设置样式
func (m *MultiProgressManager) WithStyle(style MultiProgressStyle) *MultiProgressManager {
m.style = style
return m
}
// 设置最大宽度
func (m *MultiProgressManager) WithMaxWidth(width int) *MultiProgressManager {
m.maxWidth = width
return m
}
// 渲染单个任务
func (m *MultiProgressManager) renderTask(task *TaskProgress) string {
var b strings.Builder
// 任务名称和状态
nameWidth := 30
if len(task.Name) > nameWidth {
task.Name = task.Name[:nameWidth-3] + "..."
}
statusStyle := m.style.Status[task.Status]
taskInfo := fmt.Sprintf("%-*s [%s]", nameWidth, task.Name, task.Status.String())
b.WriteString(m.style.TaskName.Render(taskInfo))
b.WriteString(" ")
// 进度条
progressWidth := m.maxWidth - nameWidth - 20 // 留出空间给状态和百分比
if progressWidth < 10 {
progressWidth = 10
}
filledWidth := int(float64(progressWidth) * task.Percentage() / 100)
emptyWidth := progressWidth - filledWidth
filled := strings.Repeat("█", filledWidth)
empty := strings.Repeat("░", emptyWidth)
progressBar := m.style.ProgressBar.Filled.Render(filled) +
m.style.ProgressBar.Empty.Render(empty)
b.WriteString(progressBar)
// 百分比和时间
percent := fmt.Sprintf(" %.1f%% ", task.Percentage())
b.WriteString(m.style.ProgressBar.Percent.Render(percent))
if task.Status == StatusRunning || task.Status == StatusCompleted {
duration := task.Duration()
b.WriteString(fmt.Sprintf("(%v)", duration.Round(time.Second)))
}
// 错误信息
if task.Error != nil {
b.WriteString("\n")
errorMsg := fmt.Sprintf(" 错误: %s", task.Error.Error())
b.WriteString(m.style.Status[StatusFailed].Render(errorMsg))
}
return b.String()
}
// 渲染视图
func (m *MultiProgressManager) View() string {
var b strings.Builder
// 任务列表
tasks := m.GetAllTasks()
for i, task := range tasks {
if i > 0 {
b.WriteString("\n")
}
b.WriteString(m.renderTask(task))
}
// 总体统计
completed, failed, running, pending := m.GetStats()
total := len(tasks)
if total > 0 {
b.WriteString("\n\n")
summary := fmt.Sprintf("总计: %d | 已完成: %d | 失败: %d | 运行中: %d | 等待: %d",
total, completed, failed, running, pending)
b.WriteString(m.style.Summary.Render(summary))
// 总体进度
if total > 0 {
overallPercent := float64(completed) / float64(total) * 100
b.WriteString(fmt.Sprintf(" (%.1f%%)", overallPercent))
}
}
return b.String()
}实时进度更新示例
go
// examples/progress_example.go
package main
import (
"context"
"fmt"
"math/rand"
"time"
"lzt/pkg/bubble"
)
func main() {
// 创建多任务进度管理器
manager := bubble.NewMultiProgressManager()
// 添加多个任务
tasks := []*bubble.TaskProgress{
bubble.NewTaskProgress("download1", "下载文件 1", 100),
bubble.NewTaskProgress("download2", "下载文件 2", 150),
bubble.NewTaskProgress("process1", "处理数据 1", 80),
bubble.NewTaskProgress("process2", "处理数据 2", 120),
}
for _, task := range tasks {
manager.AddTask(task)
}
// 启动任务处理
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for _, task := range tasks {
go simulateTaskExecution(ctx, task)
}
// 实时显示进度
for !manager.IsAllCompleted() {
// 清屏并显示进度
fmt.Print("\033[H\033[2J") // ANSI 清屏
fmt.Println(manager.View())
time.Sleep(100 * time.Millisecond)
}
// 显示最终结果
fmt.Print("\033[H\033[2J")
fmt.Println(manager.View())
fmt.Println("\n所有任务已完成!")
}
func simulateTaskExecution(ctx context.Context, task *bubble.TaskProgress) {
// 随机延迟启动
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
task.Start()
for i := 0; i <= task.Total; i++ {
select {
case <-ctx.Done():
task.Cancel()
return
default:
}
// 模拟工作
time.Sleep(time.Duration(rand.Intn(50)+10) * time.Millisecond)
// 随机失败
if rand.Float64() < 0.01 { // 1% 失败率
task.Fail(fmt.Errorf("随机失败"))
return
}
task.UpdateProgress(i)
}
}🎨 自定义样式
主题样式配置
go
// pkg/bubble/themes.go
package bubble
import "github.com/charmbracelet/lipgloss"
// 暗色主题
func DarkTheme() MultiProgressStyle {
return MultiProgressStyle{
TaskName: lipgloss.NewStyle().
Foreground(lipgloss.Color("#E0E0E0")).
Bold(true),
ProgressBar: ProgressStyle{
Filled: lipgloss.NewStyle().
Foreground(lipgloss.Color("#00FF7F")).
Background(lipgloss.Color("#003225")),
Empty: lipgloss.NewStyle().
Foreground(lipgloss.Color("#404040")).
Background(lipgloss.Color("#1A1A1A")),
Percent: lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFFFF")).
Bold(true),
Title: lipgloss.NewStyle().
Foreground(lipgloss.Color("#00BFFF")).
Bold(true),
},
Status: map[TaskStatus]lipgloss.Style{
StatusPending: lipgloss.NewStyle().
Foreground(lipgloss.Color("#696969")),
StatusRunning: lipgloss.NewStyle().
Foreground(lipgloss.Color("#00FF7F")),
StatusCompleted: lipgloss.NewStyle().
Foreground(lipgloss.Color("#32CD32")),
StatusFailed: lipgloss.NewStyle().
Foreground(lipgloss.Color("#FF4500")),
StatusCancelled: lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFD700")),
},
Summary: lipgloss.NewStyle().
Foreground(lipgloss.Color("#00BFFF")).
Bold(true),
}
}
// 亮色主题
func LightTheme() MultiProgressStyle {
return MultiProgressStyle{
TaskName: lipgloss.NewStyle().
Foreground(lipgloss.Color("#2F2F2F")).
Bold(true),
ProgressBar: ProgressStyle{
Filled: lipgloss.NewStyle().
Foreground(lipgloss.Color("#228B22")).
Background(lipgloss.Color("#E6FFE6")),
Empty: lipgloss.NewStyle().
Foreground(lipgloss.Color("#D3D3D3")).
Background(lipgloss.Color("#F5F5F5")),
Percent: lipgloss.NewStyle().
Foreground(lipgloss.Color("#000000")).
Bold(true),
Title: lipgloss.NewStyle().
Foreground(lipgloss.Color("#4682B4")).
Bold(true),
},
Status: map[TaskStatus]lipgloss.Style{
StatusPending: lipgloss.NewStyle().
Foreground(lipgloss.Color("#808080")),
StatusRunning: lipgloss.NewStyle().
Foreground(lipgloss.Color("#228B22")),
StatusCompleted: lipgloss.NewStyle().
Foreground(lipgloss.Color("#006400")),
StatusFailed: lipgloss.NewStyle().
Foreground(lipgloss.Color("#DC143C")),
StatusCancelled: lipgloss.NewStyle().
Foreground(lipgloss.Color("#FF8C00")),
},
Summary: lipgloss.NewStyle().
Foreground(lipgloss.Color("#4682B4")).
Bold(true),
}
}📚 相关资源
项目文档
外部参考
💡 使用建议: 进度条组件适用于长时间运行的任务。合理设置更新频率,避免过度刷新影响性能。对于大量并发任务,考虑使用分页或折叠显示。