fix(workspace): refine workbench defaults and cursor
Add Quick Commands as the default Workbench tab and update the default three-column layout proportions to better match the intended workspace design. Also fix terminal hover cursor behavior and add locale and docs updates for the new default interaction.
This commit is contained in:
@@ -5,3 +5,4 @@
|
||||
- 2026-03-25:初始化 `.helloagents/` 知识库骨架与首批模块文档,不代表源码功能变更。
|
||||
- 2026-03-25:新增 GHCR Docker 发布 workflow,并将 `docker-compose.yml` 的三个业务镜像切换到 `ghcr.io/micah123321/*`。
|
||||
- 2026-03-25:`/workspace` 默认布局改为“左侧 Workbench + 中央视终端 + 右侧状态监控”,并在状态监控中新增开机累计上下行流量展示。
|
||||
- 2026-03-25:继续微调 `/workspace` Workbench,新增默认“快捷指令”标签、调整三栏宽度到更接近 xterminal 参考图,并修复终端区域鼠标悬停时指针异常消失的问题。
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
| 2026-03-25 12:20 | 3.1 / 3.2 | 完成 | 后端透出累计上下行字节数,前端状态监控新增“开机累计流量”展示 |
|
||||
| 2026-03-25 12:31 | 4.1 | 完成 | 执行 `npm install` 后,前后端 build 均通过 |
|
||||
| 2026-03-25 12:33 | 4.2 | 完成 | 更新模块文档、变更日志并准备归档 |
|
||||
| 2026-03-25 12:48 | follow-up | 完成 | Workbench 新增默认快捷指令 tab,微调三栏宽度并修复终端鼠标悬停指针异常 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
|
||||
### 工作区交互
|
||||
**条件**: 用户进入 `/workspace` 或相关管理页面。
|
||||
**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 以 tab 容器整合命令历史、文件管理和编辑器。
|
||||
**结果**: 页面逻辑分散在 `views/`、`components/`、`stores/` 与 `composables/`,其中布局调整优先落在 `layout.store.ts`、`LayoutRenderer.vue` 与 `WorkspaceWorkbench.vue`。
|
||||
**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 以 tab 容器整合快捷指令、命令历史、文件管理和编辑器,默认激活快捷指令。
|
||||
**结果**: 页面逻辑分散在 `views/`、`components/`、`stores/` 与 `composables/`,其中布局与交互微调优先落在 `layout.store.ts`、`LayoutRenderer.vue`、`WorkspaceWorkbench.vue` 和 `Terminal.vue`。
|
||||
|
||||
## 依赖关系
|
||||
|
||||
|
||||
@@ -739,6 +739,17 @@ watchEffect(() => {
|
||||
/* z-index 调整或移除,因为背景层不再在此组件内 */
|
||||
}
|
||||
|
||||
.terminal-inner-container,
|
||||
.terminal-inner-container :deep(.xterm),
|
||||
.terminal-inner-container :deep(.xterm-screen),
|
||||
.terminal-inner-container :deep(.xterm-viewport) {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.terminal-inner-container :deep(.xterm .xterm-cursor-pointer) {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
/* 文字描边和阴影样式 */
|
||||
.terminal-inner-container.has-text-stroke :deep(.xterm-rows span),
|
||||
.terminal-inner-container.has-text-stroke :deep(.xterm-rows div > span), /* 更具体地针对嵌套 span */
|
||||
|
||||
@@ -3,13 +3,14 @@ import { computed, ref, watch, type PropType } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import CommandHistoryView from '../views/CommandHistoryView.vue';
|
||||
import QuickCommandsView from '../views/QuickCommandsView.vue';
|
||||
import FileManager from './FileManager.vue';
|
||||
import FileEditorContainer from './FileEditorContainer.vue';
|
||||
import { useSessionStore } from '../stores/session.store';
|
||||
import type { FileTab } from '../stores/fileEditor.store';
|
||||
import type { WebSocketDependencies } from '../composables/useSftpActions';
|
||||
|
||||
type WorkbenchTab = 'files' | 'history' | 'editor';
|
||||
type WorkbenchTab = 'quickCommands' | 'files' | 'history' | 'editor';
|
||||
|
||||
const props = defineProps({
|
||||
tabs: {
|
||||
@@ -42,9 +43,14 @@ const { t } = useI18n();
|
||||
const sessionStore = useSessionStore();
|
||||
const { sessions } = storeToRefs(sessionStore);
|
||||
|
||||
const activeWorkbenchTab = ref<WorkbenchTab>('files');
|
||||
const activeWorkbenchTab = ref<WorkbenchTab>('quickCommands');
|
||||
|
||||
const workbenchTabs = computed(() => [
|
||||
{
|
||||
id: 'quickCommands' as const,
|
||||
label: t('workspace.workbench.tabs.quickCommands', '快捷指令'),
|
||||
icon: 'fas fa-bolt',
|
||||
},
|
||||
{
|
||||
id: 'files' as const,
|
||||
label: t('workspace.workbench.tabs.files', '文件'),
|
||||
@@ -106,7 +112,7 @@ watch(
|
||||
{{ t('workspace.workbench.label', '工作台') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-3 grid grid-cols-3 gap-2">
|
||||
<div class="mt-3 grid grid-cols-2 gap-2 xl:grid-cols-4">
|
||||
<button
|
||||
v-for="tab in workbenchTabs"
|
||||
:key="tab.id"
|
||||
@@ -126,6 +132,10 @@ watch(
|
||||
</div>
|
||||
|
||||
<div class="relative flex-1 min-h-0 overflow-hidden bg-background">
|
||||
<div v-show="activeWorkbenchTab === 'quickCommands'" class="absolute inset-0 min-h-0 workbench-quick-commands">
|
||||
<QuickCommandsView />
|
||||
</div>
|
||||
|
||||
<div v-show="activeWorkbenchTab === 'files'" class="absolute inset-0 min-h-0">
|
||||
<FileManager
|
||||
v-if="hasFileManagerContext"
|
||||
@@ -163,3 +173,58 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.workbench-quick-commands {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(15, 17, 22, 0.98) 0%, rgba(12, 14, 18, 1) 100%);
|
||||
}
|
||||
|
||||
.workbench-quick-commands :deep(> div),
|
||||
.workbench-quick-commands :deep(> div > div) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.workbench-quick-commands :deep(input) {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
color: #f5f7fa;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.workbench-quick-commands :deep(input::placeholder) {
|
||||
color: rgba(226, 232, 240, 0.55);
|
||||
}
|
||||
|
||||
.workbench-quick-commands :deep(button) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.workbench-quick-commands :deep([data-command-id]) {
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.workbench-quick-commands :deep([data-command-id]::before) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0.2rem;
|
||||
top: 0.2rem;
|
||||
bottom: 0.2rem;
|
||||
width: 1px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.workbench-quick-commands :deep([data-command-id]:hover) {
|
||||
background: rgba(139, 92, 246, 0.14);
|
||||
}
|
||||
|
||||
.workbench-quick-commands :deep([data-command-id].bg-primary\/20) {
|
||||
background: linear-gradient(90deg, rgba(139, 92, 246, 0.3), rgba(139, 92, 246, 0.18));
|
||||
}
|
||||
|
||||
.workbench-quick-commands :deep(.font-semibold.flex.items-center) {
|
||||
color: #f8fafc;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1281,6 +1281,7 @@
|
||||
"noSession": "No active session",
|
||||
"fileManagerHint": "Activate an SSH session to browse remote files.",
|
||||
"tabs": {
|
||||
"quickCommands": "Quick Commands",
|
||||
"files": "Files",
|
||||
"history": "History",
|
||||
"editor": "Editor"
|
||||
|
||||
@@ -573,6 +573,7 @@
|
||||
"noSession": "アクティブなセッションはありません",
|
||||
"fileManagerHint": "SSH セッションを有効にするとリモートファイルを参照できます。",
|
||||
"tabs": {
|
||||
"quickCommands": "クイックコマンド",
|
||||
"files": "ファイル",
|
||||
"history": "履歴",
|
||||
"editor": "エディター"
|
||||
|
||||
@@ -1285,6 +1285,7 @@
|
||||
"noSession": "未激活会话",
|
||||
"fileManagerHint": "激活一个 SSH 会话后即可浏览远程文件。",
|
||||
"tabs": {
|
||||
"quickCommands": "快捷指令",
|
||||
"files": "文件",
|
||||
"history": "历史命令",
|
||||
"editor": "编辑器"
|
||||
|
||||
@@ -56,6 +56,29 @@ function isLegacyDefaultLayout(node: LayoutNode | null): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isLegacyWorkbenchLayout(node: LayoutNode | null): boolean {
|
||||
if (!node || node.type !== 'container' || node.direction !== 'horizontal' || !node.children || node.children.length !== 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [leftColumn, centerColumn, rightColumn] = node.children;
|
||||
|
||||
return Boolean(
|
||||
isPaneNode(leftColumn, 'workbench') &&
|
||||
leftColumn?.size === 23 &&
|
||||
centerColumn?.type === 'container' &&
|
||||
centerColumn.direction === 'vertical' &&
|
||||
centerColumn.size === 57 &&
|
||||
centerColumn.children?.length === 2 &&
|
||||
isPaneNode(centerColumn.children[0], 'terminal') &&
|
||||
centerColumn.children[0]?.size === 94 &&
|
||||
isPaneNode(centerColumn.children[1], 'commandBar') &&
|
||||
centerColumn.children[1]?.size === 6 &&
|
||||
isPaneNode(rightColumn, 'statusMonitor') &&
|
||||
rightColumn?.size === 20
|
||||
);
|
||||
}
|
||||
|
||||
// 定义默认布局结构
|
||||
const getDefaultLayout = (): LayoutNode => ({
|
||||
id: generateId(), // Generate new ID
|
||||
@@ -66,13 +89,13 @@ const getDefaultLayout = (): LayoutNode => ({
|
||||
id: generateId(), // Generate new ID
|
||||
type: "pane",
|
||||
component: "workbench",
|
||||
size: 23
|
||||
size: 17
|
||||
},
|
||||
{
|
||||
id: generateId(), // Generate new ID
|
||||
type: "container",
|
||||
direction: "vertical",
|
||||
size: 57,
|
||||
size: 66,
|
||||
children: [
|
||||
{
|
||||
id: generateId(), // Generate new ID
|
||||
@@ -92,7 +115,7 @@ const getDefaultLayout = (): LayoutNode => ({
|
||||
id: generateId(), // Generate new ID
|
||||
type: "pane",
|
||||
component: "statusMonitor",
|
||||
size: 20
|
||||
size: 17
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -202,8 +225,8 @@ function normalizeLoadedLayout(node: LayoutNode | null): LayoutNode | null {
|
||||
const layoutWithIds = ensureNodeIds(node);
|
||||
if (!layoutWithIds) return null;
|
||||
|
||||
if (isLegacyDefaultLayout(layoutWithIds)) {
|
||||
console.log('[Layout Store] Detected legacy workspace default layout, migrating to workbench layout.');
|
||||
if (isLegacyDefaultLayout(layoutWithIds) || isLegacyWorkbenchLayout(layoutWithIds)) {
|
||||
console.log('[Layout Store] Detected legacy workspace default layout, migrating to the latest workbench layout.');
|
||||
return ensureNodeIds(getDefaultLayout());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user