From bf6f8aa1f6b6e8ebac7aaf7c11eb96afe2730fa0 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 21 Aug 2026 10:21:13 +0800 Subject: [PATCH] feat(node): organize console into tabs --- CHANGELOG.md | 2 +- tests/test_windows_node_source.py | 13 ++- .../Zcbot.WindowsNode/ConfigurationForm.cs | 108 +++++++++++++++--- 3 files changed, 105 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6f60f0..c4f0f31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - 修复国际旗舰模型输出思考过程时偶尔无法及时停止的问题;即使模型暂时没有返回新的流式片段,点击“停止”也会迅速中断当前回答。 -- Windows Node 主窗口重新整理为更宽、更清晰的桌面控制台:节点状态、能力概览和维护操作分层展示,专业软件改为独立配置行和通栏地址框,Origin、ANSYS、Blender 均可手工选择位置;软件卡的操作按钮在高 DPI 下也会完整显示,Origin 自动检测也会在 COM 路径无效时继续查找注册表和标准安装目录。 +- Windows Node 主窗口重新整理为更宽、更清晰的桌面控制台:节点概览、专业软件、本机任务和运行设置改用独立标签页,节点状态、能力概览和维护操作分层展示;专业软件使用独立配置行和通栏地址框,Origin、ANSYS、Blender 均可手工选择位置,软件卡的操作按钮在高 DPI 下也会完整显示;Origin 自动检测也会在 COM 路径无效时继续查找注册表和标准安装目录。 - Windows Node 现在可在“专业软件”区域自动检测或手工指定应用位置,并分别安装、更新 Origin、ANSYS、Blender 的独立运行环境;只装有部分专业软件的节点不再需要处理无关软件。 diff --git a/tests/test_windows_node_source.py b/tests/test_windows_node_source.py index fac19a4..a8ff0d4 100644 --- a/tests/test_windows_node_source.py +++ b/tests/test_windows_node_source.py @@ -187,7 +187,8 @@ class WindowsNodeSourceTests(unittest.TestCase): def test_configuration_window_is_resizable_and_dpi_safe(self) -> None: form = (PROJECT / "ConfigurationForm.cs").read_text(encoding="utf-8") - self.assertIn("ClientSize = new Size(1100, 760)", form) + self.assertIn("ClientSize = new Size(1320, 820)", form) + self.assertIn("MinimumSize = new Size(1040, 700)", form) self.assertIn("FormBorderStyle.Sizable", form) self.assertIn("AutoScaleMode.Dpi", form) self.assertIn("AutoScroll = true", form) @@ -196,7 +197,15 @@ class WindowsNodeSourceTests(unittest.TestCase): self.assertIn("成功注册一次后立即失效", form) self.assertIn("CreateCard", form) self.assertIn("注册并连接", form) - self.assertIn("ContentWidth = 1020", form) + self.assertIn("ContentWidth = 1180", form) + self.assertIn('AddTab(tabs, "节点概览")', form) + self.assertIn('AddTab(tabs, "专业软件")', form) + self.assertIn('AddTab(tabs, "本机任务")', form) + self.assertIn('AddTab(tabs, "运行设置")', form) + self.assertIn("SizeMode = TabSizeMode.Fixed", form) + self.assertIn("new NavigationTabControl", form) + self.assertIn("DrawMode = TabDrawMode.OwnerDrawFixed", form) + self.assertIn("tab.Controls.Add(content)", form) self.assertIn("CreateSubsectionTitle(\"能力概览\")", form) self.assertIn("panel.SetColumnSpan(path, 2)", form) self.assertIn("BorderedTableLayoutPanel", form) diff --git a/windows-node/Zcbot.WindowsNode/ConfigurationForm.cs b/windows-node/Zcbot.WindowsNode/ConfigurationForm.cs index 8838497..f94d2b4 100644 --- a/windows-node/Zcbot.WindowsNode/ConfigurationForm.cs +++ b/windows-node/Zcbot.WindowsNode/ConfigurationForm.cs @@ -8,7 +8,7 @@ namespace Zcbot.WindowsNode; internal sealed class ConfigurationForm : Form { - private const int ContentWidth = 1020; + private const int ContentWidth = 1180; private readonly TextBox server = CreateTextBox("http://127.0.0.1:8765"); private readonly TextBox nodeName = CreateTextBox(Environment.MachineName.ToLowerInvariant()); @@ -76,8 +76,8 @@ internal sealed class ConfigurationForm : Form adapters = NodeAdapterRegistry.CreateDefault(jobInbox); Text = "zcbot Windows Node"; AutoScaleMode = AutoScaleMode.Dpi; - ClientSize = new Size(1100, 760); - MinimumSize = new Size(900, 680); + ClientSize = new Size(1320, 820); + MinimumSize = new Size(1040, 700); StartPosition = FormStartPosition.CenterScreen; Font = new Font("Microsoft YaHei UI", 9); FormBorderStyle = FormBorderStyle.Sizable; @@ -99,14 +99,14 @@ internal sealed class ConfigurationForm : Form var page = new TableLayoutPanel { - AutoSize = true, - AutoSizeMode = AutoSizeMode.GrowAndShrink, - Dock = DockStyle.Top, + Dock = DockStyle.Fill, ColumnCount = 1, - RowCount = 8, + RowCount = 2, BackColor = BackColor, }; page.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); + page.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + page.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); shell.Controls.Add(page, 1, 0); var heading = new TableLayoutPanel @@ -114,7 +114,7 @@ internal sealed class ConfigurationForm : Form AutoSize = true, Dock = DockStyle.Top, ColumnCount = 1, - Margin = new Padding(4, 0, 4, 22), + Margin = new Padding(4, 0, 4, 16), }; heading.Controls.Add(new Label { @@ -136,6 +136,21 @@ internal sealed class ConfigurationForm : Form heading.Controls.Add(headingHint); page.Controls.Add(heading); + var tabs = new NavigationTabControl + { + Dock = DockStyle.Fill, + Margin = new Padding(0), + Font = new Font("Microsoft YaHei UI", 9, FontStyle.Bold), + ItemSize = new Size(142, 38), + Padding = new Point(18, 6), + SizeMode = TabSizeMode.Fixed, + }; + var overviewPage = AddTab(tabs, "节点概览"); + var softwarePage = AddTab(tabs, "专业软件"); + var jobsPage = AddTab(tabs, "本机任务"); + var settingsPage = AddTab(tabs, "运行设置"); + page.Controls.Add(tabs, 0, 1); + var statusCard = CreateCard(); var statusHeader = new TableLayoutPanel { @@ -159,7 +174,7 @@ internal sealed class ConfigurationForm : Form resetActions.Controls.Add(copyDiagnostics); resetActions.Controls.Add(resetIdentity); statusCard.Controls.Add(resetActions); - page.Controls.Add(statusCard); + overviewPage.Controls.Add(statusCard); var softwareCard = CreateCard(); softwareCard.Controls.Add(CreateSectionTitle("专业软件")); @@ -169,7 +184,7 @@ internal sealed class ConfigurationForm : Form { softwareCard.Controls.Add(CreateSoftwarePanel(definition)); } - page.Controls.Add(softwareCard); + softwarePage.Controls.Add(softwareCard); ansysAcceptanceCard = CreateCard(); ansysAcceptanceCard.Controls.Add(CreateSectionTitle("ANSYS Mechanical 真机验收")); @@ -181,7 +196,7 @@ internal sealed class ConfigurationForm : Form acceptanceActions.Controls.Add(runAnsysAcceptance); acceptanceActions.Controls.Add(enableAnsysGate); ansysAcceptanceCard.Controls.Add(acceptanceActions); - page.Controls.Add(ansysAcceptanceCard); + softwarePage.Controls.Add(ansysAcceptanceCard); var jobsCard = CreateCard(); jobsCard.Controls.Add(CreateSectionTitle("本机任务")); @@ -190,7 +205,7 @@ internal sealed class ConfigurationForm : Form jobsCard.Controls.Add(jobSummary); jobsCard.Controls.Add(jobGrid); jobsCard.Controls.Add(jobDetail); - page.Controls.Add(jobsCard); + jobsPage.Controls.Add(jobsCard); registrationCard = CreateCard(); registrationCard.Controls.Add(CreateSectionTitle("首次注册")); @@ -202,7 +217,7 @@ internal sealed class ConfigurationForm : Form var registerActions = CreateActions(); registerActions.Controls.Add(register); registrationCard.Controls.Add(registerActions); - page.Controls.Add(registrationCard); + overviewPage.Controls.Add(registrationCard); var runtimeCard = CreateCard(); runtimeCard.Controls.Add(CreateSectionTitle("运行设置")); @@ -219,12 +234,12 @@ internal sealed class ConfigurationForm : Form runtimeCard.Controls.Add(dataRootStatus); runtimeCard.Controls.Add(CreateHint( "关闭此窗口后,节点仍在系统托盘运行。右键托盘图标可以立即重连或退出。")); - page.Controls.Add(runtimeCard); + settingsPage.Controls.Add(runtimeCard); var securityNote = CreateHint( "安全说明:Node Token 由 Windows DPAPI 加密保存,不在界面显示,也不会写入日志。"); securityNote.Margin = new Padding(4, 8, 4, 0); - page.Controls.Add(securityNote); + settingsPage.Controls.Add(securityNote); register.Click += async (_, _) => await RegisterAsync(); reconnect.Click += (_, _) => ReconnectRequested?.Invoke(); @@ -956,6 +971,31 @@ internal sealed class ConfigurationForm : Form return card; } + private static TableLayoutPanel AddTab(TabControl tabs, string text) + { + var tab = new TabPage + { + Text = text, + AutoScroll = true, + BackColor = Color.FromArgb(246, 248, 252), + Padding = new Padding(18, 20, 18, 20), + UseVisualStyleBackColor = false, + }; + var content = new TableLayoutPanel + { + AutoSize = true, + AutoSizeMode = AutoSizeMode.GrowAndShrink, + Dock = DockStyle.Top, + ColumnCount = 1, + BackColor = tab.BackColor, + Margin = new Padding(0), + }; + content.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); + tab.Controls.Add(content); + tabs.TabPages.Add(tab); + return content; + } + private static Label CreateSectionTitle(string text) => new() { Text = text, @@ -1280,4 +1320,42 @@ internal sealed class ConfigurationForm : Form Math.Max(0, ClientSize.Height - 1)); } } + + private sealed class NavigationTabControl : TabControl + { + internal NavigationTabControl() + { + DrawMode = TabDrawMode.OwnerDrawFixed; + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + } + + protected override void OnDrawItem(DrawItemEventArgs eventArgs) + { + var selected = eventArgs.Index == SelectedIndex; + var bounds = eventArgs.Bounds; + using var background = new SolidBrush(selected + ? Color.White + : Color.FromArgb(241, 245, 249)); + eventArgs.Graphics.FillRectangle(background, bounds); + TextRenderer.DrawText( + eventArgs.Graphics, + TabPages[eventArgs.Index].Text, + Font, + bounds, + selected ? Color.FromArgb(29, 78, 216) : Color.FromArgb(71, 85, 105), + TextFormatFlags.HorizontalCenter + | TextFormatFlags.VerticalCenter + | TextFormatFlags.SingleLine); + if (selected) + { + using var accent = new SolidBrush(Color.FromArgb(37, 99, 235)); + eventArgs.Graphics.FillRectangle( + accent, + bounds.Left + 12, + bounds.Bottom - 3, + Math.Max(0, bounds.Width - 24), + 3); + } + } + } }