优化tui交互
This commit is contained in:
Binary file not shown.
@@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user