feat(node): organize console into tabs

This commit is contained in:
caoqianming 2026-08-21 10:21:13 +08:00
parent 2a7c6a0fb2
commit bf6f8aa1f6
3 changed files with 105 additions and 18 deletions

View File

@ -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 的独立运行环境;只装有部分专业软件的节点不再需要处理无关软件。

View File

@ -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)

View File

@ -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);
}
}
}
}