diff --git a/.agents/rules/wpf-xaml.md b/.agents/rules/wpf-xaml.md
index a12b8df..d1c0d9f 100644
--- a/.agents/rules/wpf-xaml.md
+++ b/.agents/rules/wpf-xaml.md
@@ -60,6 +60,9 @@ DataContext="{core:DISource Type=local:DialogViewModel}">
**`BindingProxy` 映射关系**:`Data="{Binding}"` 把 ViewModel 存到 `BindingProxy.Data`。引用时 `Path=Data.SelectedItem` 中 `Data` 对应 `BindingProxy.Data`(即 ViewModel)。
+### 文字只读显示
+尽量使用 `TextBlock` 而不是 `Label`
+
### DataTemplate(CellTemplate)中
Command 用 CommandProxy,行数据用 `{Binding}`:
@@ -262,6 +265,24 @@ C# 侧:`"#FF5733".toColor()`、`"#80000000".toColor()`。
```
+## 顺序排列与间距布局
+
+**页面排版需要横向、纵向顺序排列或简单自动换行时,优先使用 `wpf:SpacingStackPanel`。** 普通堆叠布局通过 `Orientation` 指定方向,需要换行时设置 `ChildWrapping="True"`。
+
+- 优先用 `HorizontalSpacing` / `VerticalSpacing` 统一控制子项间距,避免逐项设置重复的 `Margin`;容器外边距和个别元素的特殊偏移仍按需使用 `Margin`。
+- 需要统一子项尺寸或对齐时,按需设置 `ItemWidth`、`ItemHeight`、`ItemHorizontalAlignment`、`ItemVerticalAlignment`。
+- “文字 + 简单内容”可由 `wpf:TitleContent` 组成,再用 `wpf:SpacingStackPanel` 排列多个字段。
+- 需要行列划分、跨行跨列、重叠或虚拟化时,使用满足这些要求的布局容器,不强行替换为 `SpacingStackPanel`。
+- 属性及用法见 [max-common-wpf SpacingStackPanel 参考](../skills/max-common-wpf/references/api.md#controlsspacingstackpanelcs)。
+
+## 文字与简单内容布局
+
+**页面排版遇到“说明文字 + 简单内容”(如输入框、选择器、按钮等输入或点击控件)时,优先使用 `wpf:TitleContent`。** 将说明文字放在 `Title`,交互控件作为内容,避免为同一组合另用 `TextBlock` 配合 `Grid` / `StackPanel` 手工拼接。
+
+- 按布局设置 `Placement`;左侧标题表单优先使用 `Placement="Left"`,并按需设置 `TitleWidth`、`TitleVerticalAlignment="Center"` 和 `LeftTitlePadding`,保持同组字段对齐。
+- 内容控件仍自行配置绑定、命令、输入长度和可访问名称;`TitleContent` 只负责标题与内容的布局。
+- 属性及用法见 [max-common-wpf TitleContent 参考](../skills/max-common-wpf/references/api.md#controlstitlecontentcs)。
+
## 布局注释
主要布局分块/区域前加 `` 注释标明用途。复杂视图(多面板、多 Tab、对话框)必须注释;简单单控件视图可省略:
@@ -302,48 +323,34 @@ C# 侧:`"#FF5733".toColor()`、`"#80000000".toColor()`。
## 枚举绑定
-### EnumDisplayConverter(XAML)
+**页面的枚举选项绑定优先使用 `EnumBindingMarkup`(`EnumBindingMarkupExtension`)生成 ItemsSource,配合 `EnumBindingExtension.SelectedEnum` 绑定选中的枚举值。** 不要仅为列出枚举值,在 ViewModel 中额外定义 `Enum.GetValues` 列表或在 XAML 中硬编码选项。
+
+- 显示名优先使用 MAX.Common.WPF 自带的 `EnumDisplayConverter`,显式传入标记扩展的 `Converter`;未传入时默认显示枚举成员名称。
+- `DisplayMemberPath="Display"` 用于显示名称,`SelectedValuePath="Value"` 对应枚举值本身。
+- 系统初始化、视图加载前配置一次 `EnumDisplayConverter.EnumDisplayManager = new EnumDisplayManagerWPF(null);`;已有自定义显示管理器时复用现有配置。
+- 仅显示单个枚举值的文本时,直接绑定该属性并使用 `EnumDisplayConverter`,无需生成选项列表。
+
+### ComboBox 绑定枚举
+
+`xmlns:wpf="MAX.WPF"`,`xmlns:local` 指向业务枚举命名空间:
```xml
-
+
-
-
+
+
+
+
```
-### ComboBox 绑定枚举(推荐:ViewModel 提供列表)
-
-```csharp
-// ViewModel
-public IReadOnlyList StatusList { get; } = Enum.GetValues(typeof(MyStatus)).Cast().ToList();
-
-public MyStatus SelectedStatus
-{
- get => GetProperty(() => SelectedStatus);
- set => SetProperty(() => SelectedStatus, value);
-}
-```
-
-```xml
-
-
-
-
-
-
-
-```
-
-### C# 获取枚举显示文本
-
-```csharp
-string displayText = MyStatus.Active.GetEnumDisplay();
-```
-
-EnumDisplayService 内部实现和自定义逻辑详见 `reference-enum-display` skill。
+初始化命名空间、Description 显示、空选项等完整用法见 [max-common-wpf 枚举绑定参考](../skills/max-common-wpf/references/api.md#markupextensionsenumbindingmarkupextensioncs)。自定义枚举显示管理器见 `reference-enum-display` skill。
## MarkupExtension 示例
diff --git a/.agents/skills/add-service/SKILL.md b/.agents/skills/add-service/SKILL.md
index 5909237..5d9874f 100644
--- a/.agents/skills/add-service/SKILL.md
+++ b/.agents/skills/add-service/SKILL.md
@@ -24,7 +24,7 @@ disable-model-invocation: false
```csharp
using MAX.Core.DependencyInjections;
-namespace PipeReviewReport.<模块名>.Services;
+namespace 项目.<模块名>.Services;
public interface IXxxService
{
@@ -43,7 +43,7 @@ internal class XxxService : IXxxService, ISingletonDependency
using MAX.Core.DependencyInjections;
using MAX.Core.Interfaces;
-namespace PipeReviewReport.<模块名>.Services;
+namespace 项目.<模块名>.Services;
public interface IXxxService { }
diff --git a/.agents/skills/max-common-wpf/SKILL.md b/.agents/skills/max-common-wpf/SKILL.md
index 764a78b..02dcd5b 100644
--- a/.agents/skills/max-common-wpf/SKILL.md
+++ b/.agents/skills/max-common-wpf/SKILL.md
@@ -18,6 +18,8 @@ description: 在 Windows WPF 项目中接入和使用 MAX.Common.WPF 的控件
按任务读取 [功能索引](references/features.md)。具体命名空间、属性和方法在 [常用 API 声明](references/api.md) 中按类型搜索。声明摘录没有完整方法体;行为不明确时查看当前安装包 XML 文档、反射或可用源码,再以编译和运行验证。消费项目没有原仓库也应能使用本指南。
+标题布局与间距布局分别参见 [TitleContent 用法](references/api.md#controlstitlecontentcs) 和 [SpacingStackPanel 用法](references/api.md#controlsspacingstackpanelcs)。
+
## XAML 资源入口
程序集声明了 `xmlns:wpf="MAX.WPF"`。控件样式和共享转换器位于 `Resources/Generic.xaml`,不要猜成 `Themes/Generic.xaml`。需要这些资源时,在现有 Application.Resources 的 MergedDictionaries 中合并一次:
@@ -26,12 +28,14 @@ description: 在 Windows WPF 项目中接入和使用 MAX.Common.WPF 的控件
```
-页面最小示例(绑定属性由页面的 DataContext 提供):
+## 页面最小示例
+
+绑定属性由页面的 DataContext 提供:
```xml
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:wpf="MAX.WPF">
@@ -55,18 +59,11 @@ description: 在 Windows WPF 项目中接入和使用 MAX.Common.WPF 的控件
- 虚拟化依赖有限可视区域、滚动宿主和 ItemsPanel 配置。`VirtualizingItemsControl` 默认使用 VirtualizingStackPanel,需要换行时显式设置 VirtualizingWrapPanel。不能仅添加面板便承诺大数据性能。
- 通用命令在 `MAX.Common.Commands`,不是 WPF.Commands。MethodCommand / AsyncMethodCommand 的 T 需引用类型或可空值类型,状态变化手动 RaiseCanExecuteChanged;异步命令委托需自行处理业务异常。
-## 枚举绑定示例
+## 枚举绑定
-已有 `xmlns:local` 指向业务枚举命名空间、`SelectedStatus` 是 `Status` 属性时:
+枚举显示优先使用 MAX.Common.WPF 自带的 `EnumDisplayConverter`,并通过 `EnumBindingMarkupExtension.Converter` 显式传入;未设置转换器时只显示枚举成员名称。系统初始化时、视图加载前配置一次 `EnumDisplayConverter.EnumDisplayManager = new EnumDisplayManagerWPF(null);`;已有自定义管理器时复用现有配置。
-```xml
-
-```
-
-允许空选项时使用 AllowNull 并让业务属性可空;自定义显示通过扩展的 Converter 提供,勿假设自动读取所有枚举描述标记。
+初始化代码、Description 显示示例及选中值绑定见 [枚举绑定用法](references/api.md#markupextensionsenumbindingmarkupextensioncs)。
## 验证与交付
diff --git a/.agents/skills/max-common-wpf/references/api.md b/.agents/skills/max-common-wpf/references/api.md
index dfd7fa9..37ca037 100644
--- a/.agents/skills/max-common-wpf/references/api.md
+++ b/.agents/skills/max-common-wpf/references/api.md
@@ -2,12 +2,18 @@
来源:MAX.Common.WPF,2026-10-27 工作区源码快照。
-按下方主题或文件名搜索。仅摘录选定文件的 public / protected 声明;省略方法体、初始化表达式与 XML 注释。这是定位参考,不是可直接编译的 C#,不保证包含嵌套类型的完整可见性上下文。行为说明见 SKILL.md;目标包版本不同时以实际包为准。
+按下方主题或文件名搜索。API 代码块仅摘录选定文件的 public / protected 声明;省略方法体、初始化表达式与 XML 注释。TitleContent 与 SpacingStackPanel 另附属性说明和 XAML 用法。这是定位参考,不是可直接编译的 C#,不保证包含嵌套类型的完整可见性上下文。通用集成约束见 SKILL.md;目标包版本不同时以实际包为准。
目录:控件与布局、枚举、视图与绑定、线程与防抖、虚拟化与转换器
## 控件与布局
+以下 TitleContent 与 SpacingStackPanel 示例在视图根元素声明命名空间:
+
+```xml
+xmlns:max="clr-namespace:MAX.Common.WPF.Controls;assembly=MAX.Common.WPF"
+```
+
### Controls/NumberBox.cs
命名空间:`MAX.Common.WPF.Controls`
@@ -46,6 +52,55 @@ public Thickness LeftTitlePadding
public TitleContent()
```
+`TitleContent` 是一个"标题 + 内容"布局控件:在 `Content` 周围额外渲染一个 `Title`,并可通过 `Placement` 决定标题相对内容的位置,同时支持标题宽度、对齐方式与内边距的精细控制。
+
+| 属性 | 类型 | 默认值 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `Content` | `object` | — | 继承自 `ContentControl`,主内容 |
+| `Title` | `object` | `null` | 标题内容(可放字符串、任意 UIElement) |
+| `Placement` | `TitlePlacementType` | `Top` | 标题相对内容的位置 |
+| `TitleWidth` | `double` | `NaN` | 标题宽度;`NaN` 表示自动(Auto) |
+| `TitleHorizontalAlignment` | `HorizontalAlignment` | `Left` | 标题在自身区域内的水平对齐 |
+| `TitleVerticalAlignment` | `VerticalAlignment` | `Top` | 标题在自身区域内的垂直对齐 |
+| `TopTitlePadding` | `Thickness` | `(7,2,0,1)` | `Placement=Top` 时标题的内边距 |
+| `LeftTitlePadding` | `Thickness` | `(0,0,6,0)` | `Placement=Left` 时标题的内边距 |
+
+标题位置示例使用 Top 与 Left;其他枚举值以实际程序集为准,不根据 padding 属性名推断。
+
+#### 1. 最简用法(标题在上)
+
+```xml
+
+
+
+```
+
+#### 2. 标题在左侧 + 固定标题宽度(表单场景)
+
+```xml
+
+
+
+```
+
+#### 3. 标题使用复杂 UIElement
+
+```xml
+
+
+
+
+
+
+
+
+
+```
+
### Controls/BoolContent.cs
命名空间:`MAX.Common.WPF.Controls`
@@ -74,6 +129,87 @@ public HorizontalAlignment? ItemHorizontalAlignment
public VerticalAlignment? ItemVerticalAlignment
```
+> `SpacingStackPanel` 是一个"带间距 + 可选自动换行 + 可选统一子项尺寸"的布局面板,可看作 `StackPanel` 的增强版:既支持 StackPanel 式的单向排列,也支持 `WrapPanel` 式的换行排列,并且可以在**每个子项之间**插入水平/垂直间距。
+
+#### 核心属性速查
+
+| 属性 | 类型 | 默认值 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `Orientation` | `Orientation` | `Horizontal` | 排列方向(水平/垂直),影响"主轴"与间距的解释 |
+| `ChildWrapping` | `bool` | `false` | 是否自动换行(类似 WrapPanel);`false` 时类似 StackPanel |
+| `HorizontalSpacing` | `double` | `NaN`(视为 0) | 子项之间的水平间距;校验要求 `> 0` 或 `NaN` |
+| `VerticalSpacing` | `double` | `NaN`(视为 0) | 子项之间的垂直间距;校验要求 `> 0` 或 `NaN` |
+| `ItemWidth` | `double` | `NaN`(自动) | 统一子项宽度;示例使用数值宽度,不将其当作 GridLength 的星号尺寸 |
+| `ItemHeight` | `double` | `NaN`(自动) | 统一子项高度 |
+| `ItemHorizontalAlignment` | `HorizontalAlignment?` | `Stretch` | 强制设置所有子项的 `HorizontalAlignment` |
+| `ItemVerticalAlignment` | `VerticalAlignment?` | `Stretch` | 强制设置所有子项的 `VerticalAlignment` |
+
+> 关键点:`HorizontalSpacing`/`VerticalSpacing` 不是"主轴/交叉轴"间距,而是**屏幕坐标意义上的水平/垂直间距**。当 `Orientation=Horizontal` 时通常用 `HorizontalSpacing` 拉开子项;`Orientation=Vertical` 时通常用 `VerticalSpacing` 拉开子项。
+
+#### XAML 基本用法
+
+示例沿用本节开头的 max 命名空间声明。
+
+##### 1. 水平排列 + 子项水平间距(最常用)
+
+```xml
+
+
+
+
+
+```
+
+##### 2. 垂直排列 + 行间距
+
+```xml
+
+
+
+
+
+```
+
+##### 3. 自动换行(标签云 / 按钮流)
+
+```xml
+
+
+
+
+
+
+```
+
+##### 4. 统一子项尺寸(等宽卡片网格)
+
+```xml
+
+
+
+
+
+```
+
+##### 5. 控制子项对齐
+
+```xml
+
+
+
+
+```
+
### Controls/SpacingUniformGrid.cs
命名空间:`MAX.Common.WPF.Controls`
@@ -156,6 +292,60 @@ public override bool Equals(object obj)
public override int GetHashCode()
```
+#### 显示名称与库自带转换器
+
+MAX.Common.WPF 已提供 `EnumDisplayConverter`,枚举显示优先复用它,不必另写读取 Description 的转换器。`EnumBindingMarkupExtension` 不会自动使用这个转换器,必须通过 `Converter` 显式传入:
+
+- `Value` 保存枚举值本身,例如 `EEE.Admin`。
+- 未设置 `Converter` 时,`Display` 为枚举值的 `ToString()`,例如 `"Admin"`,不会由标记扩展直接读取 `Description`。
+- 设置 `EnumDisplayConverter` 后,`Display` 由枚举显示管理器生成;界面仍绑定 `Display`,选中值绑定 `Value`。
+
+#### 系统初始化
+
+`EnumDisplayConverter` 提供全局静态属性 `public static EnumDisplayManagerBase EnumDisplayManager { get; set; }`。在系统初始化、相关视图加载之前设置一次:
+
+```csharp
+using MAX.Common.WPF.Converters;
+using MAX.Common.WPF.Utilities;
+
+EnumDisplayConverter.EnumDisplayManager = new EnumDisplayManagerWPF(null);
+```
+
+将赋值放进项目已有的启动初始化流程;若已配置自定义显示管理器,复用现有配置,不要重复覆盖。
+
+#### Description 显示示例
+
+```csharp
+using System.ComponentModel;
+
+public enum EEE
+{
+ [Description("管理员")]
+ Admin,
+ [Description("用户")]
+ User
+}
+```
+
+`xmlns:wpf="MAX.WPF"`,`xmlns:local` 指向枚举所在命名空间。在视图资源中注册库自带转换器,再将它传给标记扩展:
+
+```xml
+
+
+
+
+
+```
+
+`SelectedRole` 是带属性变更通知的 `EEE` 属性。界面显示“管理员”“用户”,选中值仍为 `EEE.Admin` 或 `EEE.User`。只需要展示选项时,可以省略 `SelectedValuePath` 和 `SelectedEnum` 绑定。
+
+允许空选项时设置 `AllowNull=True`,并让选中属性可空。`NullDisplay` 默认为空字符串;只有显式设置为 null 时,标记扩展才会尝试调用转换器生成空选项的显示内容。`DisplayNullLast` 控制空选项是否放在末尾。
+
### Extensions/EnumBindingExtension.cs
命名空间:`MAX.Common.WPF.Extensions`
diff --git a/.agents/skills/max-common-wpf/references/features.md b/.agents/skills/max-common-wpf/references/features.md
index 2ac3918..65b1efa 100644
--- a/.agents/skills/max-common-wpf/references/features.md
+++ b/.agents/skills/max-common-wpf/references/features.md
@@ -5,8 +5,8 @@
| 需求 | 类型 / 命名空间 | 使用要点 |
| --- | --- | --- |
| 数值输入 | Controls.NumberBox | Value 为 double;Minimum、Maximum、Increment、DecimalPlaces、ValueFormat、ShowUpDownButton;模板资源在 Resources/Generic.xaml |
-| 标题与条件内容 | Controls.TitleContent、BoolContent | TitleContent 有 Placement 等布局属性;BoolContent 用 Bool、TrueContent、FalseContent |
-| 间距布局 | Controls.SpacingStackPanel、SpacingUniformGrid、AutoColumnsUniformGrid | 区分面板间距与子元素 Margin,按声明配置 |
+| 标题与条件内容 | Controls.TitleContent、BoolContent | [TitleContent](api.md#controlstitlecontentcs) 提供标题位置、宽度、对齐和内边距,详见属性表与示例;BoolContent 用 Bool、TrueContent、FalseContent |
+| 间距布局 | Controls.SpacingStackPanel、SpacingUniformGrid、AutoColumnsUniformGrid | [SpacingStackPanel](api.md#controlsspacingstackpanelcs) 支持水平/垂直间距、换行、统一子项尺寸与对齐,详见属性表与示例;区分面板间距与子元素 Margin |
| 图标与链接 | Controls.FontIcon、HyperlinkEx | FontIcon 继承 TextBlock,具体字体和字形必须与项目资源一致 |
| 按钮与弹出层 | Controls.MenuButton、ButtonPopup、CustomPopup、ClippingBorder | 按实际属性处理关闭、焦点和裁切,不猜测通用 Popup 行为 |
| 托盘图标 | Controls.NotifyIcon;Enums.NotifyIconInfoType | 涉及 Win32 资源与应用生命周期,检查实际清理 API |
@@ -14,7 +14,7 @@
| 标准控件附加行为 | Extensions.TextBoxExtension、ButtonExtension、ListBoxExtension、TreeViewExtension、SelectorExtension、ScrollViewerExtension、ProgressBarExtension、ToolTipExtension | 搜索对应 Get/Set 方法确认附加属性名称、默认值与事件机制 |
| 通用控件行为 | Extensions.ControlExtension(Context / DragDrop / Focus / Keyboard / Layout / Loaded / Mouse 分文件)、WindowExtension、GridLayoutHelper | 使用实际 API 名称,不把文件名当作附加属性名 |
| 上下文菜单和绑定代理 | Extensions.ContextMenuExtension、ContextMenuListener、BindingProxy / BooleanProxy、CommandProxy;Models.ContextMenuModel;ViewModels.ContextMenuViewModel | Popup/ContextMenu 不在普通可视树,核对 DataContext 来源 |
-| 枚举选择与显示 | MarkupExtensions.EnumBindingMarkupExtension;Extensions.EnumBindingExtension;Converters.EnumDisplayConverter;Utilities.EnumDisplayManagerWPF | ItemsSource 返回包装对象;旧 EnumBindingComboBox 已弃用 |
+| 枚举选择与显示 | MarkupExtensions.EnumBindingMarkupExtension;Extensions.EnumBindingExtension;Converters.EnumDisplayConverter;Utilities.EnumDisplayManagerWPF | [枚举绑定用法](api.md#markupextensionsenumbindingmarkupextensioncs):显式传入库自带 EnumDisplayConverter,启动时初始化 EnumDisplayManager;ItemsSource 返回 Display/Value 包装对象;旧 EnumBindingComboBox 已弃用 |
| 布尔、空值与可见性 | Converters.Boolean2VisibilityConverter、Boolean2VisibilityReConverter、Object2VisibilityConverter、String2VisibilityConverter、IListToVisibilityConverter、BooleanArr2VisibilityConverter 等 | 各 Converter 对 null、空串、集合的定义不同,核对实现;多值转换器需 MultiBinding |
| 格式、颜色、尺寸转换 | Converters.FileSizeDisplayConverter、DateTimeFormatConverter、FormatConverter、StringJoinConverter、ColorToBrushConverter、ContrastBrushConverter、ThicknessSplitConverter、CornerRadiusSplitConverter 等 | 按 IValueConverter / IMultiValueConverter 及 ConvertBack 支持情况选择绑定模式 |
| 视图生命周期 | ViewModels.ViewModelBase;Extensions.ViewModelExtension | LoadedOnce / Loaded / Unloaded;关联视图并非 IoC 自动创建 ViewModel |
diff --git a/DesignDemo/index.html b/DesignDemo/index.html
index 9c1dcd8..30b259f 100644
--- a/DesignDemo/index.html
+++ b/DesignDemo/index.html
@@ -26,12 +26,24 @@ button,input,select{border-radius:2px;font-size:12px}button{padding:3px 10px;hei
th:first-child,td:first-child{width:30px;min-width:30px;padding:0 8px;position:sticky;left:0;background:white;z-index:1}th:first-child{background:#f0f2f4;z-index:3}.row-number{color:#9299a1;text-align:right;font-size:11px;min-width:34px}thead th{z-index:2}th:last-child{z-index:3;background:#f0f2f4}td:last-child,th:last-child{box-shadow:-3px 0 4px #0000000a;border-left:1px solid #dce0e4}
tbody tr:nth-child(even) td{background:#fafbfc}tbody tr:hover td{background:#f0f2f4}tbody tr.selected td{background:#f3eaf1}.entity{gap:0}.entity-mark,.entity small,.compact .entity-mark{display:none}.entity-name{margin:0;font-size:12px;font-weight:500}.pill{font-size:11px;padding:0 5px;border-radius:2px;line-height:19px}.tag{background:none;padding:0;font-size:12px;color:#60666e}.date{font-size:12px}.subline{display:inline;font-size:10px;margin-left:6px}.mono{color:#69727d;font-size:11px}.muted{color:#7c838c}.row-action{padding:0 5px!important;height:23px!important}.pagination{height:35px;min-height:35px;flex-shrink:0;padding:3px 9px;background:#f7f8f9;font-size:11px}.pagination button,.pagination select{height:24px}.pagination-controls{gap:6px}.current-page{padding:2px 8px}.statusbar{height:23px;min-height:23px;align-items:center;padding:0 5px;font-size:10px}
.drawer{margin:auto;width:620px;max-width:90vw;height:auto;max-height:90vh;border-radius:3px}.dialog-head{padding:11px 15px;background:#f5f6f7}.dialog-head h2{font-size:14px}.dialog-head p{font-size:10px;margin-top:2px}.dialog-body{padding:15px;gap:13px}.drawer .dialog-body{display:grid;grid-template-columns:1fr 1fr}.drawer .dialog-body>.help{grid-column:1/-1}.dialog-body input:not([type=checkbox]),.dialog-body select{height:29px}.dialog-body .field{gap:5px}.dialog-footer{padding:9px 15px}.permissions{grid-template-columns:1fr;gap:6px}.columns-dialog{border-radius:3px}.help{border-radius:2px;font-size:11px}
+/* Desktop window frame and navigation hierarchy. */
+.app{grid-template-columns:180px minmax(0,1fr);grid-template-rows:32px minmax(0,1fr);height:100dvh;min-height:594px;border:1px solid #c9c4cd}
+.ui-icon{display:block;width:16px;height:16px;flex-shrink:0;fill:none;stroke:currentColor;stroke-width:1.65;stroke-linecap:round;stroke-linejoin:round}
+.window-titlebar{grid-column:1/-1;display:flex;align-items:center;justify-content:space-between;background:var(--brand);border-bottom:1px solid var(--brand);user-select:none}.window-caption{display:flex;align-items:center;gap:8px;padding-left:11px;color:#fff;font-size:11px}.window-caption .ui-icon{color:#fff}.window-actions{display:flex;height:31px}.window-actions button{display:grid;place-items:center;width:46px;height:31px;padding:0;border:0;border-radius:0;background:transparent;color:#fff}.window-actions .ui-icon{width:12px;height:12px;stroke-width:1}.window-actions button:hover{background:#ffffff25}.window-actions .window-close:hover{background:#c42b1c;color:#fff}
+.sidebar{grid-column:1;grid-row:2;min-height:0;overflow:auto}.shell{grid-column:2;grid-row:2;min-height:0}
+.brand{justify-content:center;padding:0;background:#f7f8f9;border-bottom:1px solid #e2e4e8;color:var(--brand)}.brand>.ui-icon{width:32px;height:32px;stroke-width:1.5}
+.section-label{display:flex;align-items:center;gap:8px;margin:13px 8px 4px;padding:7px 8px;background:transparent;color:#65516b;font-size:12px;font-weight:650;border-radius:3px;letter-spacing:0}.section-label>.ui-icon{width:17px;height:17px}
+.sidebar nav{margin:0 8px 0 16px;border-left:1px solid #ddd6e0;padding-left:5px}.sidebar .nav{height:34px;padding:0 9px;gap:8px;border-radius:3px;font-size:12px;color:#727785}.sidebar .nav.active{padding-left:9px;background:transparent;color:var(--brand-dark);font-weight:650}.sidebar .nav .ui-icon{width:15px;height:15px}.sidebar .nav:hover:not(:disabled){background:transparent;color:var(--brand-dark)}.sidebar .nav.active:after{display:block;content:"";height:14px;width:2px;border-radius:1px;background:var(--brand);margin-left:auto}
+
+.title-account{display:flex;align-items:center;gap:12px;margin-left:auto;padding:0 18px;color:#fff;font-size:10px}
+.title-account b{font-weight:600;color:#fff}
+.account-divider{height:11px;border-left:1px solid #ffffff40}
-
-
-
管理工作空间 /店铺管理
账户积分 9,759管管理员
+
+
+
diff --git a/DesignDemo2/check.cjs b/DesignDemo2/check.cjs
new file mode 100644
index 0000000..8513e11
--- /dev/null
+++ b/DesignDemo2/check.cjs
@@ -0,0 +1,58 @@
+const fs = require('node:fs');
+const vm = require('node:vm');
+const assert = require('node:assert/strict');
+const html = fs.readFileSync(__dirname + '/index.html', 'utf8');
+const script = html.match(/
+
+