refactor: 重构终端底层渲染逻辑

Refs #28
This commit is contained in:
Baobhan Sith
2025-05-12 20:29:55 +08:00
parent 35717dea47
commit 62717ef7ef
2 changed files with 63 additions and 49 deletions
@@ -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,36 +445,45 @@ 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'">
<keep-alive> <div class="terminal-pane-container relative flex-grow overflow-hidden bg-background"> <!-- Add bg-background -->
<component <template v-for="[sessionId, sessionState] in sessionStore.sessions" :key="sessionId">
v-if="activeSession" <!-- Only render terminals if terminalManager exists (indicates SSH) -->
:is="currentMainComponent" <template v-if="sessionState.terminalManager">
:key="activeSessionId" <keep-alive>
v-bind="componentProps" <component
class="flex-grow overflow-auto" :is="componentMap.terminal"
/> v-show="sessionId === activeSessionId"
</keep-alive> :session-id="sessionId"
<div v-if="!activeSession" class="flex-grow flex justify-center items-center text-center text-text-secondary bg-header text-sm p-4"> :is-active="sessionId === activeSessionId"
<div class="flex flex-col items-center justify-center p-8 w-full h-full"> class="absolute inset-0 w-full h-full"
<i class="fas fa-plug text-4xl mb-3 text-text-secondary"></i> :options="{}"
<span class="text-lg font-medium text-text-secondary mb-2">{{ t('layout.noActiveSession.title') }}</span> />
<div class="text-xs text-text-secondary mt-2">{{ t('layout.noActiveSession.message') }}</div> </keep-alive>
</div> </template>
</div> </template>
</template> <!-- Placeholder if no session is active or no SSH sessions exist -->
<!-- FileManager --> <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 -->
<template v-else-if="layoutNode.component === 'fileManager'"> <div class="flex flex-col items-center justify-center p-8 w-full h-full">
<template v-if="activeSession"> <i class="fas fa-plug text-4xl mb-3 text-text-secondary"></i>
<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">{{ activeSessionId ? t('layout.noSshSessionActive.message', '请激活一个 SSH 会话以使用此终端面板。') : t('layout.noActiveSession.message') }}</div>
</div>
</div>
</div>
</template>
<!-- FileManager -->
<template v-else-if="layoutNode.component === 'fileManager'">
<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>
<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">{{ t('layout.noActiveSession.title') }}</span>
@@ -498,22 +510,10 @@ onMounted(() => {
</template> </template>
<!-- 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) => {