diff --git a/TERMINAL_MODULE_PLAN.md b/TERMINAL_MODULE_PLAN.md index 7266d15..9dfbd78 100644 --- a/TERMINAL_MODULE_PLAN.md +++ b/TERMINAL_MODULE_PLAN.md @@ -794,6 +794,394 @@ ZMODEM 剩余价值主要在串口/老旧嵌入式设备。成本收益不成立 3. 连接复用:同主机双标签秒连、关一个另一个不受影响、全关后连接断开。 4. ProxyJump 跳板链、端口转发 -L/-R、`.ppk` 导入、会话模板拉起、AI 助手(需翻译引擎配置)。 +### 界面重构与标题口径(2026-09-20) + +用户反馈两条:「本地 Shell 的标题显示成了全路径」「终端页像内嵌了一个独立终端页面」。 +两条都成立,根因不同,分别处理。 + +#### 一、会话标题为什么曾经是全路径 + +标题有两个来源,后者会覆盖前者: + +1. 创建时写入的标题(`terminal_open_local` → `ConPTYSession::spawn`); +2. shell 通过 OSC 0/2 上报的「自定义标题」。 + +`cmd.exe` 与 `powershell.exe` **默认就把自己的可执行文件全路径设成控制台标题** +(cmd 运行命令时还会追加 ` - <命令>`),经 PTY 原样到达 → 标签栏与状态栏被顶成 +`C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`。Windows Terminal / +VS Code 同样要过滤这类标题,属于同类问题。 + +**提权会话还多一层坑**:以管理员身份运行时标题前面会带一个本地化前缀 +(简体中文下是 `管理员: `,英文是 `Administrator: `)。首版过滤只判「以盘符开头」, +日常(非提权)会话表现完全正确,只有提权运行时才漏 —— 因此 +`osc_title_is_executable_path` 现在对「原串」与「剥掉前缀后的串」各判一次, +并有单测覆盖 `管理员: ` / `Administrator: ` / 全角冒号三种形态。 + +修复分四处: + +| 改动 | 位置 | 理由 | +|---|---|---| +| 过滤「绝对路径 + `.exe`/`.com`」形态的 OSC 标题(含提权前缀) | `shell.rs::osc_title_is_executable_path` + `session.rs` | 判据收紧到绝对路径,WSL/Git Bash 的 `user@host: ~/dir`、`/usr/bin/htop` 必须保留 | +| **标题改用 `shell::session_label()`**(按可执行文件名取短名:`pwsh.exe`→`PowerShell 7`、`powershell.exe`→`PowerShell`、`cmd.exe`→`cmd`、`bash.exe`→`Git Bash`,认不出则退回 `name`) | `shell.rs::session_label` | 标签要短、稳定、一行放下多个会话;设置页的 `name` 可编辑、可很长,两者诉求不同 | +| 设置页的探测名**保持不变**(`PowerShell 7` / `Windows PowerShell` / `命令提示符` / `Git Bash`) | `shell.rs::detect_shells` | 用户明确要求:命名问题在标签位置,不动设置里的命名 | +| 历史默认名迁移同时覆盖两代取值 | `shell.rs::legacy_default_names` | 合并规则是「保留用户设置的名字」,不迁移则默认名对老用户**永远不生效**;列表里含上一版误当作展示名的短名,用于把它们收回描述性默认名 | + +状态栏随之调整:本地会话不再显示从 `ShellProfile::name` 拼出的副标题 —— +标题已经是短名(`PowerShell`),再并排一个 `Windows PowerShell` 会让人以为是两个东西。 +SSH 不受影响(标题是主机名、副标题是 `user@host:port`,后者标题给不出)。 + +顺带修掉一个相邻缺陷:`terminal_rename_session` 设的标题会被下一条提示符的 OSC 标题 +覆盖(表现为「重命名无效」)。现在 `set_title_by_user` 会置 `title_locked`, +一旦用户显式命名,OSC 标题一律忽略;trait 方法名从 `set_title` 改为带 `_by_user` +后缀,从命名上堵住「程序自动改标题」这条误用路径。 + +#### 二、终端页为什么像内嵌独立应用 + +| 症状 | 处理 | +|---|---| +| 工具栏一行 + 会话标签一行,两套横向导航 | 合并为单行头部:左侧会话标签,右侧操作组(省 36px 垂直空间) | +| 工具栏写原始 `sessionId` | 从界面移除,改放标签的悬停提示(排障时仍可见) | +| 十来个别扭的图标按钮铺满一行 | 高频动作(搜索/字号/清屏/独立窗口)外露,其余入「更多」下拉 | +| 「+」只能新建上次用过的 shell,连主机要去主机页 | 改为「+▾」下拉:本地 Shell 列表 + 已保存主机 + 管理主机 | +| 七个页面头部高度/内边距/计数写法各不相同 | 新增 `TerminalPageHeader`(h-11、icon size-4、`共 N …`),六页统一 | +| 父层与页面各套一层圆角边框 | 容器下沉到各页自身,历史页的双边框随之消失 | +| 「返回终端」「关闭」按钮与模块 Tabs 重复 | 移除;`close` 意图事件保留,由父层映射为切 tab | +| 唯一未接入 `useModuleTabs` 的模块 | 接入(标题栏浮动切换器 + tab 记忆 + **全局搜索跳转到指定页**) | + +#### 三、Tabs 由 7 项收敛为 5 项 + +同类模块(proxy / monitor / music / downloader / clipboard)的 TabsList 都不超过 +5 项、统一 `max-w-md`;终端原有 7 项只能靠 `max-w-2xl` 撑开,每项被压得很窄, +横向比别处宽出半屏 —— 是「不像是同一套导航」的直接来源之一。 + +| tab | 组成 | 说明 | +|---|---|---| +| 终端 | 会话(xterm + SFTP + 状态栏) | forceMount,切走不卸载 | +| 主机 | SSH 主机管理 | 不变 | +| 凭据 | 密钥 + 已知主机 | 都是「SSH 信任材料」,同一类列表 | +| 记录 | 命令历史 + 命令片段 | 都是「命令资产」,同一类用途 | +| 设置 | 外观 / 布局 / 选择 / 安全 / 快捷键 / Shell | 不变 | + +合并的前提是**子分区仍能被直接到达**,否则用户要在一页里找两块内容,比多一个 tab 更糟。 +因此新增 `TerminalSectionSwitch`(按钮组实现,视觉规格对齐 `TabsList`/`TabsTrigger`, +不是嵌套 reka Tabs —— 那会带来两层键盘导航语义)。分区状态由 `TerminalModule` +持有(`credSection` / `recordSection`),面板卸载重挂不丢「上次看的是哪一半」。 + +**已知取舍**:`SearchIndexItem.tab` 只能表达「哪个 tab」,无法表达「tab 里的哪个分区」, +所以全局搜索「已知主机」会落在「凭据」页的密钥分区,需再点一次页内开关。 +要精确落位需给 `moduleTabsStore` / `useModuleTabs` / `TitleBar` 三处共享基建加字段, +收益不抵成本,故不做(已在 `index.ts` 的注释里写明)。 + +未改动的部分及原因:底部状态栏保留(终端习惯,且它是唯一承载编码/尺寸/cwd 的位置); +会话标签仍可拖动重排;会话面板仍常驻挂载用 `v-show` 切换(见模块文件头)。 + +`useModuleTabs` 的浮动切换器在终端模块**实际不会出现** —— 模块根是 +`overflow-hidden`,页面不滚动,TabsList 不会滚出可视区。接入的实际收益是 +tab 记忆与全局搜索跳转(此前搜索「SSH 密钥」只切模块、停在终端页)。 + +#### 验证 + +| 检查 | 结果 | +|---|---| +| `cargo check` | exit 0 | +| `cargo test --lib terminal::` | shell.rs 新增 5 个单测(提权前缀 / 有效标题保留 / 短名取值 / 兜底 / 历史默认名) | +| `vue-tsc --noEmit` | exit 0 | +| `vite build` | 成功 | +| 待真机确认 | 提权 PowerShell 标签显示为 `PowerShell`;重命名后不再被覆盖;窄窗口下头部不打架;5 个 tab 与页内分区开关的可达性 | + +### 容器口径统一与终端页拆分(2026-09-20 第二轮) + +峰提出:「终端模块的结构完全是单独的内部窗口,各 tabs 的内部窗口用同一个框架, +与其他模块 UI 模式差别很大」。勘察确认成立,并定位到三处偏离。 + +#### 一、偏离的取证(改动前) + +| # | 偏离 | 事实 | +|---|---|---| +| 1 | 每页多一层**内嵌窗口框** | `h-full flex flex-col rounded-lg border border-border bg-background overflow-hidden`,全项目**仅终端使用**,共 7 处(终端页 + 6 个子面板) | +| 2 | 每页多一条**页中页标题栏** | `TerminalPageHeader`(h-11 + border-b + px-4);6 个子面板各自手写同一套外框再内嵌该头部 | +| 3 | 滚动实现不统一 | 子面板用原生 `overflow-y-auto`,其它模块统一用 `ScrollArea`,滚动条外观不同 | + +对照口径(proxy / monitor / music / clipboard / downloader 一致遵守): +模块根 `h-full p-6` → TabsList 包 `div[ref=tabsListRef]` 且 `max-w-md !bg-transparent` → +`TabsContent flex-1 min-h-0 mt-4 tab-animate` → 内容**直接铺开** +(`ScrollArea` + Card 流 / 列表项),**没有任何容器边框**。 + +#### 二、改动 + +| 项 | 内容 | +|---|---| +| 新增 `components/layout/PageToolbar.vue` | 漂浮工具条(无边框、无底边、`mb-3`),API 与原 `TerminalPageHeader` 一致(icon / title / meta / 默认槽=动作 / info 槽);提升到 layout 层,其它模块可复用 | +| 6 个子面板 | 去外框(保留 `flex flex-col h-full overflow-hidden`);`TerminalPageHeader` → `PageToolbar`;`overflow-y-auto` → `ScrollArea`,padding 移到 ScrollArea 上(沿用 proxy/monitor 的 `pr-3` 写法) | +| 删除 `components/TerminalPageHeader.vue` | 已被 `PageToolbar` 取代,避免留两份近似组件 | +| 新增 `components/TerminalWorkspace.vue` | 终端页整体:工具条 + 终端屏幕(画布 / SFTP / 状态栏)+ 右键菜单 + 4 个对话框 | +| `TerminalModule.vue` 1197 → 383 行 | 只剩模块级编排:5 个 tab、tab 记忆、快捷键分发、初始化、主机密钥弹窗 | + +#### 三、两个关键取舍 + +**1. 终端屏幕保留边框,但边界从「整页」收缩到「画布 + 状态栏」。** +xterm 是画布而不是内容流,需要明确的视觉边界;但此前边框包住的是 +「标签行 + 主体 + 状态栏」整体,框内还有一条 h-11 标题栏 —— 那正是 +「内嵌窗口」的观感来源。现在会话标签与操作组移到框外成为页面级工具条, +边框只包画布与状态栏。 + +**2. `useTerminalStream` 全模块只创建一次。** +该 composable 的 `tabOrder` / `panes` / `activeSessionId` 是**每次调用各创建一份**的 +局部状态。拆分后若模块根与终端页各调一次,会出现两套互不相干的标签顺序与分屏布局 +(表现为「点击标签没反应」)。因此实例在模块根创建,经 prop 传给 `TerminalWorkspace`; +终端页里由模块级快捷键触发的动作(复制/粘贴/搜索/清屏/分屏/独立窗口/SFTP/重命名) +通过 `defineExpose` 的显式清单暴露,由模块根做一次转发。 + +#### 四、约束(后续改动须遵守) + +1. **`h-9` 是卡片内的横向分隔线高度**:控制栏 / 状态栏 / `SftpPanel` 头部同为 h-9, + 改其一必须同步(原契约是「SftpPanel 头部对齐卡片外的工具条行 h-11」,2026-09-21 + 控制栏收进卡片并降为 h-9 后该层已不存在)。 +2. **xterm 尺寸链不能断**:`TabsContent → 工具条 + 屏幕容器 → 画布` 全靠 `flex-1 min-h-0`。 +3. `ModuleContainer.vue` 的 `[data-main-scroll] … > div { height: 100% }` 是给全高模块(终端/翻译)补高度链的补丁,勿删。 +4. `TerminalWindow.vue`(独立窗口)与主窗口共用 `TerminalPane` / `TerminalStatusBar`,共享逻辑留在 `useSessionStream` 与 store,不下移到页面组件。 + +#### 五、非终端页与其它模块的口径对齐(2026-09-21) + +第二轮对齐只动**观感与组件选型**,不改终端页(画布 + 状态栏)的结构: + +1. **外层 padding 归模块根**:根容器 `p-6` → `p-5`,各页不再自带 `p-3/p-5` + —— 此前「根 24px + 页内 12/20px」双重内缩,列表左边缘比其它模块多缩 16px。 + 分栏页(片段、设置)的沟槽也由模块根提供:`UI_DESIGN_SYSTEM.md` §3.4 的 + 「split 无 padding」指**分栏容器自身**不加 padding,不是让页面顶到窗口边缘。 +2. **列表一律用 `Card` 承载**(与 proxy「订阅列表」同口径):`Card` + `CardContent`, + 内部行保持 `rounded-md border`;页名与计数仍由 `PageToolbar` 承担,不再加 `CardTitle`。 +3. **表单控件走 `@/components/ui`**(`Input` / `Select` / `Textarea` / `Checkbox`), + 不再出现裸 `` / `