Tui 重构代码逻辑

This commit is contained in:
2026-02-27 22:52:15 +08:00
parent 3a5f5ddd5d
commit d4e214fe23
8 changed files with 934 additions and 718 deletions

321
pkg/wizard/focused.go Normal file
View File

@@ -0,0 +1,321 @@
package wizard
import (
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// Focusable 定义可聚焦组件的通用接口
type Focusable interface {
// Focus 激活焦点(比如输入框闪烁光标、按钮高亮)
Focus() tea.Cmd
// Blur 失活焦点(取消高亮/闪烁)
Blur()
// IsFocused 判断是否处于焦点状态
IsFocused() bool
// View 渲染组件(和 bubbletea 统一)
View() string
}
// --------------- 为常用组件实现 Focusable 接口 ---------------
// TextInput 适配 bubbles/textinput
type TextInput struct {
textinput.Model
focused bool
}
func NewTextInput(placeholder string, defaultValue string) *TextInput {
ti := textinput.New()
ti.Placeholder = placeholder
ti.SetValue(defaultValue)
ti.Focus()
return &TextInput{Model: ti, focused: true}
}
func (t *TextInput) Focus() tea.Cmd {
t.focused = true
return t.Model.Focus()
}
func (t *TextInput) Blur() {
t.focused = false
t.Model.Blur()
}
func (t *TextInput) IsFocused() bool {
return t.focused
}
// Button 适配 bubbles/button
type Button struct {
label string
focused bool
buttonBlur lipgloss.Style
buttonFocus lipgloss.Style
}
func NewButton(label string) *Button {
return &Button{
label: label,
focused: false,
buttonBlur: btnBaseStyle,
buttonFocus: btnSelectedStyle,
}
}
func (b *Button) Focus() tea.Cmd {
b.focused = true
b.buttonBlur = b.buttonFocus
return nil
}
func (b *Button) Blur() {
b.focused = false
b.buttonBlur = btnBaseStyle
}
func (b *Button) IsFocused() bool {
return b.focused
}
func (b *Button) View() string {
if b.focused {
return b.buttonFocus.Render(b.label)
}
return b.buttonBlur.Render(b.label)
}
// List 适配 bubbles/list
type List struct {
list.Model
focused bool
}
func NewList(items []list.Item) List {
l := list.New(items, list.NewDefaultDelegate(), 0, 0)
l.SetShowHelp(false)
return List{Model: l, focused: false}
}
func (l *List) Focus() tea.Cmd {
l.focused = true
return nil
}
func (l *List) Blur() {
l.focused = false
}
func (l *List) IsFocused() bool {
return l.focused
}
// FocusManager 焦点管理器
type FocusManager struct {
// 所有可聚焦组件key=唯一标识,比如 "form1.ip_input"、"form1.next_btn"
components map[string]Focusable
// 组件切换顺序(按这个顺序切换焦点)
order []string
// 当前焦点组件的标识
currentFocusID string
// 是否循环切换(到最后一个后回到第一个)
loop bool
}
// NewFocusManager 创建焦点管理器
func NewFocusManager(loop bool) *FocusManager {
return &FocusManager{
components: make(map[string]Focusable),
order: make([]string, 0),
currentFocusID: "",
loop: loop,
}
}
// Register 注册可聚焦组件(指定标识和切换顺序)
func (fm *FocusManager) Register(id string, comp Focusable) {
// 防御性检查:避免 components 为空导致 panic
if fm.components == nil {
fm.components = make(map[string]Focusable)
}
// 避免重复注册
if _, exists := fm.components[id]; exists {
return
}
fm.components[id] = comp
fm.order = append(fm.order, id)
// 如果是第一个注册的组件,默认聚焦
if fm.currentFocusID == "" {
fm.currentFocusID = id
comp.Focus()
}
}
// Next 切换到下一个组件
func (fm *FocusManager) Next() tea.Cmd {
if len(fm.order) == 0 {
return nil
}
// 1. 找到当前组件的索引
currentIdx := -1
for i, id := range fm.order {
if id == fm.currentFocusID {
currentIdx = i
break
}
}
// 2. 计算下一个索引
nextIdx := currentIdx + 1
if fm.loop && nextIdx >= len(fm.order) {
nextIdx = 0
}
if nextIdx >= len(fm.order) {
return nil // 不循环则到最后一个停止
}
// 3. 切换焦点(当前组件失活,下一个激活)
fm.components[fm.currentFocusID].Blur()
nextID := fm.order[nextIdx]
fm.currentFocusID = nextID
return fm.components[nextID].Focus()
}
// Prev 切换到上一个组件
func (fm *FocusManager) Prev() tea.Cmd {
if len(fm.order) == 0 {
return nil
}
currentIdx := -1
for i, id := range fm.order {
if id == fm.currentFocusID {
currentIdx = i
break
}
}
prevIdx := currentIdx - 1
if fm.loop && prevIdx < 0 {
prevIdx = len(fm.order) - 1
}
if prevIdx < 0 {
return nil
}
fm.components[fm.currentFocusID].Blur()
prevID := fm.order[prevIdx]
fm.currentFocusID = prevID
return fm.components[prevID].Focus()
}
// GetCurrent 获取当前焦点组件
func (fm *FocusManager) GetCurrent() (Focusable, bool) {
comp, exists := fm.components[fm.currentFocusID]
return comp, exists
}
// HandleInput 统一处理焦点切换输入(比如 Tab/Shift+Tab
func (fm *FocusManager) HandleInput(msg tea.KeyMsg) tea.Cmd {
switch msg.String() {
case "tab": // Tab 下一个
return fm.Next()
case "shift+tab": // Shift+Tab 上一个
return fm.Prev()
case "left": // Left 上一个
return fm.Prev()
case "right": // Right 下一个
return fm.Next()
default:
return nil
}
}
func (m *model) switchPage(targetPage PageType) tea.Cmd {
// 边界检查(不能超出 1-6 页面)
if targetPage < PageAgreement || targetPage > PageSummary {
return nil
}
// 更新当前页面
m.currentPage = targetPage
// 初始化新页面的焦点
m.initPageFocus(targetPage)
// 返回空指令(或返回第一个组件的Focus命令)
return nil
}
func (m *model) initPageFocus(page PageType) {
m.focusManager = NewFocusManager(true)
// 获取当前页面的组件
pageComps, exists := m.pageComponents[page]
if !exists {
return
}
// 按 [业务逻辑顺序] 注册组件 (决定Tab切换的顺序)
var componentOrder []string
// 按页面类型定义不同的注册顺序
switch page {
case PageAgreement:
componentOrder = []string{"accept_btn", "reject_btn"}
case PageData:
componentOrder = []string{
"Homepage_input",
"Hostname_input",
"Country_input",
"Region_input",
"Timezone_input",
"DBPath_input",
"Software_input",
"next_btn",
"prev_btn",
}
case PagePublicNetwork:
componentOrder = []string{
"PublicInterface_input",
"PublicIPAddress_input",
"PublicNetmask_input",
"PublicGateway_input",
"next_btn",
"prev_btn",
}
case PageInternalNetwork:
componentOrder = []string{
"InternalInterface_input",
"InternalIPAddress_input",
"InternalNetmask_input",
"next_btn",
"prev_btn",
}
case PageDNS:
componentOrder = []string{
"Pri_DNS_input",
"Sec_DNS_input",
"next_btn",
"prev_btn",
}
case PageSummary:
componentOrder = []string{"confirm_btn", "cancel_btn"}
}
// 注册组件到焦点管理器(按顺序)
for _, compID := range componentOrder {
if comp, exists := pageComps[compID]; exists {
m.focusManager.Register(compID, comp)
}
}
}