feat: add settings page visual boundaries

This commit is contained in:
QiuSW
2026-07-26 17:42:40 +08:00
parent eeaf4c7826
commit 45ea8f3194
5 changed files with 197 additions and 57 deletions
+1
View File
@@ -43,6 +43,7 @@
| T-305 | GUI 受管实例优雅停止与启停状态机 | T-304 | DONE |
| T-306 | 精简代理下拉编辑与实例行安全操作布局 | T-304,T-305 | DONE |
| T-307 | 受管浏览器意外退出监控与状态提示 | T-101,T-305,T-306 | DONE |
| T-308 | 设置页分区与控件边框层级 | T-306 | DONE |
## Backlog
+4 -3
View File
@@ -2,13 +2,14 @@
## 快照
- 日期:2026-07-25
- 阶段:Phase 3 真实实例操作(T-301 至 T-307、T-201 至 T-208 已完成)
- 日期:2026-07-26
- 阶段:Phase 3 真实实例操作(T-301 至 T-308、T-201 至 T-208 已完成)
- 代码:已建立 Go module `chub`、`cmd/chub` 入口、logging 测试基座、浏览器 domain/application 合约、Chrome/Edge 参数/发现模块、启动 registry、Windows 身份/占用检查、loopback CDP 端口分配与端点校验、优雅关闭、Job Object、真实 Chrome/Edge smoke、JSON 配置存储、启动恢复、CLI JSON 合约、应用内事件总线、UI 状态测试和 Windows smoke 脚本,T-001 至 T-003、T-101 至 T-104、T-201 至 T-208、T-301 至 T-307 已完成
- UI:Gio 双页 Shell 使用左侧“实例/设置”导航;实例页以等宽“新建实例 / 刷新实例状态”命令区开始,窄内容区自动堆叠。列表固定显示实例名称、浏览器类型、用户数据目录、调试端口、状态和带边框的操作列;操作顺序固定为启动/停止(或重新检测)、编辑、删除,删除与相邻操作保持更大间距以降低误点。主操作在已退出时启动,在 Chub 托管运行或调试不可用时显示停止,在启动/停止中禁用,在外部关联、外部占用或未知占用时仅重新检测;优雅停止通过后台 adapter 验证 registry 的 PID/profile 身份并等待其释放,绝不按进程名关闭或接管外部 Chrome/Edge。刷新通过后台回调只检查已保存实例,以配置快照丢弃编辑或删除后的过期结果;它结合 Chub registry、指定 profile 的外部占用证据和 loopback CDP 端点更新状态,但不扫描、接管或关闭其他 Chrome/Edge。双击、Enter 或编辑图标打开实例编辑弹层,支持名称、浏览器类型、User Data Dir、启动 URL、完整地址代理选择和只读实际端口;保存保留未公开启动选项,Escape 对脏表单先请求确认,活跃/外部关联实例锁定身份约束字段。设置页使用完整代理地址选择器、地址输入及保存/删除操作;选择会回填地址,保存按稳定 `proxyId` 新增或更新,删除未引用代理前必须确认,仍被实例引用的代理会被拒绝。启动通过异步回调接到 Windows 浏览器启动器:未占用目录从已保存的起始端口(空值默认 9666)选择 loopback CDP 端口,并在启动时按已选代理 ID 解析最新的 `--proxy-server` 参数;同目录外部浏览器只有在 `DevToolsActivePort` 与 CDP 端点可验证时才显示“外部已关联”,且不会接管其生命周期。删除使用确认弹层,只删除 Chub 实例配置而不删除 User Data Dir。新建、编辑和删除实例会保存到本地配置;四个设置路径有独立异步可取消搜索,切换页面后输入和任务状态保留;新建实例表单使用 Label、Windows 原生目录选择器和 Chrome/Edge RadioButton;CLI `list/events` 已可用,`start/stop/restart` 等待 BrowserManager adapter
- T-307:本应用会话启动的受管 Chrome/Edge 根进程由单实例后台 `Wait` 监控;意外退出按 ID、启动代次和 PID 验证后立即变为“已退出”,清空运行时 PID/端口,并以可关闭、可合并的提示告知用户。提示关闭后焦点回到启动操作;Chub 请求停止、外部实例、过期事件和应用关闭取消监控均不提示。
- T-308:设置页以可滚动的外层工作区承载浏览器路径、默认目录、运行行为和代理分区;分区、字段、状态提示和保存操作区使用统一的浅色圆角描边。路径字段在紧凑内容区会将输入与“选择/搜索”操作纵向重排,切换页面后滚动、输入与异步搜索状态仍由持久 Gio widget 保留。
- 浏览器核心:设计参考来自 `D:\OPC\shop_helm\internal\platform\chrome`,尚未复制或接入本项目
- blocker:无;T-307 已完成,下一项从 Backlog 或后续需求确定。
- blocker:无;T-308 已完成,下一项从 Backlog 或后续需求确定。
## 当前目录
+35
View File
@@ -0,0 +1,35 @@
---
id: T-308
title: 设置页分区与控件边框层级
phase: 3
deps: [T-306]
status: DONE
created: 2026-07-26
owner: codex
---
## 需求与背景
设置页目前只有部分卡片和操作区具有边界,浏览器路径、目录、端口、代理和退出行为等组件在 Gio 实现中缺少清晰的视觉归属。需要在不改变现有保存、搜索、代理编辑或键盘交互的前提下,为设置模块整体及关键组件补充克制、一致的边框层级。
## 方案与边界
- 设置页采用三层层级:页面内容外层工作区、功能分区卡片、字段/状态/操作条。描边使用浅色 1dp、圆角与既有实例列表一致,不使用装饰性阴影。
- 浏览器路径、默认目录、运行行为和代理分别具有可识别的分区;路径、端口和代理地址的标签、说明、输入与关联操作保持在同一个字段边界内。代理删除仍使用既有确认流程。
- 底部“保存设置/取消”保持现有命令和顺序,仅以带上分隔线的操作区与表单内容分离。紧凑宽度下分区和字段按阅读顺序纵向排列,不裁剪按钮或文本。
- 仅调整设置页的视觉容器和间距;不修改实例页、创建/编辑实例表单、配置格式、浏览器启动、代理校验及异步搜索逻辑。
- 同步更新 `docs/ui/browser-manager.html`,作为设计交接基准;HTML 原型不代表 Gio 原生主题、DPI 或无障碍已完全验证。
## 验收要点
- 设置整体、浏览器路径、默认目录、运行行为、代理、路径字段、端口字段、状态提示和保存操作区均具有清晰且不过重的边框层级。
- 既有四个路径的选择/搜索/取消搜索、远程调试起始端口、退出时关闭托管浏览器、代理选择/保存/删除确认、保存/取消设置行为保持不变。
- 窄窗口按纵向重排,标签与帮助文本可读,按钮不被裁剪;Tab 顺序仍与视觉和阅读顺序一致。
- HTML 原型脚本可解析;`gofmt`、`go test ./...`、`go vet ./...`、`go build -o build/chub.exe ./cmd/chub` 和 Windows smoke 通过。
## 执行记录
- 状态:DONE
- 变更:设置内容改为持久 `widget.List` 承载的可滚动工作区;Gio 使用外层工作区、路径/目录/运行/代理分区卡片、字段容器、状态提示与操作条的浅色圆角 1dp 边框。路径字段在窄内容区会将编辑框与“选择/搜索”操作纵向重排,既有路径搜索、代理编辑和保存命令不变。HTML 原型同步了相同的工作区、卡片、字段、状态和操作区边框层级。
- 验证:`gofmt -w internal/ui/shell.go`、`go test ./internal/ui`、HTML 内嵌脚本语法检查、`scripts/smoke-windows.ps1`(含 `go test ./...`、`go vet ./...`、Windows 构建和 CLI JSON contract)均通过。
- 阻塞:无。
+13 -7
View File
@@ -174,16 +174,22 @@
.toast { position: fixed; right: 22px; bottom: 22px; z-index: 30; max-width: min(420px, calc(100vw - 44px)); padding: 12px 15px; border: 1px solid var(--line); border-left: 4px solid var(--accent); border-radius: 10px; background: var(--surface); box-shadow: var(--shadow); color: var(--text); opacity: 0; transform: translateY(8px); pointer-events: none; transition: opacity .16s ease, transform .16s ease; }
.toast.show { opacity: 1; transform: translateY(0); }
.page-hidden { display: none !important; }
.settings-grid { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(260px, .8fr); gap: 18px; }
.settings-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card); padding: 22px; display: grid; gap: 20px; }
.settings-workspace { padding: 16px; border: 1px solid var(--line); border-radius: calc(var(--radius-card) + 2px); background: var(--surface-2); }
.settings-grid { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(260px, .8fr); gap: 16px; }
.settings-card { background: var(--surface); border: 1px solid var(--line-strong); border-radius: var(--radius-card); padding: 20px; display: grid; gap: 16px; }
.settings-card h2 { margin: 0; font-size: 18px; letter-spacing: -.02em; }
.settings-card > p { margin: -12px 0 0; color: var(--muted); line-height: 1.5; }
.settings-card > p { margin: -10px 0 0; color: var(--muted); line-height: 1.5; }
.settings-card .form-section, .settings-card .setting-list { padding: 14px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2); }
.settings-card .form-section { gap: 12px; }
.settings-card .field { padding: 12px; border: 1px solid var(--line); border-radius: 9px; background: var(--surface); }
.settings-card .field:focus-within { border-color: var(--accent); box-shadow: var(--focus); }
.settings-card .check-line { padding: 12px; border: 1px solid var(--line); border-radius: 9px; background: var(--surface); }
.path-field { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 8px; }
.radio-row { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; min-height: 38px; }
.radio-row label { font-weight: 500; display: inline-flex; gap: 7px; align-items: center; }
.settings-status { min-height: 42px; padding: 11px 12px; border-radius: 9px; background: var(--success-soft); color: var(--success); font-size: 12px; line-height: 1.45; }
.settings-status { min-height: 42px; padding: 11px 12px; border: 1px solid color-mix(in srgb, var(--success) 35%, var(--line)); border-radius: 9px; background: var(--success-soft); color: var(--success); font-size: 12px; line-height: 1.45; }
.settings-status.warning { background: var(--warning-soft); color: var(--warning); }
.settings-actions { display: flex; justify-content: flex-end; gap: 8px; border-top: 1px solid var(--line); padding-top: 18px; }
.settings-actions { display: flex; justify-content: flex-end; gap: 8px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2); padding: 12px; }
.setting-list { display: grid; gap: 12px; }
.setting-list .detail-line { padding-bottom: 12px; border-bottom: 1px solid var(--line); }
.setting-list .detail-line:last-child { border-bottom: 0; padding-bottom: 0; }
@@ -253,7 +259,7 @@
</section>
</section>
<section class="content page-hidden" id="settingsPage" aria-label="应用设置">
<div class="settings-grid">
<div class="settings-workspace"><div class="settings-grid">
<form class="settings-card" id="settingsForm">
<div><h2>浏览器与目录</h2><p>这些设置用于新建实例的默认值;已有实例的配置不会被静默修改。</p></div>
<div class="form-section"><h3>浏览器路径</h3>
@@ -268,7 +274,7 @@
<div class="settings-actions"><button type="button" class="secondary" id="discardSettings">取消</button><button type="submit" class="primary">保存设置</button></div>
</form>
<aside class="settings-card"><div><h2>运行行为</h2><p>影响后续启动和应用关闭时的默认行为。</p></div><div class="setting-list"><label class="check-line"><input type="checkbox" id="closeOnExit"><span><b>退出 Chub 时关闭本工具启动的实例</b><br><small class="help">默认关闭。不会影响外部浏览器。</small></span></label><div class="field"><label for="remoteDebugPort">远程调试起始端口</label><input id="remoteDebugPort" type="number" min="1024" max="65535" value="9666" inputmode="numeric"><small>留空时使用 9666。启动实例仅绑定 127.0.0.1;端口被占用时顺序尝试下一个可用端口。</small></div><div class="settings-status" id="settingsStatus">设置已保存 · 最后更新:刚刚</div></div></aside>
</div>
</div></div>
</section>
</main>
</div>