From 6160be6e08821d5144c86d68ad67746fd8f14a91 Mon Sep 17 00:00:00 2001 From: yinjianm Date: Mon, 30 Mar 2026 02:18:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E6=96=B0=E5=A2=9E=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=BF=AB=E6=8D=B7=E6=A3=80?= =?UTF-8?q?=E7=B4=A2=E5=B9=B6=E4=BC=98=E5=8C=96=E5=B7=A5=E4=BD=9C=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 Ctrl+Shift+F 全局服务器检索面板,支持对 SSH、 RDP、VNC 连接进行本地模糊搜索、键盘导航与直接连接, 并统一复用现有 sessionStore 连接链路 同时将 Workbench 导航从左侧竖排 icon rail 调整为标题上方 横向纯图标栏,并补充终端标签页“关闭全部”菜单项 --- .helloagents/CHANGELOG.md | 4 + .../.status.json | 1 + .../proposal.md | 127 +++++++++++++ .../tasks.md | 57 ++++++ .../proposal.md | 117 ++++++++++++ .../tasks.md | 52 +++++ .helloagents/archive/_index.md | 3 + .helloagents/modules/frontend.md | 2 +- packages/frontend/src/App.vue | 57 ++++++ .../GlobalConnectionQuickSearch.vue | 179 ++++++++++++++++++ .../src/components/TerminalTabBar.vue | 4 + .../src/components/WorkspaceWorkbench.vue | 110 ++++++----- packages/frontend/src/locales/en-US.json | 11 ++ packages/frontend/src/locales/ja-JP.json | 10 + packages/frontend/src/locales/zh-CN.json | 11 ++ .../frontend/src/utils/connectionSearch.ts | 123 ++++++++++++ 16 files changed, 811 insertions(+), 57 deletions(-) create mode 100644 .helloagents/archive/2026-03/202603300204_global-server-quick-search/.status.json create mode 100644 .helloagents/archive/2026-03/202603300204_global-server-quick-search/proposal.md create mode 100644 .helloagents/archive/2026-03/202603300204_global-server-quick-search/tasks.md create mode 100644 .helloagents/archive/2026-03/202603300206_workspace-workbench-top-tabs/proposal.md create mode 100644 .helloagents/archive/2026-03/202603300206_workspace-workbench-top-tabs/tasks.md create mode 100644 packages/frontend/src/components/GlobalConnectionQuickSearch.vue create mode 100644 packages/frontend/src/utils/connectionSearch.ts diff --git a/.helloagents/CHANGELOG.md b/.helloagents/CHANGELOG.md index 2f9fd8a..0c9eb48 100644 --- a/.helloagents/CHANGELOG.md +++ b/.helloagents/CHANGELOG.md @@ -8,6 +8,8 @@ - 2026-03-25:继续微调 `/workspace` Workbench,新增默认“快捷指令”标签、调整三栏宽度到更接近 xterminal 参考图,并修复终端区域鼠标悬停时指针异常消失的问题。 ### 修复 +- **[frontend]**: 将 `/workspace` Workbench 的导航从左侧竖排 icon rail 调整为 `Workbench` header 上方的横向纯图标栏,保留原有四面板切换逻辑与信息头部层级 — by yinjianm + - 方案: [202603300206_workspace-workbench-top-tabs](archive/2026-03/202603300206_workspace-workbench-top-tabs/) - **[frontend]**: 将 `/workspace` 的 SSH 多终端展示从顶部组头胶囊改为“顶部只切服务器、终端面板内部切换同服务器多个终端”,修正服务器与终端的视觉层级 - by yinjianm - 方案: [202603292139_terminal-server-internal-tabs](archive/2026-03/202603292139_terminal-server-internal-tabs/) - **[frontend]**: 修复文件管理器右键菜单的回归关闭竞态,避免“终端 / 上传 / 压缩”子菜单在展开或点击前被捕获阶段监听提前关闭 - by yinjianm @@ -59,6 +61,8 @@ - 文件: packages/frontend/src/components/AddEditQuickCommandForm.vue:9,184-185,242-245 ### 新增 +- **[frontend]**: 为已登录页面新增 `Ctrl+Shift+F` 全局服务器快捷检索面板,支持模糊搜索并直接复用既有 SSH / RDP / VNC 连接链路 — by yinjianm + - 方案: [202603300204_global-server-quick-search](archive/2026-03/202603300204_global-server-quick-search/) - **[frontend]**: 为文件管理器补齐“上传文件夹”入口,选择目录后会先在浏览器端打包为 zip,再上传并自动触发远端解压 — by yinjianm - 方案: [202603260234_folder-upload-auto-zip](archive/2026-03/202603260234_folder-upload-auto-zip/) - **[frontend]**: 为工作台文件面板补齐左侧多根目录资源管理器,支持收藏路径与当前路径同屏作为多个根目录展开浏览 — by yinjianm diff --git a/.helloagents/archive/2026-03/202603300204_global-server-quick-search/.status.json b/.helloagents/archive/2026-03/202603300204_global-server-quick-search/.status.json new file mode 100644 index 0000000..4753520 --- /dev/null +++ b/.helloagents/archive/2026-03/202603300204_global-server-quick-search/.status.json @@ -0,0 +1 @@ +{"status":"completed","completed":6,"failed":0,"pending":0,"total":6,"done":6,"percent":100,"current":"全局服务器快捷检索与连接链路已完成","updated_at":"2026-03-30 02:28:00"} diff --git a/.helloagents/archive/2026-03/202603300204_global-server-quick-search/proposal.md b/.helloagents/archive/2026-03/202603300204_global-server-quick-search/proposal.md new file mode 100644 index 0000000..fce4c8d --- /dev/null +++ b/.helloagents/archive/2026-03/202603300204_global-server-quick-search/proposal.md @@ -0,0 +1,127 @@ +# 变更提案: global-server-quick-search + +## 元信息 +```yaml +类型: 新功能 +方案类型: implementation +优先级: P1 +状态: 开发中 +状态说明: 为已登录用户新增全局服务器快捷检索面板,复用现有连接链路自动进入工作区或打开远程桌面弹窗 +创建: 2026-03-30 +``` + +--- + +## 1. 需求 + +### 背景 +当前工作区只在局部位置提供服务器搜索与连接能力:顶部 `+` 弹窗里的连接列表支持搜索,`WorkspaceConnectionList.vue` 也支持本地筛选和键盘上下切换,但用户无法在任意已登录页面直接用快捷键唤起“搜索服务器并立即连接”的全局入口。 + +### 目标 +- 为已登录状态新增全局快捷键 `Ctrl+Shift+F` +- 按下后弹出独立输入框/面板,可按关键词模糊检索所有连接类型的服务器 +- 支持 `ArrowUp` / `ArrowDown` 在候选结果中切换,`Enter` 选中后自动连接 +- 连接动作必须复用现有 `sessionStore.handleConnectRequest()` 链路,保持 SSH / RDP / VNC 的既有行为一致 + +### 约束条件 +```yaml +时间约束: 本轮完成前端最小闭环,不扩展到后端接口和持久化最近搜索 +性能约束: 搜索在前端本地完成,结果限制在少量高相关候选,避免每次输入都触发远程请求 +兼容性约束: 不破坏现有 Alt 焦点切换、工作区连接列表搜索和既有 SSH/RDP/VNC 连接逻辑 +业务约束: 搜索范围固定为所有连接类型(SSH / RDP / VNC) +``` + +### 验收标准 +- [x] 已登录页面按下 `Ctrl+Shift+F` 能打开全局服务器检索面板 +- [x] 输入任意关键词后,能对 `SSH / RDP / VNC` 连接做模糊匹配并按相关度排序 +- [x] 面板支持 `ArrowUp` / `ArrowDown` 切换高亮项,`Enter` 自动连接,`Esc` 关闭 +- [x] 选中 `SSH` 时自动进入 `/workspace` 并按既有逻辑打开/激活会话 +- [x] 选中 `RDP / VNC` 时复用现有弹窗逻辑,不破坏原有连接行为 +- [ ] 前端构建通过 + +--- + +## 2. 方案 + +### 技术方案 +在 `App.vue` 注册全局快捷键监听,新增一个独立的全局连接检索组件承载输入框、结果列表和键盘导航。组件内部使用轻量模糊评分函数,对连接名称、主机、用户名和类型做本地排序;`App.vue` 在面板打开时确保连接缓存已加载,并在用户确认后直接调用 `sessionStore.handleConnectRequest(connection)`,利用现有逻辑处理 SSH 新建/重连、RDP 弹窗、VNC 弹窗和自动路由跳转。 + +### 影响范围 +```yaml +涉及模块: + - frontend: App.vue 负责全局快捷键监听、面板生命周期和连接提交 + - frontend: 新增全局连接检索组件/工具,负责模糊检索、结果渲染与键盘导航 + - frontend: locale 文案,补齐面板标题、占位符、空态和提示语 +预计变更文件: 5-7 +``` + +### 风险评估 +| 风险 | 等级 | 应对 | +|------|------|------| +| 全局键盘监听与现有 Alt 快捷键或输入框事件冲突 | 中 | 面板打开时优先消费快捷键,并对 Alt 逻辑加早退保护 | +| 不引入第三方库时模糊匹配排序不稳定 | 低 | 使用“精确包含优先 + 子序列匹配兜底”的轻量评分策略,并限制结果数量 | +| 非工作区页面直接连接遗漏既有跳转逻辑 | 低 | 统一走 `sessionStore.handleConnectRequest()`,由既有 router 负责跳转到 `/workspace` | + +--- + +## 3. 技术设计(可选) + +> 涉及架构变更、API设计、数据模型变更时填写 + +### 架构设计 +```mermaid +flowchart TD + A[App.vue 全局快捷键] --> B[GlobalConnectionQuickSearch] + B --> C[connectionSearch 工具] + B --> D[sessionStore.handleConnectRequest] + D --> E[Workspace / RDP / VNC 既有链路] +``` + +### API设计 +N/A,复用现有前端 store 与路由逻辑,不新增接口。 + +### 数据模型 +| 字段 | 类型 | 说明 | +|------|------|------| +| `GlobalSearchItem` | 前端派生结构 | 由 `ConnectionInfo` 派生,用于面板渲染和提交连接 | +| `query` | `string` | 当前检索关键词 | +| `selectedIndex` | `number` | 当前高亮候选索引 | + +--- + +## 4. 核心场景 + +> 执行完成后同步到对应模块文档 + +### 场景: 全局快捷检索并自动连接服务器 +**模块**: frontend +**条件**: 用户已登录,位于任意受保护页面,连接数据可从缓存或接口获取 +**行为**: 用户按下 `Ctrl+Shift+F` 打开全局检索面板,输入关键词后通过键盘上下选择目标服务器,按下 `Enter` 后提交现有连接链路 +**结果**: SSH 自动进入工作区并打开/激活会话,RDP / VNC 继续使用既有弹窗连接逻辑 + +--- + +## 5. 技术决策 + +> 本方案涉及的技术决策,归档后成为决策的唯一完整记录 + +### global-server-quick-search#D001: 全局快捷检索直接复用 sessionStore 连接入口 +**日期**: 2026-03-30 +**状态**: ✅采纳 +**背景**: 全局检索需要在工作区外也能发起连接,如果复用 workspace 事件总线,会受当前视图是否挂载相关组件影响 +**选项分析**: +| 选项 | 优点 | 缺点 | +|------|------|------| +| A: 直接调用 `sessionStore.handleConnectRequest()` | 可跨页面工作,自动复用 SSH/RDP/VNC 与路由逻辑,入口单一 | 需要在 App 层拿到 store 并管理连接数据 | +| B: 复用 `workspaceEmitter` 的 `connection:connect` 事件 | 能沿用部分工作区现有事件流 | 非工作区页面不稳定,依赖 `TerminalTabBar` 等订阅方已挂载 | +**决策**: 选择方案 A +**理由**: 该需求强调“全局”唤起,必须保证不论当前处于仪表盘、连接管理还是工作区都能直达既有连接逻辑,因此直接走 `sessionStore` 是最稳定的主入口 +**影响**: 影响 `App.vue`、全局检索组件以及连接缓存加载时机 + +--- + +## 6. 成果设计 + +> 含视觉产出的任务由 DESIGN Phase2 填充。非视觉任务整节标注"N/A"。 + +N/A,本次为现有前端界面内的功能型弹层增强,沿用项目既有主题变量与组件风格,不单独引入新的视觉体系。 diff --git a/.helloagents/archive/2026-03/202603300204_global-server-quick-search/tasks.md b/.helloagents/archive/2026-03/202603300204_global-server-quick-search/tasks.md new file mode 100644 index 0000000..dc979a6 --- /dev/null +++ b/.helloagents/archive/2026-03/202603300204_global-server-quick-search/tasks.md @@ -0,0 +1,57 @@ +# 任务清单: global-server-quick-search + +> **@status:** completed | 2026-03-30 02:15 + +```yaml +@feature: global-server-quick-search +@created: 2026-03-30 +@status: completed +@mode: R2 +``` + +## 进度概览 + +| 完成 | 失败 | 跳过 | 总数 | +|------|------|------|------| +| 6 | 0 | 0 | 6 | + +--- + +## 任务列表 + +### 1. 方案与范围确认 + +- [√] 1.1 创建全局服务器快捷检索方案包并锁定为前端实现 | depends_on: [] + +### 2. 全局检索能力实现 + +- [√] 2.1 新增全局服务器检索面板与本地模糊匹配排序 | depends_on: [1.1] +- [√] 2.2 在 `App.vue` 接入 `Ctrl+Shift+F` 打开/关闭逻辑并管理连接数据加载 | depends_on: [2.1] +- [√] 2.3 接通上下键切换、回车自动连接与现有 `sessionStore` 连接链路 | depends_on: [2.2] + +### 3. 文案与验证 + +- [√] 3.1 补齐多语言文案并执行前端构建验证 | depends_on: [2.3] +- [√] 3.2 同步前端知识库与 CHANGELOG 记录并完成归档 | depends_on: [3.1] + +--- + +## 执行日志 + +| 时间 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 2026-03-30 02:04 | 1.1 | 完成 | 创建 implementation 方案包,范围锁定为前端全局快捷检索与现有连接链路复用 | +| 2026-03-30 02:12 | 2.1 | 完成 | 新增 `GlobalConnectionQuickSearch.vue` 与 `connectionSearch.ts`,提供本地模糊搜索和结果排序 | +| 2026-03-30 02:15 | 2.2 | 完成 | `App.vue` 接入 `Ctrl+Shift+F` 全局快捷键、面板开关和连接列表加载 | +| 2026-03-30 02:17 | 2.3 | 完成 | 接通上下键、回车自动连接与 `sessionStore.handleConnectRequest()` 复用链路 | +| 2026-03-30 02:22 | 3.1 | 完成 | 补齐中英日文案,并通过 `npm run build --workspace @nexus-terminal/frontend` | +| 2026-03-30 02:28 | 3.2 | 完成 | 已同步 `frontend` 模块文档与 CHANGELOG,准备归档方案包 | + +--- + +## 执行备注 + +> 记录执行过程中的重要说明、决策变更、风险提示等 + +- 当前选择范围: 搜索所有连接类型(SSH / RDP / VNC) +- 默认策略: 全局唤起、局部实现,不新增后端接口 diff --git a/.helloagents/archive/2026-03/202603300206_workspace-workbench-top-tabs/proposal.md b/.helloagents/archive/2026-03/202603300206_workspace-workbench-top-tabs/proposal.md new file mode 100644 index 0000000..4e1e1bc --- /dev/null +++ b/.helloagents/archive/2026-03/202603300206_workspace-workbench-top-tabs/proposal.md @@ -0,0 +1,117 @@ +# 变更提案: workspace-workbench-top-tabs + +## 元信息 +```yaml +类型: 优化 +方案类型: implementation +优先级: P2 +状态: 已完成 +状态说明: 已完成实现、构建验证通过,待归档 +创建: 2026-03-30 +``` + +--- + +## 1. 需求 + +### 背景 +当前 `WorkspaceWorkbench.vue` 把四个入口做成最左侧窄 icon rail,竖向贴在内容区左侧;用户希望保留纯图标按钮风格,但把导航挪到 `Workbench` 标题区上方,改成横向排列,以更符合预期的顶部工作台切换方式。 + +### 目标 +- 保持 Workbench 的四个入口仍为纯图标按钮。 +- 将 `快捷指令 / 文件 / 历史命令 / 编辑器` 导航从左侧竖排 rail 改为顶部横向栏。 +- 保留现有 `Workbench` 头部信息与下方内容区切换逻辑,不改变三栏主布局与面板能力。 + +### 约束条件 +```yaml +范围约束: 仅调整 `packages/frontend/src/components/WorkspaceWorkbench.vue` 的导航结构与对应样式 +交互约束: 保持 `v-show` 常驻切换,不引入新的状态管理或路由逻辑 +视觉约束: 保留当前高对比深色渐变工作台风格,按钮仍以纯图标为主 +兼容性约束: 不改动 Workbench 四个面板的组件挂载关系,避免影响文件管理器与编辑器状态保持 +``` + +### 验收标准 +- [ ] Workbench 导航显示在 header 上方,并按横向排列展示四个图标按钮 +- [ ] 当前激活态、hover 态和默认快捷指令初始选中行为保持不变 +- [ ] `Workbench` 标题、会话名和“工作台”标签仍保留在横向导航下方 +- [ ] 前端构建通过,无新增类型错误 + +--- + +## 2. 方案 + +### 技术方案 +将 `WorkspaceWorkbench.vue` 的根布局从“左右两栏”改为“上下三段”:顶部 `workbench-rail` 横向图标栏、中部原有 `Workbench` header、底部内容区。复用现有 `workbenchTabs` 数据与 `activeWorkbenchTab` 切换逻辑,仅调整模板结构和 `workbench-rail` 样式方向,避免触碰四个面板组件的生命周期与数据流。 + +### 影响范围 +```yaml +涉及模块: + - frontend: 更新 Workbench 导航结构、样式与知识库文档描述 +预计变更文件: 4 +``` + +### 风险评估 +| 风险 | 等级 | 应对 | +|------|------|------| +| 横向栏在窄宽度 pane 中压缩过度 | 低 | 保持纯图标按钮尺寸,并让导航容器支持横向排布 | +| 调整容器层级后影响内容区高度计算 | 低 | 维持根容器 `flex-col` + 内容区 `flex-1 min-h-0` 结构 | +| 知识库仍保留“左侧竖排 rail”旧描述 | 低 | 同步更新 `frontend.md` 与 `CHANGELOG.md` | + +--- + +## 3. 技术设计(可选) + +N/A,本次不涉及 API、数据模型或跨组件数据流改造。 + +--- + +## 4. 核心场景 + +> 执行完成后同步到对应模块文档 + +### 场景: 工作台顶部导航切换 +**模块**: frontend +**条件**: 用户进入 `/workspace`,左侧 Workbench pane 已展示。 +**行为**: 用户在 `Workbench` 标题区上方使用横向纯图标导航切换 `快捷指令 / 文件 / 历史命令 / 编辑器` 面板。 +**结果**: 内容区沿用现有常驻切换逻辑,仅切换可见面板,不重构整体三栏布局。 + +--- + +## 5. 技术决策 + +> 本方案涉及的技术决策,归档后成为决策的唯一完整记录 + +### workspace-workbench-top-tabs#D001: Workbench 导航改为顶部独立横向图标栏 +**日期**: 2026-03-30 +**状态**: ✅采纳 +**背景**: 用户明确要求将当前位于内容区左侧的竖排图标 rail 移到 `Workbench` header 上方,同时保留纯图标表现。 +**选项分析**: +| 选项 | 优点 | 缺点 | +|------|------|------| +| A: 顶部独立一行横向图标栏 | 保留现有图标按钮密度;与 header 信息分层清晰;改动范围最小 | 占用少量垂直空间 | +| B: 并入 header 同一行 | 视觉更紧凑 | 会挤压会话标题区域,窄宽度下更容易拥挤 | +**决策**: 选择方案 A +**理由**: 这是用户明确确认的方案,且只需调整模板层级与 rail 样式方向,不影响现有 header 文案布局和内容区状态保持。 +**影响**: 影响 `WorkspaceWorkbench.vue` 的 DOM 结构、样式方向,以及 `frontend.md` 中对 Workbench 导航形态的描述。 + +--- + +## 6. 成果设计 + +> 含视觉产出的任务由 DESIGN Phase2 填充。非视觉任务整节标注"N/A"。 + +### 设计方向 +- **美学基调**: 深色控制台工具条,像设备面板顶部功能拨片一样把四个入口压缩成紧凑横向导航 +- **记忆点**: 顶部一条窄而亮的图标导航带,与下方信息型 header 形成明显分层 +- **参考**: 用户提供的现有 Workbench 截图与“放到标题上方、横着排”的明确要求 + +### 视觉要素 +- **配色**: 继续沿用现有深蓝黑渐变工具条,激活项保持 `primary` 高亮,非激活项维持低饱和灰阶 +- **字体**: 沿用项目现有字体体系,本次视觉重点不在字形,而在图标导航层级 +- **布局**: 顶部横向图标栏单独占一行,下方保留信息型 header,再下方为内容区 +- **动效**: 延续现有按钮 hover/active 过渡,不新增额外动画 +- **氛围**: rail 保留深色渐变与内阴影,但阴影方向从纵向分隔改为横向分隔 + +### 技术约束 +- **可访问性**: 保留现有 `title` 与 `aria-label`,确保纯图标按钮仍可被识别 +- **响应式**: 顶部导航需在窄 pane 宽度下仍保持横向排列,不引入文字标签导致拥挤 diff --git a/.helloagents/archive/2026-03/202603300206_workspace-workbench-top-tabs/tasks.md b/.helloagents/archive/2026-03/202603300206_workspace-workbench-top-tabs/tasks.md new file mode 100644 index 0000000..89c99de --- /dev/null +++ b/.helloagents/archive/2026-03/202603300206_workspace-workbench-top-tabs/tasks.md @@ -0,0 +1,52 @@ +# 任务清单: workspace-workbench-top-tabs + +> **@status:** completed | 2026-03-30 02:12 + +```yaml +@feature: workspace-workbench-top-tabs +@created: 2026-03-30 +@status: completed +@mode: R2 +``` + +## 进度概览 + +| 完成 | 失败 | 跳过 | 总数 | +|------|------|------|------| +| 5 | 0 | 0 | 5 | + +--- + +## 任务列表 + +### 1. 方案与上下文 + +- [√] 1.1 创建 Workbench 顶部横向导航方案包并记录用户确认的纯图标方案 | depends_on: [] + +### 2. 前端实现 + +- [√] 2.1 在 `packages/frontend/src/components/WorkspaceWorkbench.vue` 中将左侧竖排 rail 改为顶部横向图标导航栏 | depends_on: [1.1] +- [√] 2.2 校准 `workbench-rail` 样式方向与容器层级,确保 header 与内容区高度计算不回归 | depends_on: [2.1] + +### 3. 验证与知识库同步 + +- [√] 3.1 运行前端构建验证,确认本次 Workbench 结构调整无类型或打包错误 | depends_on: [2.2] +- [√] 3.2 同步更新 `.helloagents/modules/frontend.md` 与 `.helloagents/CHANGELOG.md` 的 Workbench 导航描述 | depends_on: [3.1] + +--- + +## 执行日志 + +| 时间 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 2026-03-30 02:06 | 1.1 | 完成 | 创建 implementation 方案包,并记录用户确认采用顶部横向纯图标导航 | +| 2026-03-30 02:08 | 2.1 / 2.2 | 完成 | 将 Workbench 左侧竖排 icon rail 调整为 header 上方横向 icon rail,并同步 rail 渐变方向 | +| 2026-03-30 02:09 | 3.1 | 完成 | 执行 `npm run build --workspace @nexus-terminal/frontend`,构建通过,仅保留既有 chunk 体积与 dynamic import 警告 | +| 2026-03-30 02:10 | 3.2 | 完成 | 更新 frontend 模块文档与 CHANGELOG,准备归档 | + +--- + +## 执行备注 + +- 本次只调整 Workbench 内部导航层级,不改三栏主布局和四个面板的组件关系。 +- 用户已确认保留纯图标风格,不切到“图标 + 文本”标签样式。 diff --git a/.helloagents/archive/_index.md b/.helloagents/archive/_index.md index 292d372..e0b1c3c 100644 --- a/.helloagents/archive/_index.md +++ b/.helloagents/archive/_index.md @@ -7,6 +7,8 @@ | 时间戳 | 名称 | 类型 | 涉及模块 | 决策 | 结果 | |--------|------|------|---------|------|------| +| 202603300204 | global-server-quick-search | - | - | - | ✅完成 | +| 202603300206 | workspace-workbench-top-tabs | implementation | frontend | workspace-workbench-top-tabs#D001 | ✅完成 | | 202603292139 | terminal-server-internal-tabs | - | - | - | ✅完成 | | 202603292247 | workbench-left-icon-rail | - | - | - | ✅完成 | | 202603292300 | terminal-tab-close-all | implementation | frontend | terminal-tab-close-all#D001 | ✅完成 | @@ -43,6 +45,7 @@ ## 按月归档 ### 2026-03 +- [202603300206_workspace-workbench-top-tabs](./2026-03/202603300206_workspace-workbench-top-tabs/) - 将 Workbench 的导航从左侧竖排 icon rail 调整为 `Workbench` header 上方的横向纯图标栏 - [202603292300_terminal-tab-close-all](./2026-03/202603292300_terminal-tab-close-all/) - 为终端标签右键菜单补充“关闭全部”,并复用现有工作区会话清理链路 - [202603260527_file-manager-context-submenu-regression](./2026-03/202603260527_file-manager-context-submenu-regression/) - 修复文件管理器右键菜单回归关闭竞态,恢复终端、上传、压缩等子菜单展开与点击 - [202603260324_file-manager-delete-upload-stability](./2026-03/202603260324_file-manager-delete-upload-stability/) - 修复文件管理器右键子菜单点击、拖拽上传目标确认、目录删除模式选择与删除后路径失效回退 diff --git a/.helloagents/modules/frontend.md b/.helloagents/modules/frontend.md index c533674..25e5517 100644 --- a/.helloagents/modules/frontend.md +++ b/.helloagents/modules/frontend.md @@ -36,7 +36,7 @@ ### 工作区交互 **条件**: 用户进入 `/workspace` 或相关管理页面。 -**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 继续整合快捷指令、命令历史、文件管理和编辑器四个面板,但导航入口已从顶部文本 tab 收敛为最左侧窄 icon rail,四个入口图标自上而下竖排排列、默认仅显示图标并通过 tooltip 暴露名称,默认激活快捷指令。`CommandInputBar.vue` 当前已将底部命令框升级为支持会话级草稿保留的多行 `textarea`:普通 `Enter` 插入换行,`Ctrl+Shift+Enter` 发送当前命令,输入框会按内容自动增高至约 6 行,超出后在输入框内部滚动,并继续兼容快捷指令/命令历史同步与选中发送逻辑。快捷指令相关能力目前由 `AddEditQuickCommandForm.vue`、`QuickCommandsView.vue` 与新增的 `utils/quickCommandTemplate.ts` 协同实现:编辑弹窗左侧既可维护自定义 `${变量名}`,也提供 `${{date}}`、`${{time}}`、`${{timestamp}}`、`${{week}}`、`${{uuid}}`、`${{random:8}}`、`${{clipboard}}`、`${{password}}` 等动态变量的一键插入;实际执行时会统一走共享解析器,覆盖编辑弹窗执行、列表直接执行、粘贴到命令输入框和发送到全部服务器等链路,并对未定义变量、无法读取的剪贴板或不可用密码给出非阻断告警。`QuickCommandsView.vue` 内的新增按钮、空状态按钮和列表操作按钮统一复用 `bg-button`、`text-button-text`、`hover:bg-button-hover`、`hover:bg-border` 等主题变量类,避免写死黑白 hover 色值;该视图当前还支持命令项右键菜单,并已修正为实底卡片式上下文菜单,提供立即执行、粘贴到命令输入框(不自动发送)、复制命令、发送到全部服务器、编辑和删除等动作。`Terminal.vue` 会跟踪 xterm 的视口行号与贴底状态,在终端标签切换、重新激活和 `fit()` 后按原滚动意图恢复,并在渲染层为带 `xterm-fg-*` class 或内联 `style.color` 的显式前景色字符打标记,让终端文字描边/阴影仅作用于默认前景文本,不覆盖 ANSI 彩色输出;`session.store` 当前会为同一 SSH 连接下的新终端分配递增的 `terminalIndex`。当前顶部 `TerminalTabBar.vue` 已改为服务器级入口:SSH 项只负责在不同服务器之间切换,全局 `+` 继续负责选择其他服务器;同一服务器下的多个终端则下沉到 `LayoutRenderer.vue` 的终端面板内部,以次级标签条承载切换、关闭和新增,从而让“进入服务器后再管理该服务器的多个终端”成为主要交互模型。当前终端标签右键菜单继续复用 `WorkspaceView.vue` 中转的会话关闭链路,除关闭当前、关闭其他、关闭左右侧外,也支持直接触发“关闭全部”来清空当前工作区中的全部终端标签。`ConnectionsView.vue` 已升级为“左侧范围树 + 顶部搜索工具条 + 右侧结果列表”的双栏管理台,当前左侧进一步支持基于标签名路径分隔符推导的多级标签树、树节点展开状态持久化、分组 scope 恢复,以及树工具栏中的展开全部、收起全部和重置范围控制;近期又补上了独立的左侧树搜索、命中节点及祖先路径过滤、命中链路自动展开、节点计数高亮,以及更接近资源管理器的树头部布局;本轮继续为树节点加入 hover 工具按钮、资源管理器式分隔标题行与拖拽重排占位反馈;右侧结果列表则同时支持顶部排序控件、列头点击排序,并将行内操作整理为“连接”主按钮加“更多”菜单(编辑/测试/克隆/删除);`FileManager.vue` 当前已进一步收敛为固定 `/` 根节点的单栏资源管理器树,组件加载时会优先拉取 `/` 目录,树中按“目录在前、文件在后”同时显示目录和文件节点,点击目录只展开与聚焦,点击文件则沿用现有工作区文件打开链路;文件右键菜单链路则已补齐图标化菜单结构、危险态删除项、终端子菜单(执行 `cd` 命令到终端 / 新建终端到当前目录)、复制文件名与复制绝对路径等动作,并继续复用现有下载、权限、新建、上传和删除逻辑,同时又新增了独立“上传文件夹”入口:前端会先将本地目录打包为 zip,再复用现有 `sftp:upload` 链路上传,并在上传成功后自动调用远端解压、尝试清理临时压缩包;外部拖拽文件或目录上传时,则会按鼠标当前悬停的目录作为目标路径,其中目录同样走“先压缩再上传”的路径,从而显著降低小文件很多时的扫描与上传耗时;本轮又补上了拖拽上传前的目标路径确认、桌面端右键子菜单点击展开,以及目录删除时“仅删空目录 / 强制递归删除”的显式二选一;当前右键菜单的关闭职责已经收敛到 `FileManagerContextMenu.vue` 组件层处理,`useFileManagerContextMenu.ts` 不再额外注册捕获阶段的全局点击关闭监听,以避免“终端 / 上传 / 压缩”等带子菜单项在展开或点击前被提前关闭;同时 `useSftpActions.ts` 会在删除目录后自动回退当前或待加载的失效路径,避免文件树持续对已删除目录刷出 `No such file`。样式编辑器中的终端文字描边/阴影默认开关也已与新的黑绿终端风格保持默认开启。 +**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 继续整合快捷指令、命令历史、文件管理和编辑器四个面板,导航入口保持为纯图标按钮,但已调整为位于 `Workbench` 标题区上方的横向 icon rail,四个入口自左向右排列、默认仅显示图标并通过 tooltip 暴露名称,默认激活快捷指令。`CommandInputBar.vue` 当前已将底部命令框升级为支持会话级草稿保留的多行 `textarea`:普通 `Enter` 插入换行,`Ctrl+Shift+Enter` 发送当前命令,输入框会按内容自动增高至约 6 行,超出后在输入框内部滚动,并继续兼容快捷指令/命令历史同步与选中发送逻辑。应用根组件 `App.vue` 现在还新增了全局服务器快捷检索:已登录页面按下 `Ctrl+Shift+F` 会打开 `GlobalConnectionQuickSearch.vue`,通过 `utils/connectionSearch.ts` 对连接名称、主机、用户名和类型做本地模糊排序,并直接复用 `sessionStore.handleConnectRequest()` 触发 SSH 工作区跳转或 RDP / VNC 弹窗连接。快捷指令相关能力目前由 `AddEditQuickCommandForm.vue`、`QuickCommandsView.vue` 与新增的 `utils/quickCommandTemplate.ts` 协同实现:编辑弹窗左侧既可维护自定义 `${变量名}`,也提供 `${{date}}`、`${{time}}`、`${{timestamp}}`、`${{week}}`、`${{uuid}}`、`${{random:8}}`、`${{clipboard}}`、`${{password}}` 等动态变量的一键插入;实际执行时会统一走共享解析器,覆盖编辑弹窗执行、列表直接执行、粘贴到命令输入框和发送到全部服务器等链路,并对未定义变量、无法读取的剪贴板或不可用密码给出非阻断告警。`QuickCommandsView.vue` 内的新增按钮、空状态按钮和列表操作按钮统一复用 `bg-button`、`text-button-text`、`hover:bg-button-hover`、`hover:bg-border` 等主题变量类,避免写死黑白 hover 色值;该视图当前还支持命令项右键菜单,并已修正为实底卡片式上下文菜单,提供立即执行、粘贴到命令输入框(不自动发送)、复制命令、发送到全部服务器、编辑和删除等动作。`Terminal.vue` 会跟踪 xterm 的视口行号与贴底状态,在终端标签切换、重新激活和 `fit()` 后按原滚动意图恢复,并在渲染层为带 `xterm-fg-*` class 或内联 `style.color` 的显式前景色字符打标记,让终端文字描边/阴影仅作用于默认前景文本,不覆盖 ANSI 彩色输出;`session.store` 当前会为同一 SSH 连接下的新终端分配递增的 `terminalIndex`。当前顶部 `TerminalTabBar.vue` 已改为服务器级入口:SSH 项只负责在不同服务器之间切换,全局 `+` 继续负责选择其他服务器;同一服务器下的多个终端则下沉到 `LayoutRenderer.vue` 的终端面板内部,以次级标签条承载切换、关闭和新增,从而让“进入服务器后再管理该服务器的多个终端”成为主要交互模型。当前终端标签右键菜单继续复用 `WorkspaceView.vue` 中转的会话关闭链路,除关闭当前、关闭其他、关闭左右侧外,也支持直接触发“关闭全部”来清空当前工作区中的全部终端标签。`ConnectionsView.vue` 已升级为“左侧范围树 + 顶部搜索工具条 + 右侧结果列表”的双栏管理台,当前左侧进一步支持基于标签名路径分隔符推导的多级标签树、树节点展开状态持久化、分组 scope 恢复,以及树工具栏中的展开全部、收起全部和重置范围控制;近期又补上了独立的左侧树搜索、命中节点及祖先路径过滤、命中链路自动展开、节点计数高亮,以及更接近资源管理器的树头部布局;本轮继续为树节点加入 hover 工具按钮、资源管理器式分隔标题行与拖拽重排占位反馈;右侧结果列表则同时支持顶部排序控件、列头点击排序,并将行内操作整理为“连接”主按钮加“更多”菜单(编辑/测试/克隆/删除);`FileManager.vue` 当前已进一步收敛为固定 `/` 根节点的单栏资源管理器树,组件加载时会优先拉取 `/` 目录,树中按“目录在前、文件在后”同时显示目录和文件节点,点击目录只展开与聚焦,点击文件则沿用现有工作区文件打开链路;文件右键菜单链路则已补齐图标化菜单结构、危险态删除项、终端子菜单(执行 `cd` 命令到终端 / 新建终端到当前目录)、复制文件名与复制绝对路径等动作,并继续复用现有下载、权限、新建、上传和删除逻辑,同时又新增了独立“上传文件夹”入口:前端会先将本地目录打包为 zip,再复用现有 `sftp:upload` 链路上传,并在上传成功后自动调用远端解压、尝试清理临时压缩包;外部拖拽文件或目录上传时,则会按鼠标当前悬停的目录作为目标路径,其中目录同样走“先压缩再上传”的路径,从而显著降低小文件很多时的扫描与上传耗时;本轮又补上了拖拽上传前的目标路径确认、桌面端右键子菜单点击展开,以及目录删除时“仅删空目录 / 强制递归删除”的显式二选一;当前右键菜单的关闭职责已经收敛到 `FileManagerContextMenu.vue` 组件层处理,`useFileManagerContextMenu.ts` 不再额外注册捕获阶段的全局点击关闭监听,以避免“终端 / 上传 / 压缩”等带子菜单项在展开或点击前被提前关闭;同时 `useSftpActions.ts` 会在删除目录后自动回退当前或待加载的失效路径,避免文件树持续对已删除目录刷出 `No such file`。样式编辑器中的终端文字描边/阴影默认开关也已与新的黑绿终端风格保持默认开启。 **结果**: 页面逻辑分散在 `views/`、`components/`、`stores/` 与 `composables/`,其中工作区终端行为和标签交互优先落在 `session.store.ts`、`session/actions/sessionActions.ts`、`session/getters.ts`、`TerminalTabBar.vue`、`WorkspaceView.vue`、`Terminal.vue` 与相关 locale 文件。 ### 仪表盘总览 diff --git a/packages/frontend/src/App.vue b/packages/frontend/src/App.vue index db689f9..f059309 100644 --- a/packages/frontend/src/App.vue +++ b/packages/frontend/src/App.vue @@ -9,12 +9,14 @@ import { useAppearanceStore } from './stores/appearance.store'; import { useLayoutStore } from './stores/layout.store'; import { useFocusSwitcherStore } from './stores/focusSwitcher.store'; import { useSessionStore } from './stores/session.store'; +import { useConnectionsStore } from './stores/connections.store'; import { useFavoritePathsStore } from './stores/favoritePaths.store'; import { storeToRefs } from 'pinia'; import UINotificationDisplay from './components/UINotificationDisplay.vue'; import FileEditorOverlay from './components/FileEditorOverlay.vue'; import StyleCustomizer from './components/StyleCustomizer.vue'; import FocusSwitcherConfigurator from './components/FocusSwitcherConfigurator.vue'; +import GlobalConnectionQuickSearch from './components/GlobalConnectionQuickSearch.vue'; import RemoteDesktopModal from './components/RemoteDesktopModal.vue'; import VncModal from './components/VncModal.vue'; import ConfirmDialog from './components/common/ConfirmDialog.vue'; @@ -27,10 +29,12 @@ const appearanceStore = useAppearanceStore(); const layoutStore = useLayoutStore(); const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++ const sessionStore = useSessionStore(); // +++ 实例化 Session Store +++ +const connectionsStore = useConnectionsStore(); const dialogStore = useDialogStore(); // +++ 实例化 DialogStore +++ const { state: dialogState } = storeToRefs(dialogStore); const favoritePathsStore = useFavoritePathsStore(); // +++ 实例化 favoritePathsStore +++ const { isAuthenticated } = storeToRefs(authStore); +const { connections, isLoading: connectionsLoading } = storeToRefs(connectionsStore); const { showPopupFileEditorBoolean } = storeToRefs(settingsStore); const { isStyleCustomizerVisible } = storeToRefs(appearanceStore); const { isLayoutVisible, isHeaderVisible } = storeToRefs(layoutStore); // 添加 isHeaderVisible @@ -46,6 +50,7 @@ const underlineRef = ref(null); const lastFocusedIdBySwitcher = ref(null); const isAltPressed = ref(false); // 跟踪 Alt 键是否按下 const altShortcutKey = ref(null); +const isGlobalConnectionSearchVisible = ref(false); // --- 移除 shortcutTriggeredInKeyDown 标志 --- const updateUnderline = async () => { @@ -70,6 +75,7 @@ onMounted(() => { setTimeout(updateUnderline, 100); // +++ 全局 Alt 键监听器 +++ + window.addEventListener('keydown', handleGlobalShortcutKeyDown); window.addEventListener('keydown', handleAltKeyDown); // +++ 监听 keydown 设置状态 +++ window.addEventListener('keyup', handleGlobalKeyUp); // +++ 监听 keyup 执行切换 +++ @@ -96,6 +102,7 @@ watch(isAuthenticated, (loggedIn) => { // +++ 卸载钩子以移除监听器 +++ onUnmounted(() => { + window.removeEventListener('keydown', handleGlobalShortcutKeyDown); window.removeEventListener('keydown', handleAltKeyDown); // +++ 移除 keydown 监听 +++ window.removeEventListener('keyup', handleGlobalKeyUp); // +++ 移除 keyup 监听 +++ }); @@ -108,6 +115,12 @@ watch(route, () => { updateUnderline(); }, { immediate: true }); // *** 确保 immediate: true 存在 *** +watch(isAuthenticated, (loggedIn) => { + if (!loggedIn) { + isGlobalConnectionSearchVisible.value = false; + } +}); + const handleLogout = () => { authStore.logout(); @@ -123,8 +136,43 @@ const closeStyleCustomizer = () => { appearanceStore.toggleStyleCustomizer(false); }; +const openGlobalConnectionSearch = () => { + if (!isAuthenticated.value) { + return; + } + + isGlobalConnectionSearchVisible.value = true; + void connectionsStore.fetchConnections(); +}; + +const closeGlobalConnectionSearch = () => { + isGlobalConnectionSearchVisible.value = false; +}; + +const handleGlobalConnectionSelect = (connection: (typeof connections.value)[number]) => { + closeGlobalConnectionSearch(); + sessionStore.handleConnectRequest(connection); +}; + +const handleGlobalShortcutKeyDown = (event: KeyboardEvent) => { + const key = event.key.length === 1 ? event.key.toLowerCase() : event.key; + if (!(event.ctrlKey && event.shiftKey && key === 'f')) { + return; + } + + if (!isAuthenticated.value) { + return; + } + + event.preventDefault(); + if (!isGlobalConnectionSearchVisible.value) { + openGlobalConnectionSearch(); + } +}; + // +++ 处理 Alt 键按下的事件处理函数,并记录快捷键 +++ const handleAltKeyDown = async (event: KeyboardEvent) => { // +++ 改为 async +++ + if (isGlobalConnectionSearchVisible.value) return; if (!isWorkspaceRoute.value) return; // 只在 workspace 路由下执行 // 只在 Alt 键首次按下时设置状态 if (event.key === 'Alt' && !event.repeat) { @@ -178,6 +226,7 @@ const handleAltKeyDown = async (event: KeyboardEvent) => { // +++ 改为 async + // +++ 全局键盘事件处理函数,监听 keyup,优先处理快捷键 +++ const handleGlobalKeyUp = async (event: KeyboardEvent) => { + if (isGlobalConnectionSearchVisible.value) return; if (!isWorkspaceRoute.value) return; // 只在 workspace 路由下执行 if (event.key === 'Alt') { const altWasPressed = isAltPressed.value; @@ -363,6 +412,14 @@ const isElementVisibleAndFocusable = (element: HTMLElement): boolean => { @update:visible="(val: boolean) => dialogStore.state.visible = val" /> + + diff --git a/packages/frontend/src/components/GlobalConnectionQuickSearch.vue b/packages/frontend/src/components/GlobalConnectionQuickSearch.vue new file mode 100644 index 0000000..4c1630e --- /dev/null +++ b/packages/frontend/src/components/GlobalConnectionQuickSearch.vue @@ -0,0 +1,179 @@ + + + diff --git a/packages/frontend/src/components/TerminalTabBar.vue b/packages/frontend/src/components/TerminalTabBar.vue index 2e2aa8b..0be7317 100644 --- a/packages/frontend/src/components/TerminalTabBar.vue +++ b/packages/frontend/src/components/TerminalTabBar.vue @@ -243,6 +243,9 @@ const handleContextMenuAction = (payload: { action: string; targetId: string | n case 'close': emitWorkspaceEvent('session:close', { sessionId: targetId }); break; + case 'close-all': + emitWorkspaceEvent('session:closeAll'); + break; case 'close-others': emitWorkspaceEvent('session:closeOthers', { targetSessionId: targetId }); break; @@ -324,6 +327,7 @@ const contextMenuItems = computed(() => { items.push({ label: 'tabs.contextMenu.close', action: 'close' }); if (totalTabs > 1) { + items.push({ label: 'tabs.contextMenu.closeAll', action: 'close-all' }); items.push({ label: 'tabs.contextMenu.closeOthers', action: 'close-others' }); } diff --git a/packages/frontend/src/components/WorkspaceWorkbench.vue b/packages/frontend/src/components/WorkspaceWorkbench.vue index 59b0807..428fa93 100644 --- a/packages/frontend/src/components/WorkspaceWorkbench.vue +++ b/packages/frontend/src/components/WorkspaceWorkbench.vue @@ -97,8 +97,8 @@ watch(