@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ConnectionInfo } from '../stores/connections.store'; // +++ 导入 ConnectionInfo 类型 +++
|
import type { ConnectionInfo } from '../stores/connections.store'; // +++ 导入 ConnectionInfo 类型 +++
|
||||||
import { computed, defineAsyncComponent, type PropType, type Component, ref, watch, onMounted } from 'vue'; // +++ Add onMounted +++
|
import { computed, defineAsyncComponent, type PropType, type Component, ref, watch, onMounted } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n'; // <-- Import useI18n
|
import { useI18n } from 'vue-i18n'; // <-- Import useI18n
|
||||||
// 添加依赖 font-awesome
|
// 添加依赖 font-awesome
|
||||||
import '@fortawesome/fontawesome-free/css/all.min.css';
|
import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||||
@@ -55,7 +55,7 @@ const fileEditorStore = useFileEditorStore(); // <-- Initialize FileEditorStore
|
|||||||
const settingsStore = useSettingsStore(); // +++ Initialize SettingsStore +++
|
const settingsStore = useSettingsStore(); // +++ Initialize SettingsStore +++
|
||||||
const { t } = useI18n(); // <-- Get translation function
|
const { t } = useI18n(); // <-- Get translation function
|
||||||
const { activeSession } = storeToRefs(sessionStore);
|
const { activeSession } = storeToRefs(sessionStore);
|
||||||
const { workspaceSidebarPersistentBoolean, getSidebarPaneWidth } = storeToRefs(settingsStore); // +++ Get sidebar setting and width getter +++
|
const { workspaceSidebarPersistentBoolean, getSidebarPaneWidth } = storeToRefs(settingsStore);
|
||||||
const { sidebarPanes } = storeToRefs(layoutStore);
|
const { sidebarPanes } = storeToRefs(layoutStore);
|
||||||
const { orderedTabs: editorTabsFromStore, activeTabId: activeEditorTabIdFromStore } = storeToRefs(fileEditorStore); // <-- Get editor state
|
const { orderedTabs: editorTabsFromStore, activeTabId: activeEditorTabIdFromStore } = storeToRefs(fileEditorStore); // <-- Get editor state
|
||||||
|
|
||||||
@@ -101,6 +101,16 @@ const currentRightSidebarComponent = computed(() => {
|
|||||||
return activeRightSidebarPane.value ? componentMap[activeRightSidebarPane.value] : null;
|
return activeRightSidebarPane.value ? componentMap[activeRightSidebarPane.value] : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasSshSessions = computed(() => {
|
||||||
|
// Check if any session has a terminalManager (indicates SSH)
|
||||||
|
for (const [_, sessionState] of sessionStore.sessions) {
|
||||||
|
if (sessionState.terminalManager) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
// 面板标签 (Similar to LayoutConfigurator)
|
// 面板标签 (Similar to LayoutConfigurator)
|
||||||
const paneLabels = computed(() => ({
|
const paneLabels = computed(() => ({
|
||||||
connections: t('layout.pane.connections', '连接列表'),
|
connections: t('layout.pane.connections', '连接列表'),
|
||||||
@@ -126,14 +136,7 @@ const componentProps = computed(() => {
|
|||||||
|
|
||||||
switch (componentName) {
|
switch (componentName) {
|
||||||
// --- 为需要转发事件的组件添加事件绑定 ---
|
// --- 为需要转发事件的组件添加事件绑定 ---
|
||||||
case 'terminal':
|
// 'terminal' case removed as props are now passed directly in the v-for loop
|
||||||
// Terminal 需要 sessionId, isActive, 并转发 ready, data, resize 事件
|
|
||||||
// 确保 sessionId 始终为字符串
|
|
||||||
return {
|
|
||||||
sessionId: props.activeSessionId ?? '', // 如果 activeSessionId 为 null,则传递空字符串
|
|
||||||
isActive: true,
|
|
||||||
// --- 移除事件转发 ---
|
|
||||||
};
|
|
||||||
case 'fileManager':
|
case 'fileManager':
|
||||||
// 仅当有活动会话时才返回实际 props,否则返回空对象
|
// 仅当有活动会话时才返回实际 props,否则返回空对象
|
||||||
if (!currentActiveSession) return {};
|
if (!currentActiveSession) return {};
|
||||||
@@ -442,35 +445,44 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- Pane Node -->
|
<!-- Pane Node -->
|
||||||
<template v-else-if="layoutNode.type === 'pane'">
|
<template v-else-if="layoutNode.type === 'pane'">
|
||||||
<!-- Terminal -->
|
<!-- Terminal Pane: Render ALL SSH sessions, show only the active one -->
|
||||||
<template v-if="layoutNode.component === 'terminal'">
|
<template v-if="layoutNode.component === 'terminal'">
|
||||||
|
<div class="terminal-pane-container relative flex-grow overflow-hidden bg-background"> <!-- Add bg-background -->
|
||||||
|
<template v-for="[sessionId, sessionState] in sessionStore.sessions" :key="sessionId">
|
||||||
|
<!-- Only render terminals if terminalManager exists (indicates SSH) -->
|
||||||
|
<template v-if="sessionState.terminalManager">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component
|
<component
|
||||||
v-if="activeSession"
|
:is="componentMap.terminal"
|
||||||
:is="currentMainComponent"
|
v-show="sessionId === activeSessionId"
|
||||||
:key="activeSessionId"
|
:session-id="sessionId"
|
||||||
v-bind="componentProps"
|
:is-active="sessionId === activeSessionId"
|
||||||
class="flex-grow overflow-auto"
|
class="absolute inset-0 w-full h-full"
|
||||||
|
:options="{}"
|
||||||
/>
|
/>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
<div v-if="!activeSession" class="flex-grow flex justify-center items-center text-center text-text-secondary bg-header text-sm p-4">
|
</template>
|
||||||
|
</template>
|
||||||
|
<!-- Placeholder if no session is active or no SSH sessions exist -->
|
||||||
|
<div v-if="!activeSessionId || !hasSshSessions" class="absolute inset-0 flex justify-center items-center text-center text-text-secondary bg-header text-sm p-4"> <!-- Use absolute positioning for placeholder too -->
|
||||||
<div class="flex flex-col items-center justify-center p-8 w-full h-full">
|
<div class="flex flex-col items-center justify-center p-8 w-full h-full">
|
||||||
<i class="fas fa-plug text-4xl mb-3 text-text-secondary"></i>
|
<i class="fas fa-plug text-4xl mb-3 text-text-secondary"></i>
|
||||||
<span class="text-lg font-medium text-text-secondary mb-2">{{ t('layout.noActiveSession.title') }}</span>
|
<span class="text-lg font-medium text-text-secondary mb-2">{{ activeSessionId ? t('layout.noSshSessionActive.title', '无活动的 SSH 会话') : t('layout.noActiveSession.title') }}</span>
|
||||||
<div class="text-xs text-text-secondary mt-2">{{ t('layout.noActiveSession.message') }}</div>
|
<div class="text-xs text-text-secondary mt-2">{{ activeSessionId ? t('layout.noSshSessionActive.message', '请激活一个 SSH 会话以使用此终端面板。') : t('layout.noActiveSession.message') }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- FileManager -->
|
<!-- FileManager -->
|
||||||
<template v-else-if="layoutNode.component === 'fileManager'">
|
<template v-else-if="layoutNode.component === 'fileManager'">
|
||||||
<template v-if="activeSession">
|
|
||||||
<component
|
<component
|
||||||
:is="currentMainComponent"
|
:is="currentMainComponent"
|
||||||
:key="layoutNode.id"
|
:key="layoutNode.id"
|
||||||
v-bind="componentProps"
|
v-bind="componentProps"
|
||||||
class="flex-grow overflow-auto">
|
class="flex-grow overflow-auto"
|
||||||
|
v-if="activeSession"
|
||||||
|
>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
|
||||||
<div v-if="!activeSession" class="flex-grow flex justify-center items-center text-center text-text-secondary bg-header text-sm p-4">
|
<div v-if="!activeSession" class="flex-grow flex justify-center items-center text-center text-text-secondary bg-header text-sm p-4">
|
||||||
<div class="flex flex-col items-center justify-center p-8 w-full h-full">
|
<div class="flex flex-col items-center justify-center p-8 w-full h-full">
|
||||||
<i class="fas fa-plug text-4xl mb-3 text-text-secondary"></i>
|
<i class="fas fa-plug text-4xl mb-3 text-text-secondary"></i>
|
||||||
@@ -499,21 +511,9 @@ onMounted(() => {
|
|||||||
<!-- Other Panes -->
|
<!-- Other Panes -->
|
||||||
<template v-else-if="currentMainComponent">
|
<template v-else-if="currentMainComponent">
|
||||||
<component
|
<component
|
||||||
v-if="layoutNode.component === 'connections'"
|
|
||||||
:is="currentMainComponent"
|
|
||||||
v-bind="componentProps"
|
|
||||||
class="flex-grow overflow-auto"
|
|
||||||
/>
|
|
||||||
<component
|
|
||||||
v-else-if="layoutNode.component === 'commandBar'"
|
|
||||||
:is="currentMainComponent"
|
|
||||||
v-bind="componentProps"
|
|
||||||
class="flex-grow overflow-auto"
|
|
||||||
/>
|
|
||||||
<component
|
|
||||||
v-else
|
|
||||||
:is="currentMainComponent"
|
:is="currentMainComponent"
|
||||||
v-bind="componentProps"
|
v-bind="componentProps"
|
||||||
|
:class="['flex-grow overflow-auto', componentProps.class]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<!-- Invalid Pane Component -->
|
<!-- Invalid Pane Component -->
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ onMounted(() => {
|
|||||||
subscribeToWorkspaceEvents('terminal:resize', handleTerminalResize);
|
subscribeToWorkspaceEvents('terminal:resize', handleTerminalResize);
|
||||||
subscribeToWorkspaceEvents('terminal:ready', handleTerminalReady);
|
subscribeToWorkspaceEvents('terminal:ready', handleTerminalReady);
|
||||||
subscribeToWorkspaceEvents('terminal:clear', handleClearTerminal);
|
subscribeToWorkspaceEvents('terminal:clear', handleClearTerminal);
|
||||||
|
subscribeToWorkspaceEvents('terminal:scrollToBottomRequest', handleScrollToBottomRequest);
|
||||||
|
|
||||||
subscribeToWorkspaceEvents('editor:closeTab', (payload) => handleCloseEditorTab(payload.tabId));
|
subscribeToWorkspaceEvents('editor:closeTab', (payload) => handleCloseEditorTab(payload.tabId));
|
||||||
subscribeToWorkspaceEvents('editor:activateTab', (payload) => handleActivateEditorTab(payload.tabId));
|
subscribeToWorkspaceEvents('editor:activateTab', (payload) => handleActivateEditorTab(payload.tabId));
|
||||||
@@ -167,6 +168,7 @@ onBeforeUnmount(() => {
|
|||||||
unsubscribeFromWorkspaceEvents('terminal:resize', handleTerminalResize);
|
unsubscribeFromWorkspaceEvents('terminal:resize', handleTerminalResize);
|
||||||
unsubscribeFromWorkspaceEvents('terminal:ready', handleTerminalReady);
|
unsubscribeFromWorkspaceEvents('terminal:ready', handleTerminalReady);
|
||||||
unsubscribeFromWorkspaceEvents('terminal:clear', handleClearTerminal);
|
unsubscribeFromWorkspaceEvents('terminal:clear', handleClearTerminal);
|
||||||
|
unsubscribeFromWorkspaceEvents('terminal:scrollToBottomRequest', handleScrollToBottomRequest);
|
||||||
|
|
||||||
unsubscribeFromWorkspaceEvents('editor:closeTab', (payload) => handleCloseEditorTab(payload.tabId));
|
unsubscribeFromWorkspaceEvents('editor:closeTab', (payload) => handleCloseEditorTab(payload.tabId));
|
||||||
unsubscribeFromWorkspaceEvents('editor:activateTab', (payload) => handleActivateEditorTab(payload.tabId));
|
unsubscribeFromWorkspaceEvents('editor:activateTab', (payload) => handleActivateEditorTab(payload.tabId));
|
||||||
@@ -449,6 +451,18 @@ const handleClearTerminal = () => { // +++ 修改 +++
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// +++ 处理滚动到底部请求 +++
|
||||||
|
const handleScrollToBottomRequest = (payload: { sessionId: string }) => {
|
||||||
|
const session = sessionStore.sessions.get(payload.sessionId);
|
||||||
|
const terminalManager = session?.terminalManager as (SshTerminalInstance | undefined);
|
||||||
|
if (terminalManager?.terminalInstance?.value) {
|
||||||
|
console.log(`[WorkspaceView] Scrolling to bottom for session ${payload.sessionId}`);
|
||||||
|
terminalManager.terminalInstance.value.scrollToBottom();
|
||||||
|
} else {
|
||||||
|
console.warn(`[WorkspaceView] Cannot scroll to bottom for session ${payload.sessionId}, terminal instance not found.`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Removed computed properties for search results, will pass manager directly
|
// Removed computed properties for search results, will pass manager directly
|
||||||
// --- 编辑器操作处理 (用于 FileEditorContainer) ---
|
// --- 编辑器操作处理 (用于 FileEditorContainer) ---
|
||||||
const handleCloseEditorTab = (tabId: string) => {
|
const handleCloseEditorTab = (tabId: string) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user