This commit is contained in:
Baobhan Sith
2025-04-16 21:19:34 +08:00
parent 0c5e4e4f30
commit b9a9d8b0af
7 changed files with 428 additions and 82 deletions
@@ -0,0 +1,73 @@
<script setup lang="ts">
import { PropType } from 'vue';
import { useLayoutStore, type PaneName } from '../stores/layout.store';
// --- Props ---
const props = defineProps({
title: {
type: String,
required: true,
},
paneName: {
type: String as PropType<PaneName>,
required: true,
},
});
// --- Setup ---
const layoutStore = useLayoutStore();
// --- Methods ---
const closePane = () => {
console.log(`[PaneTitleBar] Requesting to close pane: ${props.paneName}`);
layoutStore.setPaneVisibility(props.paneName, false);
};
</script>
<template>
<div class="pane-title-bar">
<span class="title">{{ title }}</span>
<button class="close-button" @click="closePane" :title="`关闭 ${title}`">
&times; <!-- 使用 HTML 实体 '×' -->
</button>
</div>
</template>
<style scoped>
.pane-title-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 8px; /* 调整内边距使标题栏更紧凑 */
background-color: #e9ecef; /* 标题栏背景色,可以根据主题调整 */
border-bottom: 1px solid #ced4da; /* 底部边框 */
height: 28px; /* 固定标题栏高度 */
box-sizing: border-box;
flex-shrink: 0; /* 防止标题栏被压缩 */
}
.title {
font-size: 0.85em; /* 稍小字体 */
font-weight: 600;
color: #495057; /* 标题颜色 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.close-button {
background: none;
border: none;
color: #6c757d; /* 关闭按钮颜色 */
cursor: pointer;
font-size: 1.2em; /* 稍大图标 */
line-height: 1;
padding: 0 4px; /* 微调内边距 */
border-radius: 3px;
}
.close-button:hover {
background-color: #dc3545; /* 悬停时背景变红 */
color: white; /* 悬停时图标变白 */
}
</style>
@@ -205,11 +205,11 @@ defineExpose({ write });
overflow: hidden; /* 防止滚动条出现 */
}
/* 尝试直接给 screen 添加 padding */
/* 终端屏幕样式 - 移除上下间距 */
.terminal-container :deep(.xterm-screen) {
padding: 10px; /* 为终端内容区域添加内边距 */
box-sizing: border-box; /* 确保 padding 包含在尺寸内 */
/* 覆盖 xterm.css 可能设置的 position: absolute,以便 padding 生效 */
padding: 0; /* 移除内边距,解决上下有间距的问题 */
box-sizing: border-box;
/* 覆盖 xterm.css 可能设置的 position: absolute */
position: relative !important;
width: 100% !important;
height: 100% !important;
@@ -1,10 +1,18 @@
<script setup lang="ts">
import { ref, PropType } from 'vue'; // 导入 ref
import { ref, computed, PropType } from 'vue'; // 导入 ref 和 computed
import { useI18n } from 'vue-i18n'; // 导入 i18n
import { storeToRefs } from 'pinia'; // 导入 storeToRefs
import WorkspaceConnectionListComponent from './WorkspaceConnectionList.vue'; // 导入连接列表组件
import { useSessionStore } from '../stores/session.store'; // 导入 session store
import { useLayoutStore, type PaneName } from '../stores/layout.store'; // 导入布局 store 和类型
// 导入会话状态类型
import type { SessionTabInfoWithStatus } from '../stores/session.store'; // 导入更新后的类型
// --- Setup ---
const { t } = useI18n(); // 初始化 i18n
const layoutStore = useLayoutStore(); // 初始化布局 store
const { paneVisibility } = storeToRefs(layoutStore); // 修正:使用 storeToRefs 获取响应式状态
// 定义 Props
const props = defineProps({
sessions: {
@@ -31,9 +39,10 @@ const closeSession = (event: MouseEvent, sessionId: string) => {
emit('close-session', sessionId);
};
// --- 新增:弹出窗口状态和处理 ---
const sessionStore = useSessionStore();
const showConnectionListPopup = ref(false);
// --- 本地状态 ---
const sessionStore = useSessionStore(); // Session store 保持不变
const showConnectionListPopup = ref(false); // 连接列表弹出状态
const showLayoutMenu = ref(false); // 新增:布局菜单弹出状态
const togglePopup = () => {
showConnectionListPopup.value = !showConnectionListPopup.value;
@@ -46,13 +55,42 @@ const handlePopupConnect = (connectionId: number) => {
sessionStore.handleConnectRequest(connectionId);
showConnectionListPopup.value = false; // 关闭弹出窗口
};
// --- 新增:布局菜单处理 ---
const toggleLayoutMenu = () => {
console.log('Toggling layout menu visibility. Current state:', showLayoutMenu.value); // 添加日志
showLayoutMenu.value = !showLayoutMenu.value;
console.log('New state:', showLayoutMenu.value); // 添加日志
};
// 定义面板名称到显示文本的映射 (恢复 commandBar)
const paneLabels: Record<PaneName, string> = {
connections: t('layout.pane.connections'),
terminal: t('layout.pane.terminal'),
commandBar: t('layout.pane.commandBar'), // 恢复
fileManager: t('layout.pane.fileManager'),
editor: t('layout.pane.editor'),
statusMonitor: t('layout.pane.statusMonitor'),
};
// 获取所有可控制的面板名称
const availablePanes = computed(() => Object.keys(paneVisibility.value) as PaneName[]); // 修正:使用 .value
// 处理菜单项点击
const handleTogglePane = (paneName: PaneName) => {
layoutStore.togglePaneVisibility(paneName);
// 可以选择点击后关闭菜单,或者保持打开
// showLayoutMenu.value = false;
};
</script>
<template>
<div class="terminal-tab-bar">
<ul class="tab-list">
<li
v-for="session in sessions"
<div class="tabs-and-add-button"> <!-- 新容器包裹标签和+按钮 -->
<ul class="tab-list">
<li
v-for="session in sessions"
:key="session.sessionId"
:class="['tab-item', { active: session.sessionId === activeSessionId }]"
@click="activateSession(session.sessionId)"
@@ -64,14 +102,30 @@ const handlePopupConnect = (connectionId: number) => {
<button class="close-tab-button" @click="closeSession($event, session.sessionId)" title="关闭标签页">
&times; <!-- 使用 HTML 实体 '×' -->
</button>
</li>
</ul>
<!-- 新增 "+" 按钮 -->
<button class="add-tab-button" @click="togglePopup" title="新建连接标签页">
<i class="fas fa-plus"></i>
</button>
<!-- 新增连接列表弹出窗口 -->
</li>
</ul>
<!-- "+" 按钮紧随标签列表 -->
<button class="add-tab-button" @click="togglePopup" title="新建连接标签页">
<i class="fas fa-plus"></i>
</button>
</div>
<!-- 布局菜单按钮容器推到最右侧 -->
<div class="layout-menu-container">
<button class="layout-menu-button" @click="toggleLayoutMenu" title="调整布局">
<i class="fas fa-bars"></i> <!-- 使用 Font Awesome bars 图标 -->
</button>
<!-- 布局菜单下拉列表 (保持不变) -->
<div v-if="showLayoutMenu" class="layout-menu-dropdown">
<ul>
<li v-for="pane in availablePanes" :key="pane" @click="handleTogglePane(pane)">
<span class="checkmark">{{ paneVisibility[pane] ? '✓' : '' }}</span>
{{ paneLabels[pane] || pane }}
</li>
</ul>
</div>
</div>
<!-- 移除多余的结束标签 -->
<!-- 连接列表弹出窗口 (保持不变) -->
<div v-if="showConnectionListPopup" class="connection-list-popup" @click.self="togglePopup">
<div class="popup-content">
<button class="popup-close-button" @click="togglePopup">&times;</button>
@@ -91,12 +145,23 @@ const handlePopupConnect = (connectionId: number) => {
display: flex;
background-color: #e0e0e0; /* 标签栏背景色 */
border-bottom: 1px solid #bdbdbd;
overflow-x: auto; /* 如果标签过多则允许水平滚动 */
white-space: nowrap;
/* padding: 0 0.5rem; */ /* 移除左右内边距,让标签列表和按钮自己控制 */
padding-right: 0.5rem; /* 只保留右侧内边距给按钮 */
height: 2.5rem; /* 固定标签栏高度 */
box-sizing: border-box; /* 确保 padding 不会增加总高度 */
/* justify-content: space-between; */ /* 移除,让内容自然靠左 */
overflow: hidden; /* 恢复:防止标签过多时破坏布局 */
}
/* 包裹标签和+按钮的容器 */
.tabs-and-add-button {
display: flex;
align-items: center;
overflow-x: auto; /* 允许标签和+按钮区域滚动 */
/* flex-grow: 1; */ /* 移除:让其自然宽度 */
/* max-width: calc(100% - 50px); */ /* 移除宽度限制 */
flex-shrink: 1; /* 允许在空间不足时收缩 */
min-width: 0; /* 允许收缩到0 */
height: 100%; /* 确保高度与父元素相同,消除上下间距 */
}
/* 状态点样式 */
@@ -117,9 +182,11 @@ const handlePopupConnect = (connectionId: number) => {
.tab-list {
list-style: none;
padding: 0; /* 确保列表无内边距 */
margin: 0; /* 确保列表无外边距 */
padding: 0;
margin: 0;
display: flex;
flex-shrink: 0; /* 防止标签列表被压缩 */
height: 100%; /* 确保占满整个高度 */
}
.tab-item {
@@ -133,7 +200,7 @@ const handlePopupConnect = (connectionId: number) => {
background-color: #f0f0f0; /* 未激活标签背景 */
color: #616161; /* 未激活标签文字颜色 */
transition: background-color 0.2s ease, color 0.2s ease;
max-width: 200px; /* 限制标签最大宽度 */
/* max-width: 200px; */ /* 移除最大宽度限制 */
position: relative; /* 保持相对定位,以防万一需要定位子元素 */
}
@@ -153,10 +220,11 @@ const handlePopupConnect = (connectionId: number) => {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* margin-right: 1.5rem; */ /* 调整右边距,因为关闭按钮现在是 flex item */
/* margin-right: 1.5rem; */ /* 移除 */
line-height: normal; /* 默认行高 */
flex-grow: 1; /* 允许名称伸展 */
flex-grow: 1; /* 保持:允许名称伸展 */
margin-left: 4px; /* 在状态点和名称之间添加一点间距 */
text-align: left; /* 保持文本左对齐 */
}
.close-tab-button {
@@ -169,9 +237,9 @@ const handlePopupConnect = (connectionId: number) => {
padding: 0 0.3rem;
line-height: 1;
border-radius: 50%;
margin-left: auto; /* 推到右侧 */
margin-left: auto; /* 保持 auto 将按钮推到右侧 */
flex-shrink: 0; /* 防止按钮被压缩 */
/* 移除绝对定位 */
/* position: absolute; */
/* top: 50%; */
/* right: 0.5rem; */
/* transform: translateY(-50%); */
@@ -194,9 +262,8 @@ const handlePopupConnect = (connectionId: number) => {
.add-tab-button {
background: none;
border: none;
border-left: 1px solid #bdbdbd; /* 左侧分隔线 */
border-left: 1px solid #bdbdbd; /* 恢复左侧分隔线 */
padding: 0 0.8rem;
/* margin-left: 0.5rem; */ /* 移除左外边距 */
cursor: pointer;
font-size: 1.1em;
color: #616161;
@@ -213,6 +280,8 @@ const handlePopupConnect = (connectionId: number) => {
line-height: 1; /* 确保图标垂直居中 */
}
/* 移除 action-buttons-container 样式 */
/* 弹出窗口样式 */
.connection-list-popup {
position: fixed; /* 固定定位,覆盖整个屏幕 */
@@ -277,4 +346,77 @@ const handlePopupConnect = (connectionId: number) => {
padding: 0; /* 保持移除内边距 */
}
/* 新增:布局菜单样式 */
.layout-menu-container {
position: relative; /* 用于定位下拉菜单 */
display: flex; /* 确保按钮垂直居中 */
align-items: center;
height: 100%;
margin-left: auto; /* 保持:将布局按钮推到最右侧 */
border-left: 1px solid #bdbdbd; /* 确保布局按钮左侧有分隔线 */
flex-shrink: 0; /* 保持:防止被压缩 */
}
.layout-menu-button {
background: none;
border: none;
/* border-left: 1px solid #bdbdbd; */ /* 移除按钮左侧分隔线 */
padding: 0 0.8rem;
cursor: pointer;
font-size: 1.1em;
color: #616161;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
flex-shrink: 0;
}
.layout-menu-button:hover {
background-color: #d0d0d0;
}
.layout-menu-button i {
line-height: 1;
}
.layout-menu-dropdown {
position: absolute; /* 恢复绝对定位 */
top: 100%; /* 定位在按钮下方 */
right: 0; /* 对齐到右侧 */
background-color: lightblue; /* 临时调试背景色 */
min-height: 20px; /* 临时调试最小高度 */
border: 1px solid #ccc;
box-shadow: 0 2px 5px rgba(0,0,0,0.15);
z-index: 9999; /* 保持高 z-index */
min-width: 150px; /* 最小宽度 */
padding: 5px 0;
border-radius: 4px;
}
.layout-menu-dropdown ul {
list-style: none;
padding: 0;
margin: 0;
}
.layout-menu-dropdown li {
padding: 8px 15px;
cursor: pointer;
white-space: nowrap;
display: flex;
align-items: center;
}
.layout-menu-dropdown li:hover {
background-color: #f0f0f0;
}
.layout-menu-dropdown .checkmark {
display: inline-block;
width: 20px; /* 固定宽度以便对齐 */
text-align: center;
margin-right: 5px;
font-weight: bold;
color: #28a745; /* 勾选标记颜色 */
}
</style>
+10
View File
@@ -545,5 +545,15 @@
},
"commandInputBar": {
"placeholder": "Enter command and press Enter to send..."
},
"layout": {
"pane": {
"connections": "Connections",
"terminal": "Terminal",
"commandBar": "Command Bar",
"fileManager": "File Manager",
"editor": "Editor",
"statusMonitor": "Status Monitor"
}
}
}
+10
View File
@@ -548,5 +548,15 @@
},
"commandInputBar": {
"placeholder": "在此输入命令后按 Enter 发送到终端..."
},
"layout": {
"pane": {
"connections": "连接列表",
"terminal": "终端",
"commandBar": "命令栏",
"fileManager": "文件管理器",
"editor": "编辑器",
"statusMonitor": "状态监视器"
}
}
}
@@ -0,0 +1,44 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
// 定义面板名称的类型,方便管理和引用 (恢复 commandBar)
export type PaneName = 'connections' | 'terminal' | 'commandBar' | 'fileManager' | 'editor' | 'statusMonitor';
// 定义 Store
export const useLayoutStore = defineStore('layout', () => {
// 使用 ref 创建响应式状态,存储每个面板的可见性 (恢复 commandBar)
const paneVisibility = ref<Record<PaneName, boolean>>({
connections: true,
terminal: true,
commandBar: true, // 恢复
fileManager: true,
editor: true,
statusMonitor: true,
});
// Action: 切换指定面板的可见性
function togglePaneVisibility(paneName: PaneName) {
if (paneVisibility.value[paneName] !== undefined) {
paneVisibility.value[paneName] = !paneVisibility.value[paneName];
console.log(`[Layout Store] Toggled visibility for ${paneName}: ${paneVisibility.value[paneName]}`);
} else {
console.warn(`[Layout Store] Attempted to toggle visibility for unknown pane: ${paneName}`);
}
}
// Action: 设置指定面板的可见性
function setPaneVisibility(paneName: PaneName, isVisible: boolean) {
if (paneVisibility.value[paneName] !== undefined) {
paneVisibility.value[paneName] = isVisible;
console.log(`[Layout Store] Set visibility for ${paneName} to: ${isVisible}`);
} else {
console.warn(`[Layout Store] Attempted to set visibility for unknown pane: ${paneName}`);
}
}
return {
paneVisibility,
togglePaneVisibility,
setPaneVisibility,
};
});
+118 -51
View File
@@ -10,9 +10,11 @@ import AddConnectionFormComponent from '../components/AddConnectionForm.vue';
import TerminalTabBar from '../components/TerminalTabBar.vue';
import CommandInputBar from '../components/CommandInputBar.vue';
import FileEditorContainer from '../components/FileEditorContainer.vue'; // 导入编辑器容器
import PaneTitleBar from '../components/PaneTitleBar.vue'; // 导入标题栏组件
import { useSessionStore, type SessionTabInfoWithStatus, type SshTerminalInstance } from '../stores/session.store'; // 导入 SshTerminalInstance
import { useSettingsStore } from '../stores/settings.store'; // 导入设置 Store
import { useFileEditorStore } from '../stores/fileEditor.store'; // 导入文件编辑器 Store
import { useLayoutStore } from '../stores/layout.store'; // 导入布局 Store
import type { ConnectionInfo } from '../stores/connections.store';
// 导入 splitpanes 组件
import { Splitpanes, Pane } from 'splitpanes';
@@ -24,11 +26,13 @@ const { t } = useI18n();
const sessionStore = useSessionStore();
const settingsStore = useSettingsStore(); // 初始化设置 Store
const fileEditorStore = useFileEditorStore(); // 初始化文件编辑器 Store (用于共享模式)
const layoutStore = useLayoutStore(); // 初始化布局 Store
// --- 从 Store 获取响应式状态和 Getters ---
const { sessionTabsWithStatus, activeSessionId, activeSession } = storeToRefs(sessionStore);
const { shareFileEditorTabsBoolean } = storeToRefs(settingsStore); // 获取共享设置
const { orderedTabs: globalEditorTabs, activeTabId: globalActiveEditorTabId } = storeToRefs(fileEditorStore); // 获取全局编辑器状态
const { paneVisibility } = storeToRefs(layoutStore); // 获取布局可见性状态
// --- 计算属性 (用于动态绑定编辑器 Props) ---
// **再次修正:** 确保计算属性在共享模式下严格只依赖全局状态
@@ -185,8 +189,10 @@ onBeforeUnmount(() => {
<splitpanes class="default-theme" :horizontal="false" style="height: 100%">
<!-- 1. 左侧边栏 Pane (连接列表) -->
<pane size="15" min-size="10" class="sidebar-pane"> <!-- 调整大小 -->
<pane v-if="paneVisibility.connections" size="15" min-size="10" class="sidebar-pane pane-with-title"> <!-- 使用 v-if 控制, 添加 class -->
<PaneTitleBar :title="t('layout.pane.connections')" pane-name="connections" />
<WorkspaceConnectionListComponent
class="pane-content"
@connect-request="(id) => { console.log(`[WorkspaceView] Received 'connect-request' event for ID: ${id}`); sessionStore.handleConnectRequest(id); }"
@open-new-session="(id) => { console.log(`[WorkspaceView] Received 'open-new-session' event for ID: ${id}`); sessionStore.handleOpenNewSession(id); }"
@request-add-connection="() => { console.log('[WorkspaceView] Received \'request-add-connection\' event'); handleRequestAddConnection(); }"
@@ -194,15 +200,17 @@ onBeforeUnmount(() => {
/>
</pane>
<!-- 2. 中间区域 Pane (终端/命令栏/文件管理器) -->
<pane size="50" min-size="30" class="middle-pane"> <!-- 调整大小 -->
<!-- 2. 中间区域 Pane (终端/命令栏/文件管理器) - 这个 Pane 本身通常保持可见,内部 Pane 才切换 -->
<pane size="50" min-size="30" class="middle-pane">
<!-- 上下分割 (终端 | 命令栏 | 文件管理器) -->
<splitpanes :horizontal="true" style="height: 100%" :dbl-click-splitter="false">
<!-- 上方 Pane (终端) -->
<pane size="55" min-size="20" class="terminal-pane"> <!-- 调整大小 -->
<div
v-for="tabInfo in sessionTabsWithStatus"
:key="tabInfo.sessionId"
<pane v-if="paneVisibility.terminal" size="55" min-size="20" class="terminal-pane pane-with-title"> <!-- 使用 v-if 控制, 添加 class -->
<PaneTitleBar :title="t('layout.pane.terminal')" pane-name="terminal" />
<div class="pane-content terminal-content-wrapper"> <!-- 添加包裹 div -->
<div
v-for="tabInfo in sessionTabsWithStatus"
:key="tabInfo.sessionId"
v-show="tabInfo.sessionId === activeSessionId"
class="terminal-session-wrapper"
>
@@ -214,26 +222,32 @@ onBeforeUnmount(() => {
@data="sessionStore.sessions.get(tabInfo.sessionId)?.terminalManager.handleTerminalData"
@resize="(dims) => { console.log(`[工作区视图 ${tabInfo.sessionId}] 收到 resize 事件:`, dims); sessionStore.sessions.get(tabInfo.sessionId)?.terminalManager.handleTerminalResize(dims); }"
/>
</div>
<div v-if="!activeSessionId" class="terminal-placeholder">
<h2>{{ t('workspace.selectConnectionPrompt') }}</h2>
<p>{{ t('workspace.selectConnectionHint') }}</p>
</div>
<div v-if="!activeSessionId" class="terminal-placeholder">
<h2>{{ t('workspace.selectConnectionPrompt') }}</h2>
<p>{{ t('workspace.selectConnectionHint') }}</p>
</div>
</div>
</pane> <!-- End Terminal Pane -->
<!-- 中间 Pane (命令栏) -->
<pane size="5" min-size="5" class="command-bar-pane">
<!-- 中间 Pane (命令栏) - 移除标题栏,但保留 v-if -->
<pane v-if="paneVisibility.commandBar" size="5" min-size="5" class="command-bar-pane">
<CommandInputBar
v-if="activeSessionId"
@send-command="handleSendCommand"
/>
</pane> <!-- End Command Bar Pane -->
<!-- 下方 Pane (文件管理器) -->
<pane size="40" min-size="15" class="file-manager-pane"> <!-- 调整大小 -->
<div
v-for="tabInfo in sessionTabsWithStatus"
:key="tabInfo.sessionId + '-fm-wrapper'"
<!-- 下方 Pane (文件管理器区域 - 包含新的水平分割) -->
<pane v-if="paneVisibility.fileManager" size="40" min-size="15" class="file-manager-area-pane pane-with-title"> <!-- 使用 v-if 控制, 添加 class -->
<PaneTitleBar :title="t('layout.pane.fileManager')" pane-name="fileManager" />
<!-- 新增:内部水平分割,允许未来添加列 -->
<splitpanes :horizontal="false" style="height: 100%" :dbl-click-splitter="false" class="pane-content"> <!-- 添加 class -->
<!-- 初始的文件管理器 Pane -->
<pane class="file-manager-pane"> <!-- 这个内部 pane 不需要 title bar -->
<div
v-for="tabInfo in sessionTabsWithStatus"
:key="tabInfo.sessionId + '-fm-wrapper'"
v-show="tabInfo.sessionId === activeSessionId"
class="file-manager-wrapper"
>
@@ -250,15 +264,20 @@ onBeforeUnmount(() => {
isSftpReady: sessionStore.sessions.get(tabInfo.sessionId)!.wsManager.isSftpReady
}"
/>
</div>
<div v-if="!activeSessionId" class="pane-placeholder">{{ t('fileManager.noActiveSession') }}</div>
</pane> <!-- End File Manager Pane -->
</splitpanes> <!-- End Middle Area Splitpanes -->
</div>
<div v-if="!activeSessionId" class="pane-placeholder">{{ t('fileManager.noActiveSession') }}</div>
</pane> <!-- End Inner File Manager Pane -->
<!-- 这里可以将来添加其他 Pane -->
</splitpanes> <!-- End Inner Horizontal Splitpanes -->
</pane> <!-- End File Manager Area Pane -->
</splitpanes> <!-- End Middle Area Vertical Splitpanes -->
</pane> <!-- End Middle Pane -->
<!-- 3. 右侧区域 1 Pane (文件编辑器) -->
<pane size="20" min-size="15" class="file-editor-pane"> <!-- 新增编辑器窗格 -->
<pane v-if="paneVisibility.editor" size="20" min-size="15" class="file-editor-pane pane-with-title"> <!-- 使用 v-if 控制, 添加 class -->
<PaneTitleBar :title="t('layout.pane.editor')" pane-name="editor" />
<FileEditorContainer
class="pane-content"
:tabs="editorTabs"
:active-tab-id="activeEditorTabId"
:session-id="activeSessionId"
@@ -270,10 +289,12 @@ onBeforeUnmount(() => {
</pane>
<!-- 4. 右侧区域 2 Pane (状态监视器) -->
<pane size="15" min-size="10" class="sidebar-pane status-monitor-pane"> <!-- 调整大小 -->
<div
v-for="tabInfo in sessionTabsWithStatus"
:key="tabInfo.sessionId + '-sm-wrapper'"
<pane v-if="paneVisibility.statusMonitor" size="15" min-size="10" class="sidebar-pane status-monitor-pane pane-with-title"> <!-- 使用 v-if 控制, 添加 class -->
<PaneTitleBar :title="t('layout.pane.statusMonitor')" pane-name="statusMonitor" />
<div class="pane-content status-monitor-content-wrapper"> <!-- 添加包裹 div -->
<div
v-for="tabInfo in sessionTabsWithStatus"
:key="tabInfo.sessionId + '-sm-wrapper'"
v-show="tabInfo.sessionId === activeSessionId"
class="status-monitor-wrapper"
>
@@ -284,8 +305,9 @@ onBeforeUnmount(() => {
:server-status="sessionStore.sessions.get(tabInfo.sessionId)!.statusMonitorManager.serverStatus.value"
:status-error="sessionStore.sessions.get(tabInfo.sessionId)!.statusMonitorManager.statusError.value"
/>
</div>
<div v-if="!activeSessionId" class="pane-placeholder">{{ t('statusMonitor.noActiveSession') }}</div>
</div>
<div v-if="!activeSessionId" class="pane-placeholder">{{ t('statusMonitor.noActiveSession') }}</div>
</pane>
</splitpanes>
@@ -320,57 +342,102 @@ onBeforeUnmount(() => {
}
/* 为 Pane 添加一些基本样式 */
.pane-with-title { /* 给包含标题栏的 Pane 添加基础样式 */
display: flex;
flex-direction: column;
overflow: hidden;
}
.pane-content { /* 让内容区域填充剩余空间 */
flex-grow: 1;
overflow: auto; /* 或者 hidden,根据需要 */
display: flex; /* 内部可能还需要 flex 布局 */
flex-direction: column; /* 默认列方向 */
}
.sidebar-pane, /* 用于左右侧边栏 */
.middle-pane, /* 中间包含终端、命令栏、文件管理器的 Pane */
.terminal-pane,
.command-bar-pane,
.file-editor-pane, /* 新增编辑器窗格样式 */
.file-manager-pane,
.status-monitor-pane { /* 添加状态监视器样式 */
display: flex; /* 确保 Pane 内容可以正确布局 */
flex-direction: column;
overflow: hidden; /* Pane 内部内容溢出时隐藏 */
.file-editor-pane, /* 编辑器窗格样式 */
.file-manager-area-pane, /* 文件管理器区域 Pane */
.file-manager-pane, /* 内部文件管理器 Pane */
.status-monitor-pane { /* 状态监视器样式 */
/* display: flex; flex-direction: column; overflow: hidden; 已被 pane-with-title 或 pane-content 处理 */
background-color: #f8f9fa; /* 默认背景色 */
}
.middle-pane {
padding: 0; /* 移除 middle-pane 的内边距 */
}
/* 命令栏 Pane 特定样式 - 恢复基本样式 */
/* 命令栏 Pane 特定样式 - 恢复原样 */
.command-bar-pane {
background-color: #e9ecef; /* 背景色 */
justify-content: center; /* 垂直居中输入框 */
overflow: hidden; /* 内容不应超出 */
display: flex; /* 确保 flex 布局 */
align-items: center; /* 垂直居中 */
}
/* 调整内部 CommandInputBar 样式 */
/* 调整内部 CommandInputBar 样式 - 恢复原样 */
.command-bar-pane > .command-input-bar {
border: none; /* 移除 CommandInputBar 的边框 */
background-color: transparent; /* 移除 CommandInputBar 的背景 */
min-height: auto; /* 移除最小高度 */
padding: 2px 10px; /* 调整内边距 */
border: none;
background-color: transparent;
min-height: auto;
padding: 2px 10px; /* 恢复内边距 */
flex-grow: 1; /* 让输入框填充 */
}
.terminal-pane {
background-color: #1e1e1e; /* 终端背景 */
position: relative; /* 保持相对定位用于占位符 */
background-color: #f8f9fa; /* 外层 pane 背景 */
/* position: relative; 由内部 wrapper 处理 */
}
.terminal-content-wrapper {
background-color: #1e1e1e; /* 终端实际背景 */
position: relative; /* 用于占位符 */
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.file-editor-pane {
background-color: #2d2d2d; /* 与编辑器容器背景一致 */
background-color: #f8f9fa; /* 外层 pane 背景 */
}
.file-manager-pane {
/* 分隔线由 splitpanes 提供 */
/* FileEditorContainer 自身需要 flex-grow: 1 */
.file-editor-pane > .pane-content {
background-color: #2d2d2d; /* 编辑器容器背景 */
}
.file-manager-area-pane {
padding: 0;
background-color: #f8f9fa; /* 外层 pane 背景 */
}
/* 内部的 splitpanes 需要 flex-grow: 1 */
.file-manager-area-pane > .pane-content {
background-color: #f0f0f0; /* 内部区域背景 */
}
.file-manager-pane { /* 内部文件管理器 Pane */
background-color: #ffffff; /* 文件管理器使用浅色背景 */
display: flex; /* 确保内部 flex 布局 */
flex-direction: column;
overflow: hidden;
}
.status-monitor-pane {
/* 状态监视器样式 */
text-align: center;
padding: 1rem;
background-color: #f8f9fa; /* 外层 pane 背景 */
/* text-align: center; 由内部 wrapper 处理 */
/* padding: 1rem; 由内部 wrapper 处理 */
}
.status-monitor-content-wrapper {
text-align: center;
padding: 1rem;
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: auto; /* 允许内容滚动 */
}
/* 终端会话包装器 */
.terminal-session-wrapper {
flex-grow: 1; /* 填充 terminal-pane */
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: hidden;
@@ -387,9 +454,9 @@ onBeforeUnmount(() => {
/* 状态监视器包装器 (内部组件应填充) */
.status-monitor-wrapper {
flex: 1;
display: flex; /* 使内部 StatusMonitorComponent 可以填充 */
display: flex;
flex-direction: column;
overflow: hidden;
overflow: hidden; /* 内部组件自己处理滚动 */
}
/* 终端占位符 */