优化tui交互

This commit is contained in:
user123
2026-01-29 15:01:13 +08:00
parent 321abc59cd
commit 59839ad70a
4 changed files with 764 additions and 608 deletions

Binary file not shown.

View File

@@ -1,68 +1,151 @@
package style package style
import "github.com/charmbracelet/lipgloss" import (
"fmt"
"github.com/charmbracelet/lipgloss"
)
// Colors Palette - Modern & Professional
var ( var (
// Colors
ColorPrimary = lipgloss.Color("#7D56F4") // Purple ColorPrimary = lipgloss.Color("#7D56F4") // Purple
ColorSecondary = lipgloss.Color("#04B575") // Green ColorSecondary = lipgloss.Color("#04B575") // Green
ColorError = lipgloss.Color("#FF4C4C") // Red ColorError = lipgloss.Color("#FF3B30") // Red
ColorWarning = lipgloss.Color("#FFD700") // Gold ColorWarning = lipgloss.Color("#FFCC00") // Yellow
ColorSubtle = lipgloss.Color("#626262") // Gray ColorSubtle = lipgloss.Color("#666666") // Grey
ColorText = lipgloss.Color("#FAFAFA") // White ColorText = lipgloss.Color("#E0E0E0") // White-ish
ColorHighlight = lipgloss.Color("#2A2A2A") // Dark Grey
ColorPanel = lipgloss.Color("#1E1E1E") // Panel BG
ColorBorder = lipgloss.Color("#333333") // Border
)
// Styles // Base App Style
AppStyle = lipgloss.NewStyle(). var AppStyle = lipgloss.NewStyle().
Padding(1, 2) Padding(1, 2)
HeaderStyle = lipgloss.NewStyle(). // Headers
var HeaderStyle = lipgloss.NewStyle().
Foreground(ColorPrimary). Foreground(ColorPrimary).
Bold(true). Bold(true).
PaddingBottom(1) PaddingBottom(1)
StepStyle = lipgloss.NewStyle(). var SubHeaderStyle = lipgloss.NewStyle().
Foreground(ColorText) Foreground(ColorText).
Bold(true).
PaddingBottom(1)
SuccessStyle = lipgloss.NewStyle(). var TitleStyle = lipgloss.NewStyle().
Foreground(ColorPrimary).
Bold(true).
Padding(0, 1).
Border(lipgloss.RoundedBorder()).
BorderForeground(ColorPrimary)
// Status Colors
var SuccessStyle = lipgloss.NewStyle().Foreground(ColorSecondary).Bold(true)
var ErrorStyle = lipgloss.NewStyle().Foreground(ColorError).Bold(true)
var WarningStyle = lipgloss.NewStyle().Foreground(ColorWarning)
var SubtleStyle = lipgloss.NewStyle().Foreground(ColorSubtle)
// Panels
var PanelStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(ColorBorder).
Padding(1, 2)
var FocusedPanelStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(ColorPrimary).
Padding(1, 2)
var WizardPanelStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(ColorPrimary).
Padding(1, 4).
Width(80)
// Menu Styles
var MenuNormalStyle = lipgloss.NewStyle().
Foreground(ColorText).
PaddingLeft(2)
var MenuSelectedStyle = lipgloss.NewStyle().
Foreground(ColorSecondary). Foreground(ColorSecondary).
Background(ColorHighlight).
PaddingLeft(1).
Bold(true).
Border(lipgloss.NormalBorder(), false, false, false, true).
BorderForeground(ColorSecondary)
// Input & Form Styles
var KeyStyle = lipgloss.NewStyle().
Foreground(ColorPrimary).
Bold(true) Bold(true)
ErrorStyle = lipgloss.NewStyle(). var DescriptionStyle = lipgloss.NewStyle().
Foreground(ColorError). Foreground(ColorSubtle).
Italic(true)
var InputHelpStyle = lipgloss.NewStyle().
Foreground(ColorSubtle).
MarginBottom(1)
var StepStyle = lipgloss.NewStyle().
Foreground(ColorWarning).
Bold(true) Bold(true)
WarningStyle = lipgloss.NewStyle(). var InputStyle = lipgloss.NewStyle().
Foreground(ColorWarning) Foreground(ColorText).
Border(lipgloss.RoundedBorder()).
SubtleStyle = lipgloss.NewStyle(). BorderForeground(ColorSubtle).
Foreground(ColorSubtle)
CmdStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#00FFFF")).
Padding(0, 1) Padding(0, 1)
HighlightStyle = lipgloss.NewStyle(). var InputFocusedStyle = lipgloss.NewStyle().
Foreground(ColorSecondary). Foreground(ColorText).
Border(lipgloss.RoundedBorder()).
BorderForeground(ColorPrimary).
Padding(0, 1)
// Badges
var BadgeBase = lipgloss.NewStyle().
Bold(true) Bold(true)
)
func RenderStep(prefix string, msg string, status string) string { var BadgeInfo = BadgeBase.
var statusStyle lipgloss.Style Foreground(ColorPrimary)
var BadgeSuccess = BadgeBase.
Foreground(ColorSecondary)
var BadgeWarning = BadgeBase.
Foreground(ColorWarning)
var BadgeError = BadgeBase.
Foreground(ColorError)
// Helpers
func Badge(text, status string) string {
switch status { switch status {
case "pending": case "success":
statusStyle = SubtleStyle return BadgeSuccess.Render(text)
case "running": case "warning":
statusStyle = lipgloss.NewStyle().Foreground(ColorPrimary) return BadgeWarning.Render(text)
case "done":
statusStyle = SuccessStyle
case "error": case "error":
statusStyle = ErrorStyle return BadgeError.Render(text)
default: default:
statusStyle = StepStyle return BadgeInfo.Render(text)
}
} }
return lipgloss.JoinHorizontal(lipgloss.Left, func Checkbox(label string, checked bool) string {
statusStyle.Width(3).Render(prefix), if checked {
statusStyle.Render(msg), return fmt.Sprintf("[%s] %s", SuccessStyle.Render("x"), label)
}
return fmt.Sprintf("[ ] %s", label)
}
func RenderStep(step, total int, title string) string {
return fmt.Sprintf("%s %s",
StepStyle.Render(fmt.Sprintf("STEP %d/%d", step, total)),
SubHeaderStyle.Render(title),
) )
} }

View File

@@ -293,11 +293,24 @@ func ConfigureNpmMirror() error {
// InstallNode 下载并安装 Node.js MSI // InstallNode 下载并安装 Node.js MSI
func InstallNode() error { func InstallNode() error {
// 0. Check existing installation
_, ok := CheckNode()
if ok {
// Version is >= 22, skip install
return nil
}
// If version exists but is old (ok=false, verStr not empty), we update.
// If verStr is empty, we install fresh.
msiUrl := "https://nodejs.org/dist/v24.13.0/node-v24.13.0-x64.msi" msiUrl := "https://nodejs.org/dist/v24.13.0/node-v24.13.0-x64.msi"
tempDir := os.TempDir() tempDir := os.TempDir()
msiPath := filepath.Join(tempDir, "node-v24.13.0-x64.msi") msiPath := filepath.Join(tempDir, "node-v24.13.0-x64.msi")
// 1. 下载 MSI // 1. 下载 MSI
// Check if file already exists and has size (basic cache check)
if info, err := os.Stat(msiPath); err == nil && info.Size() > 10000000 {
// Assume valid if > 10MB, skip download
} else {
fmt.Printf("正在下载 Node.js: %s\n", msiUrl) fmt.Printf("正在下载 Node.js: %s\n", msiUrl)
resp, err := http.Get(msiUrl) resp, err := http.Get(msiUrl)
if err != nil { if err != nil {
@@ -319,13 +332,39 @@ func InstallNode() error {
if err != nil { if err != nil {
return fmt.Errorf("写入文件失败: %v", err) return fmt.Errorf("写入文件失败: %v", err)
} }
}
// 2. 安装 MSI (静默安装) // 2. 安装 MSI (静默安装)
// msiexec /i <file> /qn // msiexec /i <file> /qn
fmt.Println("正在安装 Node.js...") // Error 1619: This installation package could not be opened.
// Error 1603: Fatal error during installation.
// Error 1618: Another installation is already in progress.
fmt.Println("正在安装 Node.js (可能需要管理员权限)...")
// Retry loop for 1618 (Another installation in progress)
for i := 0; i < 3; i++ {
installCmd := exec.Command("msiexec", "/i", msiPath, "/qn") installCmd := exec.Command("msiexec", "/i", msiPath, "/qn")
if err := installCmd.Run(); err != nil { output, err := installCmd.CombinedOutput()
return fmt.Errorf("安装失败: %v", err) if err == nil {
break
}
outStr := string(output)
if strings.Contains(outStr, "1618") {
time.Sleep(5 * time.Second)
continue
}
// If error is 1619, maybe file is corrupted, delete and retry download?
// For now just return error but with better message
if strings.Contains(outStr, "1619") {
return fmt.Errorf("安装包损坏或无法打开 (Error 1619). 请尝试手动下载安装: %s", msiUrl)
}
if i == 2 {
return fmt.Errorf("安装失败: %v, Output: %s", err, outStr)
}
time.Sleep(2 * time.Second)
} }
// 3. 刷新环境变量 (当前进程无法立即生效,但后续调用 getNpmPath 会尝试绝对路径) // 3. 刷新环境变量 (当前进程无法立即生效,但后续调用 getNpmPath 会尝试绝对路径)

File diff suppressed because it is too large Load Diff