Tui 重构代码逻辑
This commit is contained in:
@@ -7,19 +7,26 @@ import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// PageType 页面类型
|
||||
type PageType int
|
||||
|
||||
// 总页码
|
||||
const TotalPages = 6
|
||||
|
||||
// Config 系统配置结构
|
||||
type Config struct {
|
||||
// 协议
|
||||
AgreementAccepted bool `json:"agreement_accepted"`
|
||||
License string `json:"license"`
|
||||
AgreementAccepted bool `json:"agreement_accepted"`
|
||||
|
||||
// 数据接收
|
||||
Hostname string `json:"hostname"`
|
||||
Country string `json:"country"`
|
||||
Region string `json:"region"`
|
||||
Timezone string `json:"timezone"`
|
||||
HomePage string `json:"homepage"`
|
||||
DBAddress string `json:"db_address"`
|
||||
DataAddress string `json:"data_address"`
|
||||
Hostname string `json:"hostname"`
|
||||
Country string `json:"country"`
|
||||
Region string `json:"region"`
|
||||
Timezone string `json:"timezone"`
|
||||
HomePage string `json:"homepage"`
|
||||
DBAddress string `json:"db_address"`
|
||||
Software string `json:"software"`
|
||||
|
||||
// 公网设置
|
||||
PublicInterface string `json:"public_interface"`
|
||||
@@ -37,9 +44,6 @@ type Config struct {
|
||||
DNSSecondary string `json:"dns_secondary"`
|
||||
}
|
||||
|
||||
// PageType 页面类型
|
||||
type PageType int
|
||||
|
||||
const (
|
||||
PageAgreement PageType = iota
|
||||
PageData
|
||||
@@ -49,29 +53,24 @@ const (
|
||||
PageSummary
|
||||
)
|
||||
|
||||
const (
|
||||
FocusTypeInput int = 0
|
||||
FocusTypePrev int = 1
|
||||
FocusTypeNext int = 2
|
||||
)
|
||||
|
||||
// model TUI 主模型
|
||||
type model struct {
|
||||
config Config
|
||||
currentPage PageType
|
||||
config Config // 全局配置
|
||||
currentPage PageType // 当前页面
|
||||
totalPages int
|
||||
networkInterfaces []string // 所有系统网络接口
|
||||
textInputs []textinput.Model
|
||||
inputLabels []string // 存储标签
|
||||
focusIndex int
|
||||
focusType int // 0=输入框, 1=上一步按钮, 2=下一步按钮
|
||||
agreementIdx int // 0=拒绝,1=接受
|
||||
textInputs []textinput.Model // 当前页面的输入框
|
||||
networkInterfaces []string // 所有系统网络接口
|
||||
width int
|
||||
height int
|
||||
err error
|
||||
quitting bool
|
||||
done bool
|
||||
force bool
|
||||
|
||||
// 核心1: 按页面分组存储所有组件(6个页面 + 6个map)
|
||||
pageComponents map[PageType]map[string]Focusable
|
||||
// 核心2:焦点管理器(每次切换页面时重置)
|
||||
focusManager *FocusManager
|
||||
}
|
||||
|
||||
// defaultConfig 返回默认配置
|
||||
@@ -97,13 +96,14 @@ func defaultConfig() Config {
|
||||
}
|
||||
|
||||
return Config{
|
||||
License: "This test license is for testing purposes only. Do not use it in production.",
|
||||
Hostname: "cluster.hpc.org",
|
||||
Country: "China",
|
||||
Region: "Beijing",
|
||||
Timezone: "Asia/Shanghai",
|
||||
HomePage: "www.sunhpc.com",
|
||||
DBAddress: "/var/lib/sunhpc/sunhpc.db",
|
||||
DataAddress: "/export/sunhpc",
|
||||
Software: "/export/sunhpc",
|
||||
PublicInterface: defaultPublicInterface,
|
||||
PublicIPAddress: "",
|
||||
PublicNetmask: "",
|
||||
@@ -119,18 +119,76 @@ func defaultConfig() Config {
|
||||
// initialModel 初始化模型
|
||||
func initialModel() model {
|
||||
cfg := defaultConfig()
|
||||
|
||||
// 1. 初始化所有页面组件(6个页面)
|
||||
pageComponents := make(map[PageType]map[string]Focusable)
|
||||
|
||||
// ------------------ 页面1:协议页面 --------------------
|
||||
page1Comps := make(map[string]Focusable)
|
||||
page1Comps["accept_btn"] = NewButton("接受协议")
|
||||
page1Comps["reject_btn"] = NewButton("拒绝协议")
|
||||
pageComponents[PageAgreement] = page1Comps
|
||||
|
||||
// ------------------ 页面2:基础信息页面 --------------------
|
||||
page2Comps := make(map[string]Focusable)
|
||||
page2Comps["Homepage_input"] = NewTextInput("Homepage", cfg.HomePage)
|
||||
page2Comps["Hostname_input"] = NewTextInput("Hostname", cfg.Hostname)
|
||||
page2Comps["Country_input"] = NewTextInput("Country", cfg.Country)
|
||||
page2Comps["Region_input"] = NewTextInput("Region", cfg.Region)
|
||||
page2Comps["Timezone_input"] = NewTextInput("Timezone", cfg.Timezone)
|
||||
page2Comps["DBPath_input"] = NewTextInput("DBPath", cfg.DBAddress)
|
||||
page2Comps["Software_input"] = NewTextInput("Software", cfg.Software)
|
||||
page2Comps["next_btn"] = NewButton("下一步")
|
||||
page2Comps["prev_btn"] = NewButton("上一步")
|
||||
pageComponents[PageData] = page2Comps
|
||||
|
||||
// ------------------ 页面3:公网网络页面 --------------------
|
||||
page3Comps := make(map[string]Focusable)
|
||||
page3Comps["PublicInterface_input"] = NewTextInput("PublicInterface", cfg.PublicInterface)
|
||||
page3Comps["PublicIPAddress_input"] = NewTextInput("PublicIPAddress", cfg.PublicIPAddress)
|
||||
page3Comps["PublicNetmask_input"] = NewTextInput("PublicNetmask", cfg.PublicNetmask)
|
||||
page3Comps["PublicGateway_input"] = NewTextInput("PublicGateway", cfg.PublicGateway)
|
||||
page3Comps["next_btn"] = NewButton("下一步")
|
||||
page3Comps["prev_btn"] = NewButton("上一步")
|
||||
pageComponents[PagePublicNetwork] = page3Comps
|
||||
|
||||
// ------------------ 页面4:内网网络页面 --------------------
|
||||
page4Comps := make(map[string]Focusable)
|
||||
page4Comps["InternalInterface_input"] = NewTextInput("InternalInterface", cfg.InternalInterface)
|
||||
page4Comps["InternalIPAddress_input"] = NewTextInput("InternalIPAddress", cfg.InternalIPAddress)
|
||||
page4Comps["InternalNetmask_input"] = NewTextInput("InternalNetmask", cfg.InternalNetmask)
|
||||
page4Comps["next_btn"] = NewButton("下一步")
|
||||
page4Comps["prev_btn"] = NewButton("上一步")
|
||||
pageComponents[PageInternalNetwork] = page4Comps
|
||||
|
||||
// ------------------ 页面5:DNS页面 --------------------
|
||||
page5Comps := make(map[string]Focusable)
|
||||
page5Comps["Pri_DNS_input"] = NewTextInput("Pri DNS", cfg.DNSPrimary)
|
||||
page5Comps["Sec_DNS_input"] = NewTextInput("Sec DNS", cfg.DNSSecondary)
|
||||
page5Comps["next_btn"] = NewButton("下一步")
|
||||
page5Comps["prev_btn"] = NewButton("上一步")
|
||||
pageComponents[PageDNS] = page5Comps
|
||||
|
||||
// ------------------ 页面6:Summary页面 --------------------
|
||||
page6Comps := make(map[string]Focusable)
|
||||
page6Comps["confirm_btn"] = NewButton("Confirm")
|
||||
page6Comps["cancel_btn"] = NewButton("Cancel")
|
||||
pageComponents[PageSummary] = page6Comps
|
||||
|
||||
// 创建焦点管理器(初始化聚焦页)
|
||||
fm := NewFocusManager(true)
|
||||
|
||||
// 初始化模型
|
||||
m := model{
|
||||
config: cfg,
|
||||
totalPages: 6,
|
||||
textInputs: make([]textinput.Model, 0),
|
||||
inputLabels: make([]string, 0),
|
||||
agreementIdx: 1,
|
||||
focusIndex: 0,
|
||||
focusType: 0, // 0=输入框, 1=上一步按钮, 2=下一步按钮
|
||||
width: 80,
|
||||
height: 24,
|
||||
config: cfg,
|
||||
totalPages: 6,
|
||||
currentPage: PageAgreement, // 初始化聚焦在协议页面
|
||||
pageComponents: pageComponents,
|
||||
focusManager: fm,
|
||||
}
|
||||
m.initPageInputs()
|
||||
|
||||
// 初始化当前页 (页1) 的焦点
|
||||
m.initPageFocus(m.currentPage)
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -141,8 +199,6 @@ func (m model) Init() tea.Cmd {
|
||||
|
||||
// Update 处理消息更新
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
@@ -150,205 +206,104 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
|
||||
case "esc":
|
||||
if m.currentPage > 0 {
|
||||
return m.prevPage()
|
||||
}
|
||||
// 1. 焦点切换(Tab/Shift+Tab)交给管理器处理
|
||||
case "tab", "shift+tab", "left", "right":
|
||||
cmd := m.focusManager.HandleInput(msg)
|
||||
return m, cmd
|
||||
|
||||
// 2. 回车键:处理当前焦点组件的点击/确认
|
||||
case "enter":
|
||||
return m.handleEnter()
|
||||
currentCompID := m.focusManager.currentFocusID
|
||||
switch currentCompID {
|
||||
// 页1:accept → 进入页2
|
||||
case "accept_btn":
|
||||
return m, m.switchPage(PageData)
|
||||
// 页1:reject → 退出程序
|
||||
case "reject_btn":
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
|
||||
case "tab", "shift+tab", "up", "down", "left", "right":
|
||||
return m.handleNavigation(msg)
|
||||
}
|
||||
// 通用上一页/下一页逻辑
|
||||
case "prev_btn":
|
||||
return m, m.switchPage(m.currentPage - 1)
|
||||
case "next_btn":
|
||||
return m, m.switchPage(m.currentPage + 1)
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
// 页6:确认配置 → 退出并保存
|
||||
case "confirm_btn":
|
||||
m.done = true
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
|
||||
// 动态调整容器宽度
|
||||
/*
|
||||
if msg.Width > 100 {
|
||||
containerStyle = containerStyle.Width(90)
|
||||
} else if msg.Width > 80 {
|
||||
containerStyle = containerStyle.Width(70)
|
||||
} else {
|
||||
containerStyle = containerStyle.Width(msg.Width - 10)
|
||||
case "cancel_btn":
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
*/
|
||||
|
||||
// ✅ 动态计算容器宽度(终端宽度的 80%)
|
||||
containerWidth := msg.Width * 80 / 100
|
||||
|
||||
// ✅ 重新设置容器样式宽度
|
||||
containerStyle = containerStyle.Width(containerWidth)
|
||||
|
||||
// 动态设置协议框宽度(容器宽度的 90%)
|
||||
agreementWidth := containerWidth * 80 / 100
|
||||
agreementBox = agreementBox.Width(agreementWidth)
|
||||
|
||||
// 动态设置输入框宽度
|
||||
inputWidth := containerWidth * 60 / 100
|
||||
if inputWidth < 40 {
|
||||
inputWidth = 40
|
||||
}
|
||||
inputBox = inputBox.Width(inputWidth)
|
||||
|
||||
// 动态设置总结框宽度
|
||||
summaryWidth := containerWidth * 90 / 100
|
||||
summaryBox = summaryBox.Width(summaryWidth)
|
||||
|
||||
return m, nil
|
||||
// 其他消息(窗口大小、输入框输入等)...
|
||||
}
|
||||
|
||||
// 更新当前焦点的输入框
|
||||
if len(m.textInputs) > 0 && m.focusIndex < len(m.textInputs) {
|
||||
var cmd tea.Cmd
|
||||
m.textInputs[m.focusIndex], cmd = m.textInputs[m.focusIndex].Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
// 处理当前焦点组件的内部更新(比如输入框打字、列表选值)
|
||||
currentComp, exists := m.focusManager.GetCurrent()
|
||||
if exists {
|
||||
// 不同组件的内部更新逻辑(示例)
|
||||
switch comp := currentComp.(type) {
|
||||
case *TextInput:
|
||||
// 输入框更新
|
||||
newTI, cmd := comp.Model.Update(msg)
|
||||
comp.Model = newTI
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
// 保存输入值到全局配置(示例:主机名)
|
||||
switch m.focusManager.currentFocusID {
|
||||
// 页2:基础信息
|
||||
case "Homepage_input":
|
||||
m.config.HomePage = comp.Value()
|
||||
case "Hostname_input":
|
||||
m.config.Hostname = comp.Value()
|
||||
case "Country_input":
|
||||
m.config.Country = comp.Value()
|
||||
case "Region_input":
|
||||
m.config.Region = comp.Value()
|
||||
case "Timezone_input":
|
||||
m.config.Timezone = comp.Value()
|
||||
case "DBPath_input":
|
||||
m.config.DBAddress = comp.Value()
|
||||
case "Software_input":
|
||||
m.config.Software = comp.Value()
|
||||
|
||||
// handleEnter 处理回车事件
|
||||
func (m *model) handleEnter() (tea.Model, tea.Cmd) {
|
||||
switch m.currentPage {
|
||||
case PageAgreement:
|
||||
if m.agreementIdx == 1 {
|
||||
m.config.AgreementAccepted = true
|
||||
return m.nextPage()
|
||||
} else {
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
// 页3:公网网络
|
||||
case "PublicInterface_input":
|
||||
m.config.PublicInterface = comp.Value()
|
||||
case "PublicIPAddress_input":
|
||||
m.config.PublicIPAddress = comp.Value()
|
||||
case "PublicNetmask_input":
|
||||
m.config.PublicNetmask = comp.Value()
|
||||
case "PublicGateway_input":
|
||||
m.config.PublicGateway = comp.Value()
|
||||
|
||||
case PageData, PagePublicNetwork, PageInternalNetwork, PageDNS:
|
||||
// 根据焦点类型执行不同操作
|
||||
switch m.focusType {
|
||||
case FocusTypeInput:
|
||||
// 在输入框上,保存并下一页
|
||||
m.saveCurrentPage()
|
||||
return m.nextPage()
|
||||
case FocusTypePrev:
|
||||
// 上一步按钮,返回上一页
|
||||
return m.prevPage()
|
||||
case FocusTypeNext:
|
||||
// 下一步按钮,切换到下一页
|
||||
m.saveCurrentPage()
|
||||
return m.nextPage()
|
||||
}
|
||||
// 页4:内网网络
|
||||
case "InternalInterface_input":
|
||||
m.config.InternalInterface = comp.Value()
|
||||
case "InternalIPAddress_input":
|
||||
m.config.InternalIPAddress = comp.Value()
|
||||
case "InternalNetmask_input":
|
||||
m.config.InternalNetmask = comp.Value()
|
||||
|
||||
// 页5:DNS
|
||||
case "Pri_DNS_input":
|
||||
m.config.DNSPrimary = comp.Value()
|
||||
case "Sec_DNS_input":
|
||||
m.config.DNSSecondary = comp.Value()
|
||||
|
||||
case PageSummary:
|
||||
switch m.focusIndex {
|
||||
case 0: // 执行
|
||||
m.done = true
|
||||
if err := m.saveConfig(); err != nil {
|
||||
m.err = err
|
||||
return m, nil
|
||||
}
|
||||
return m, tea.Quit
|
||||
case 1: // 取消
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
return m, cmd
|
||||
|
||||
case *List:
|
||||
// 列表更新
|
||||
newList, cmd := comp.Model.Update(msg)
|
||||
comp.Model = newList
|
||||
return m, cmd
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// handleNavigation 处理导航
|
||||
func (m *model) handleNavigation(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
// debug
|
||||
//fmt.Fprintf(os.Stderr, "DEBUG: key=%s page=%d\n", msg.String(), m.currentPage)
|
||||
|
||||
switch m.currentPage {
|
||||
case PageAgreement:
|
||||
switch msg.String() {
|
||||
case "left", "right", "tab", "shift+tab", "up", "down":
|
||||
m.agreementIdx = 1 - m.agreementIdx
|
||||
}
|
||||
|
||||
case PageSummary:
|
||||
switch msg.String() {
|
||||
case "left", "right", "tab", "shift+tab":
|
||||
m.focusIndex = 1 - m.focusIndex
|
||||
}
|
||||
|
||||
default:
|
||||
// 输入框页面: 支持输入框和按钮之间切换
|
||||
// totalFocusable := len(m.textInputs) + 2
|
||||
|
||||
switch msg.String() {
|
||||
case "down", "tab":
|
||||
// 当前在输入框
|
||||
switch m.focusType {
|
||||
case FocusTypeInput:
|
||||
if m.focusIndex < len(m.textInputs)-1 {
|
||||
// 切换到下一个输入框
|
||||
m.textInputs[m.focusIndex].Blur()
|
||||
m.focusIndex++
|
||||
m.textInputs[m.focusIndex].Focus()
|
||||
} else {
|
||||
// 最后一个输入框,切换到“下一步”按钮
|
||||
m.textInputs[m.focusIndex].Blur()
|
||||
m.focusIndex = 0
|
||||
m.focusType = FocusTypeNext // 下一步按钮
|
||||
}
|
||||
case FocusTypePrev:
|
||||
// 当前在“上一步”按钮,切换到第一个输入框
|
||||
m.focusType = FocusTypeInput
|
||||
m.focusIndex = 0
|
||||
m.textInputs[0].Focus()
|
||||
case FocusTypeNext:
|
||||
// 当前在“下一步”按钮,切换到“上一步”按钮
|
||||
m.focusType = FocusTypePrev
|
||||
}
|
||||
case "up", "shift+tab":
|
||||
// 当前在输入框
|
||||
switch m.focusType {
|
||||
case FocusTypeInput:
|
||||
if m.focusIndex > 0 {
|
||||
// 切换到上一个输入框
|
||||
m.textInputs[m.focusIndex].Blur()
|
||||
m.focusIndex--
|
||||
m.textInputs[m.focusIndex].Focus()
|
||||
} else {
|
||||
// 第一个输入框,切换到“上一步”按钮
|
||||
m.textInputs[m.focusIndex].Blur()
|
||||
m.focusIndex = 0
|
||||
m.focusType = FocusTypePrev // 上一步按钮
|
||||
}
|
||||
case FocusTypeNext:
|
||||
// 当前在“下一步”按钮,切换到最后一个输入框
|
||||
m.focusType = FocusTypeInput
|
||||
m.focusIndex = len(m.textInputs) - 1
|
||||
m.textInputs[m.focusIndex].Focus()
|
||||
case FocusTypePrev:
|
||||
// 当前在“上一步”按钮,切换到“下一步”按钮
|
||||
m.focusType = FocusTypeNext
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// nextPage 下一页
|
||||
func (m *model) nextPage() (tea.Model, tea.Cmd) {
|
||||
if m.currentPage < PageSummary {
|
||||
m.currentPage++
|
||||
m.focusIndex = 0
|
||||
m.initPageInputs()
|
||||
}
|
||||
return m, textinput.Blink
|
||||
}
|
||||
|
||||
// prevPage 上一页
|
||||
func (m *model) prevPage() (tea.Model, tea.Cmd) {
|
||||
if m.currentPage > 0 {
|
||||
m.saveCurrentPage()
|
||||
m.currentPage--
|
||||
m.focusIndex = 0
|
||||
m.initPageInputs()
|
||||
}
|
||||
return m, textinput.Blink
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user