optimize tui interface and interaction

This commit is contained in:
2026-02-22 20:18:12 +08:00
parent ce9af9f7d0
commit 47a2dfeda1
7 changed files with 289 additions and 183 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
sunhpc
testgui

66
cmd/test/main.go Normal file
View File

@@ -0,0 +1,66 @@
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
)
// model 定义应用的状态
type model struct {
items []string // 列表数据
selectedIdx int // 当前选中的索引
}
// Init 初始化模型,返回初始命令(这里不需要,返回 nil
func (m model) Init() tea.Cmd {
return nil
}
// Update 处理用户输入和状态更新
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
// 处理键盘输入
case tea.KeyMsg:
switch msg.String() {
// Tab 键:切换选中项(循环切换)
case "tab":
m.selectedIdx = (m.selectedIdx + 1) % len(m.items)
// Ctrl+C 或 q 键:退出程序
case "ctrl+c", "q":
return m, tea.Quit
}
}
return m, nil
}
// View 渲染界面
func (m model) View() string {
s := "网络接口列表(按 Tab 切换选择,按 q 退出)\n\n"
// 遍历列表项,渲染每一项
for i, item := range m.items {
// 标记当前选中的项
if i == m.selectedIdx {
s += fmt.Sprintf("→ %s (选中)\n", item)
} else {
s += fmt.Sprintf(" %s\n", item)
}
}
return s
}
func main() {
// 初始化模型,设置列表数据
initialModel := model{
items: []string{"eth0", "eth1", "eth2", "eth3"},
selectedIdx: 0, // 默认选中第一个项
}
// 启动 Bubble Tea 程序
p := tea.NewProgram(initialModel)
if _, err := p.Run(); err != nil {
fmt.Printf("程序运行出错: %v\n", err)
}
}

View File

@@ -31,3 +31,8 @@ func GenerateID() (string, error) {
func GetTimestamp() string {
return time.Now().Format("2006-01-02 15:04:05")
}
// 定义短语
const (
NoAvailableNetworkInterfaces = "No available network interfaces"
)

View File

@@ -3,8 +3,10 @@ package wizard
import (
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"sunhpc/pkg/utils"
"github.com/charmbracelet/bubbles/textinput"
)
@@ -66,31 +68,30 @@ func (m *model) saveCurrentPage() {
func (m *model) saveDataPage() {
if len(m.textInputs) >= 8 {
m.config.Hostname = m.textInputs[0].Value()
m.config.Country = m.textInputs[1].Value()
m.config.Region = m.textInputs[2].Value()
m.config.Timezone = m.textInputs[3].Value()
m.config.HomePage = m.textInputs[4].Value()
m.config.HomePage = m.textInputs[0].Value()
m.config.Hostname = m.textInputs[1].Value()
m.config.Country = m.textInputs[2].Value()
m.config.Region = m.textInputs[3].Value()
m.config.Timezone = m.textInputs[4].Value()
m.config.DBAddress = m.textInputs[5].Value()
m.config.DBName = m.textInputs[6].Value()
m.config.DataAddress = m.textInputs[7].Value()
m.config.DataAddress = m.textInputs[6].Value()
}
}
func (m *model) savePublicNetworkPage() {
if len(m.textInputs) >= 4 {
m.config.PublicInterface = m.textInputs[0].Value()
m.config.IPAddress = m.textInputs[1].Value()
m.config.Netmask = m.textInputs[2].Value()
m.config.Gateway = m.textInputs[3].Value()
m.config.PublicIPAddress = m.textInputs[1].Value()
m.config.PublicNetmask = m.textInputs[2].Value()
m.config.PublicGateway = m.textInputs[3].Value()
}
}
func (m *model) saveInternalNetworkPage() {
if len(m.textInputs) >= 3 {
m.config.InternalInterface = m.textInputs[0].Value()
m.config.InternalIP = m.textInputs[1].Value()
m.config.InternalMask = m.textInputs[2].Value()
m.config.InternalIPAddress = m.textInputs[1].Value()
m.config.InternalNetmask = m.textInputs[2].Value()
}
}
@@ -108,20 +109,18 @@ func (m *model) initPageInputs() {
switch m.currentPage {
case PageData:
fields := []struct{ label, value string }{
{"主机名:", m.config.Hostname},
{"国家:", m.config.Country},
{"地区:", m.config.Region},
{"时区:", m.config.Timezone},
{"主页:", m.config.HomePage},
{"数据库地址:", m.config.DBAddress},
{"数据库名称:", m.config.DBName},
{"Data 地址:", m.config.DataAddress},
{"Homepage", m.config.HomePage},
{"Hostname", m.config.Hostname},
{"Country", m.config.Country},
{"Region", m.config.Region},
{"Timezone", m.config.Timezone},
{"DB Path", m.config.DBAddress},
{"Software", m.config.DataAddress},
}
for _, f := range fields {
ti := textinput.New()
ti.Placeholder = ""
ti.Placeholder = f.label
//ti.Placeholder = "请输入" + f.label[:len(f.label)-1]
ti.SetValue(f.value)
ti.Width = 50
m.textInputs = append(m.textInputs, ti)
@@ -130,18 +129,19 @@ func (m *model) initPageInputs() {
if len(m.textInputs) > 0 {
m.textInputs[0].Focus()
}
m.inputLabels = []string{"Hostname", "Country", "Region", "Timezone", "Homepage", "DBPath", "DBName", "Software"}
m.inputLabels = []string{"Homepage", "Hostname", "Country", "Region", "Timezone", "DBPath", "Software"}
case PagePublicNetwork:
fields := []struct{ label, value string }{
{"公网接口:", m.config.PublicInterface},
{"IP 地址:", m.config.IPAddress},
{"子网掩码:", m.config.Netmask},
{"网关:", m.config.Gateway},
{"Public Interface", m.config.PublicInterface},
{"Public IP Address", m.config.PublicIPAddress},
{"Public Netmask", m.config.PublicNetmask},
{"Public Gateway", m.config.PublicGateway},
}
for _, f := range fields {
ti := textinput.New()
ti.Placeholder = ""
ti.Placeholder = f.label
ti.SetValue(f.value)
ti.Width = 50
m.textInputs = append(m.textInputs, ti)
@@ -150,17 +150,17 @@ func (m *model) initPageInputs() {
if len(m.textInputs) > 0 {
m.textInputs[0].Focus()
}
m.inputLabels = []string{"Wan iface", "IPAddress", "Netmask", "Gateway"}
m.inputLabels = []string{"Public Interface", "Public IP Address", "Public Netmask", "Public Gateway"}
case PageInternalNetwork:
fields := []struct{ label, value string }{
{"内网接口:", m.config.InternalInterface},
{"内网 IP:", m.config.InternalIP},
{"内网掩码:", m.config.InternalMask},
{"Internal Interface", m.config.InternalInterface},
{"Internal IP Address", m.config.InternalIPAddress},
{"Internal Netmask", m.config.InternalNetmask},
}
for _, f := range fields {
ti := textinput.New()
ti.Placeholder = ""
ti.Placeholder = f.label
ti.SetValue(f.value)
ti.Width = 50
m.textInputs = append(m.textInputs, ti)
@@ -169,16 +169,16 @@ func (m *model) initPageInputs() {
if len(m.textInputs) > 0 {
m.textInputs[0].Focus()
}
m.inputLabels = []string{"Lan iface", "IPAddress", "Netmask"}
m.inputLabels = []string{"Internal Interface", "Internal IP", "Internal Mask"}
case PageDNS:
fields := []struct{ label, value string }{
{" DNS:", m.config.DNSPrimary},
{" DNS:", m.config.DNSSecondary},
{"Primary DNS", m.config.DNSPrimary},
{"Secondary DNS", m.config.DNSSecondary},
}
for _, f := range fields {
ti := textinput.New()
ti.Placeholder = ""
ti.Placeholder = f.label
ti.SetValue(f.value)
ti.Width = 50
m.textInputs = append(m.textInputs, ti)
@@ -187,6 +187,31 @@ func (m *model) initPageInputs() {
if len(m.textInputs) > 0 {
m.textInputs[0].Focus()
}
m.inputLabels = []string{"DNSPrimary", "DNSSecondary"}
m.inputLabels = []string{"Pri DNS", "Sec DNS"}
}
}
// 获取系统网络接口
func getNetworkInterfaces() []string {
// 实现获取系统网络接口的逻辑
// 例如:使用 net.Interface() 函数获取系统网络接口
// 返回一个字符串切片,包含系统网络接口的名称
interfaces, err := net.Interfaces()
if err != nil {
return []string{utils.NoAvailableNetworkInterfaces}
}
var result []string
for _, iface := range interfaces {
// 跳过 loopback 接口
if iface.Flags&net.FlagLoopback != 0 {
continue
}
result = append(result, iface.Name)
}
if len(result) == 0 {
return []string{utils.NoAvailableNetworkInterfaces}
}
return result
}

View File

@@ -1,6 +1,8 @@
package wizard
import (
"fmt"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
)
@@ -17,19 +19,18 @@ type Config struct {
Timezone string `json:"timezone"`
HomePage string `json:"homepage"`
DBAddress string `json:"db_address"`
DBName string `json:"db_name"`
DataAddress string `json:"data_address"`
// 公网设置
PublicInterface string `json:"public_interface"`
IPAddress string `json:"ip_address"`
Netmask string `json:"netmask"`
Gateway string `json:"gateway"`
PublicIPAddress string `json:"ip_address"`
PublicNetmask string `json:"netmask"`
PublicGateway string `json:"gateway"`
// 内网配置
InternalInterface string `json:"internal_interface"`
InternalIP string `json:"internal_ip"`
InternalMask string `json:"internal_mask"`
InternalIPAddress string `json:"internal_ip"`
InternalNetmask string `json:"internal_mask"`
// DNS 配置
DNSPrimary string `json:"dns_primary"`
@@ -59,6 +60,7 @@ type model struct {
config Config
currentPage PageType
totalPages int
networkInterfaces []string // 所有系统网络接口
textInputs []textinput.Model
inputLabels []string // 存储标签
focusIndex int
@@ -74,22 +76,41 @@ type model struct {
// defaultConfig 返回默认配置
func defaultConfig() Config {
var (
defaultPublicInterface string
defaultInternalInterface string
)
interfaces := getNetworkInterfaces()
switch len(interfaces) {
case 0:
defaultPublicInterface = ""
defaultInternalInterface = ""
case 1:
defaultPublicInterface = interfaces[0]
defaultInternalInterface = fmt.Sprintf("%s:0", interfaces[0])
case 2:
defaultPublicInterface = interfaces[0]
defaultInternalInterface = interfaces[1]
default:
defaultPublicInterface = interfaces[0]
defaultInternalInterface = interfaces[1]
}
return Config{
Hostname: "sunhpc01",
Hostname: "cluster.hpc.org",
Country: "China",
Region: "Beijing",
Timezone: "Asia/Shanghai",
HomePage: "https://sunhpc.example.com",
DBAddress: "127.0.0.1",
DBName: "sunhpc_db",
DataAddress: "/data/sunhpc",
PublicInterface: "eth0",
InternalInterface: "eth1",
IPAddress: "192.168.1.100",
Netmask: "255.255.255.0",
Gateway: "192.168.1.1",
InternalIP: "10.0.0.100",
InternalMask: "255.255.255.0",
HomePage: "www.sunhpc.com",
DBAddress: "/var/lib/sunhpc/sunhpc.db",
DataAddress: "/export/sunhpc",
PublicInterface: defaultPublicInterface,
PublicIPAddress: "",
PublicNetmask: "",
PublicGateway: "",
InternalInterface: defaultInternalInterface,
InternalIPAddress: "172.16.9.254",
InternalNetmask: "255.255.255.0",
DNSPrimary: "8.8.8.8",
DNSSecondary: "8.8.4.4",
}

View File

@@ -45,43 +45,39 @@ func (m model) View() string {
// agreementView 协议页面
func (m model) agreementView() string {
title := titleStyle.Render("SunHPC 系统初始化向导")
subtitle := subTitleStyle.Render("请先阅读并同意以下协议")
title := titleStyle.Render("SunHPC Software License Agreement")
agreement := agreementBox.Render(`
┌─────────────────────────────────────────────────────────────┐
│ SunHPC 软件许可协议
│ SunHPC License Agreement
└─────────────────────────────────────────────────────────────┘
1. 许可授予
本软件授予您非独占、不可转让的使用许可。
2. 使用限制
- 不得用于非法目的
- 不得反向工程或反编译
- 不得移除版权标识
3. 免责声明
本软件按"原样"提供,不提供任何明示或暗示的保证。
4. 责任限制
在任何情况下,作者不对因使用本软件造成的任何损失负责。
5. 协议终止
如违反本协议条款,许可将自动终止。
1. License Grant
This software grants you a non-exclusive, non-transferable
license to use it.
2. Disclaimer of Warranties
This software is provided "as is" without any warranties,
whether express or implied.
3. Limitation of Liability
This software is provided without warranty of any kind,
either express or implied.
In no event shall the author be liable for any damages
arising out of the use of this software.
4. Termination of Agreement
If you violate any of the terms of this agreement,
the license will automatically terminate.
───────────────────────────────────────────────────────────────
请仔细阅读以上条款,点击"接受"表示您同意并遵守本协议。
PLEASE READ THE ABOVE TERMS CAREFULLY AND CLICK "ACCEPT"
TO AGREE AND FOLLOW THIS AGREEMENT.
───────────────────────────────────────────────────────────────
`)
var acceptBtn, rejectBtn string
if m.agreementIdx == 0 {
rejectBtn = selectedButton.Render(">> 拒绝 <<")
acceptBtn = selectedButton.Render(" 同意 ")
rejectBtn = selectedButton.Render(">> Reject <<")
acceptBtn = " Accept "
} else {
rejectBtn = selectedButton.Render(" 拒绝 ")
acceptBtn = selectedButton.Render(">> 同意 <<")
rejectBtn = " Reject "
acceptBtn = selectedButton.Render(">> Accept <<")
}
buttonGroup := lipgloss.JoinHorizontal(
@@ -92,11 +88,9 @@ func (m model) agreementView() string {
// debugInfo := lipgloss.NewStyle().Foreground(lipgloss.Color("#888888")).
// Render(fmt.Sprintf("[DEBUG: idx=%d]", m.agreementIdx),)
hint := hintStyle.Render("使用 <- -> 或 Tab 选择,Enter 确认")
hint := hintStyle.Render("Use Up/Down OR Tab Change,Enter Confirm")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
subtitle, "",
agreement, "",
buttonGroup, "",
// debugInfo, "", // ✅ 显示调试信息
@@ -106,8 +100,7 @@ func (m model) agreementView() string {
// dataView 数据接收页面
func (m model) dataView() string {
title := titleStyle.Render("集群基础配置")
subtitle := subTitleStyle.Render("请填写系统基本信息")
title := titleStyle.Render("Cluster Information")
var inputs strings.Builder
for i, ti := range m.textInputs {
@@ -117,11 +110,9 @@ func (m model) dataView() string {
}
buttons := m.renderNavButtons()
hint := hintStyle.Render("使用 Up/Down 或 Tab 切换、Enter 确认")
hint := hintStyle.Render("Use Up/Down OR Tab Change,Enter Confirm")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
subtitle, "",
inputs.String(), "",
buttons, "",
hint,
@@ -130,25 +121,24 @@ func (m model) dataView() string {
// publicNetworkView 公网设置页面
func (m model) publicNetworkView() string {
title := titleStyle.Render("公网配置")
subtitle := subTitleStyle.Render("请配置网络接口信息")
title := titleStyle.Render("Public Network Configuration")
autoDetect := infoStyle.Render("[*] 自动检测网络接口: eth0, eth1, ens33")
networkInterfaces := getNetworkInterfaces()
autoDetect := infoStyle.Render(
"[*] Auto Detect Network Interfaces: " + strings.Join(networkInterfaces, ", "))
var inputs strings.Builder
for i, ti := range m.textInputs {
info := fmt.Sprintf("%-10s|", m.inputLabels[i])
info := fmt.Sprintf("%-20s|", m.inputLabels[i])
input := inputBox.Render(info + ti.View())
inputs.WriteString(input + "\n")
}
buttons := m.renderNavButtons()
hint := hintStyle.Render("使用 Up/Down 或 Tab 切换、Enter 确认")
hint := hintStyle.Render("Use Up/Down OR Tab Change,Enter Confirm")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
subtitle, "",
autoDetect, "",
inputs.String(), "",
buttons, "",
@@ -158,22 +148,25 @@ func (m model) publicNetworkView() string {
// internalNetworkView 内网配置页面
func (m model) internalNetworkView() string {
title := titleStyle.Render("内网配置")
subtitle := subTitleStyle.Render("请配置内网信息")
title := titleStyle.Render("Internal Network Configuration")
networkInterfaces := getNetworkInterfaces()
autoDetect := infoStyle.Render(
"[*] Auto Detect Network Interfaces: " + strings.Join(networkInterfaces, ", "))
var inputs strings.Builder
for i, ti := range m.textInputs {
info := fmt.Sprintf("%-10s|", m.inputLabels[i])
info := fmt.Sprintf("%-20s|", m.inputLabels[i])
input := inputBox.Render(info + ti.View())
inputs.WriteString(input + "\n")
}
buttons := m.renderNavButtons()
hint := hintStyle.Render("使用 Up/Down Tab 切换、Enter 确认")
hint := hintStyle.Render("Use Up/Down OR Tab Change,Enter Confirm")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
subtitle, "",
autoDetect, "",
inputs.String(), "",
buttons, "",
hint,
@@ -182,8 +175,7 @@ func (m model) internalNetworkView() string {
// dnsView DNS 配置页面
func (m model) dnsView() string {
title := titleStyle.Render("DNS 配置")
subtitle := subTitleStyle.Render("请配置 DNS 服务器")
title := titleStyle.Render("DNS Configuration")
var inputs strings.Builder
for i, ti := range m.textInputs {
@@ -193,12 +185,10 @@ func (m model) dnsView() string {
}
buttons := m.renderNavButtons()
hint := hintStyle.Render("使用 Up/Down 或 Tab 切换、Enter 确认")
hint := hintStyle.Render("Use Up/Down OR Tab Change,Enter Confirm")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
subtitle, "",
inputs.String(), "",
buttons, "",
hint,
@@ -207,71 +197,71 @@ func (m model) dnsView() string {
// summaryView 总结页面
func (m model) summaryView() string {
title := titleStyle.Render("配置总结")
subtitle := subTitleStyle.Render("请确认以下配置信息")
title := titleStyle.Render("Summary")
subtitle := subTitleStyle.Render("Please confirm the following configuration information")
summary := summaryBox.Render(fmt.Sprintf(`
+---------------------------------------------+
基本信息
+---------------------------------------------+
主机名:%-35s
国 家: %-31s
地 区:%-31s
时 区:%-38s
主 页:%-38s
+---------------------------------------------+
数据库
+---------------------------------------------+
地 址:%-38s
名 称:%-38s
软 件:%-33s
+---------------------------------------------+
公网配置
+---------------------------------------------+
接 口:%-38s
地 址: %-41s
掩 码:%-38s
网 关:%-38s
+---------------------------------------------+
内网配置
+---------------------------------------------+
接 口:%-38s
地 址: %-41s
掩 码:%-38s
+---------------------------------------------+
DNS
+---------------------------------------------+
主 DNS: %-37s
备 DNS: %-37s
+---------------------------------------------+
+----------------------------------------------------+
Basic Information
+----------------------------------------------------+
Homepage : %-38s
Hostname : %-35s
Country : %-31s
Region : %-31s
Timezone : %-38s
Homepage : %-38s
+----------------------------------------------------+
Database Configuration
+----------------------------------------------------+
Database Path : %-38s
Software : %-33s
+----------------------------------------------------+
Public Network Configuration
+----------------------------------------------------+
Public Interface : %-38s
Public IP : %-41s
Public Netmask : %-38s
Public Gateway : %-38s
+----------------------------------------------------+
Internal Network Configuration
+----------------------------------------------------+
Internal Interface: %-38s
Internal IP : %-41s
Internal Netmask : %-38s
+----------------------------------------------------+
DNS Configuration
+----------------------------------------------------+
Primary DNS : %-37s
Secondary DNS : %-37s
+----------------------------------------------------+
`,
m.config.HomePage,
m.config.Hostname,
m.config.Country,
m.config.Region,
m.config.Timezone,
m.config.HomePage,
m.config.DBAddress,
m.config.DBName,
m.config.DataAddress,
m.config.PublicInterface,
m.config.IPAddress,
m.config.Netmask,
m.config.Gateway,
m.config.PublicIPAddress,
m.config.PublicNetmask,
m.config.PublicGateway,
m.config.InternalInterface,
m.config.InternalIP,
m.config.InternalMask,
m.config.InternalIPAddress,
m.config.InternalNetmask,
m.config.DNSPrimary,
m.config.DNSSecondary,
))
var buttons string
if m.focusIndex == 0 {
buttons = selectedButton.Render("[>] 执行初始化") + " " + normalButton.Render("[ ] 取消")
buttons = selectedButton.Render("[>] Start Initialization") + " " + normalButton.Render("[ ] Cancel")
} else {
buttons = normalButton.Render("[>] 执行初始化") + " " + selectedButton.Render("[ ] 取消")
buttons = normalButton.Render("[>] Start Initialization") + " " + selectedButton.Render("[ ] Cancel")
}
hint := hintStyle.Render("使用 <- -> 或 Tab 选择Enter 确认")
hint := hintStyle.Render("Use Up/Down OR Tab Change,Enter Confirm")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
@@ -297,7 +287,7 @@ func progressView(current PageType, total int) string {
progress += " "
}
}
labels := []string{"协议", "数据", "公网", "内网", "DNS", "总结"}
labels := []string{"License", "Data", "Network", "Network", "DNS", "Summary"}
label := labelStyle.Render(labels[current])
return progressStyle.Render(progress) + " " + label
}
@@ -305,26 +295,26 @@ func progressView(current PageType, total int) string {
// successView 成功视图
func successView() string {
return containerStyle.Render(lipgloss.JoinVertical(lipgloss.Center,
successTitle.Render("初始化完成!"), "",
successMsg.Render("系统配置已保存,正在初始化..."), "",
hintStyle.Render("按任意键退出"),
successTitle.Render("Initialization Completed!"), "",
successMsg.Render("System configuration has been saved, and the system is initializing..."), "",
hintStyle.Render("Press any key to exit"),
))
}
// quitView 退出视图
func quitView() string {
return containerStyle.Render(lipgloss.JoinVertical(lipgloss.Center,
errorTitle.Render("已取消"), "",
errorMsg.Render("初始化已取消,未保存任何配置"),
errorTitle.Render("Canceled"), "",
errorMsg.Render("Initialization canceled, no configuration saved"),
))
}
// errorView 错误视图
func errorView(err error) string {
return containerStyle.Render(lipgloss.JoinVertical(lipgloss.Center,
errorTitle.Render("错误"), "",
errorTitle.Render("Error"), "",
errorMsg.Render(err.Error()), "",
hintStyle.Render(" Ctrl+C 退出"),
hintStyle.Render("Press Ctrl+C to exit"),
))
}
@@ -332,9 +322,9 @@ func errorView(err error) string {
func navButtons(m model, prev, next string) string {
var btns string
if m.currentPage == 0 {
btns = normalButton.Render(prev) + " " + selectedButton.Render(next)
btns = normalButton.Render(next) + " " + selectedButton.Render(prev)
} else {
btns = selectedButton.Render(prev) + " " + normalButton.Render(next)
btns = selectedButton.Render(next) + " " + normalButton.Render(prev)
}
return btns
}
@@ -345,22 +335,22 @@ func (m model) renderNavButtons() string {
switch m.focusType {
case FocusTypePrev:
// 焦点在"上一步"
prevBtn = selectedButton.Render("<< 上一步 >>")
nextBtn = normalButton.Render("下一步 >>")
nextBtn = normalButton.Render(" Next ")
prevBtn = selectedButton.Render(" << Prev >>")
case FocusTypeNext:
// 焦点在"下一步"
prevBtn = normalButton.Render("<< 上一步")
nextBtn = selectedButton.Render("<< 下一步 >>")
nextBtn = selectedButton.Render(" << Next >>")
prevBtn = normalButton.Render(" Prev ")
default:
// 焦点在输入框
prevBtn = normalButton.Render("<< 上一步")
nextBtn = normalButton.Render("下一步 >>")
nextBtn = normalButton.Render(" Next ")
prevBtn = normalButton.Render(" Prev ")
}
return lipgloss.JoinHorizontal(
lipgloss.Center,
prevBtn,
" ",
nextBtn,
" ",
prevBtn,
)
}

View File

@@ -41,8 +41,6 @@ var normalButton = lipgloss.NewStyle().
Foreground(lipgloss.Color("#666666")) // 深灰色,更暗
var selectedButton = lipgloss.NewStyle().
Padding(0, 2).
Foreground(lipgloss.Color("#3d4747ff")). // 亮绿色
Bold(true)
// 输入框样式