refactor: 引入 workspaceEvents 并迁移核心组件事件处理
This commit is contained in:
@@ -8,19 +8,14 @@ import { useSettingsStore } from '../stores/settings.store';
|
||||
import { useQuickCommandsStore } from '../stores/quickCommands.store';
|
||||
import { useCommandHistoryStore } from '../stores/commandHistory.store';
|
||||
import QuickCommandsModal from './QuickCommandsModal.vue'; // +++ Import the modal component +++
|
||||
import { useWorkspaceEventEmitter } from '../composables/workspaceEvents'; // +++ 新增导入 +++
|
||||
|
||||
// Disable attribute inheritance as this component has multiple root nodes (div + modal)
|
||||
defineOptions({ inheritAttrs: false });
|
||||
|
||||
const emit = defineEmits([
|
||||
'send-command',
|
||||
'search',
|
||||
'find-next',
|
||||
'find-previous',
|
||||
'close-search',
|
||||
'clear-terminal',
|
||||
'toggle-virtual-keyboard' // +++ Add new emit +++
|
||||
]);
|
||||
const emitWorkspaceEvent = useWorkspaceEventEmitter(); // +++ 获取事件发射器 +++
|
||||
const emit = defineEmits(['toggle-virtual-keyboard']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const focusSwitcherStore = useFocusSwitcherStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
@@ -72,7 +67,7 @@ const currentSessionCommandInput = computed({
|
||||
const sendCommand = () => {
|
||||
const command = currentSessionCommandInput.value; // 使用计算属性获取值
|
||||
console.log(`[CommandInputBar] Sending command: ${command || '<Enter>'} `);
|
||||
emit('send-command', command);
|
||||
emitWorkspaceEvent('terminal:sendCommand', { command });
|
||||
// 清空 store 中的值
|
||||
if (activeSessionId.value) {
|
||||
updateSessionCommandInput(activeSessionId.value, '');
|
||||
@@ -83,7 +78,7 @@ const toggleSearch = () => {
|
||||
isSearching.value = !isSearching.value;
|
||||
if (!isSearching.value) {
|
||||
searchTerm.value = ''; // 关闭搜索时清空
|
||||
emit('close-search'); // 通知父组件关闭搜索
|
||||
emitWorkspaceEvent('search:close'); // 通知父组件关闭搜索
|
||||
} else {
|
||||
// 可以在这里聚焦搜索输入框
|
||||
// nextTick(() => searchInputRef.value?.focus());
|
||||
@@ -91,16 +86,16 @@ const toggleSearch = () => {
|
||||
};
|
||||
|
||||
const performSearch = () => {
|
||||
emit('search', searchTerm.value);
|
||||
emitWorkspaceEvent('search:start', { term: searchTerm.value });
|
||||
// 实际的计数更新逻辑应该由父组件通过 props 或事件传递回来
|
||||
};
|
||||
|
||||
const findNext = () => {
|
||||
emit('find-next');
|
||||
emitWorkspaceEvent('search:findNext');
|
||||
};
|
||||
|
||||
const findPrevious = () => {
|
||||
emit('find-previous');
|
||||
emitWorkspaceEvent('search:findPrevious');
|
||||
};
|
||||
|
||||
// 监听搜索词变化,执行搜索
|
||||
@@ -151,7 +146,7 @@ const handleCommandInputKeydown = (event: KeyboardEvent) => {
|
||||
if (selectedCommand !== undefined) {
|
||||
event.preventDefault();
|
||||
console.log(`[CommandInputBar] Enter detected with selection. Sending selected command: ${selectedCommand}`);
|
||||
emit('send-command', selectedCommand); // 发送选中命令 (移除多余的 \n)
|
||||
emitWorkspaceEvent('terminal:sendCommand', { command: selectedCommand }); // 发送选中命令
|
||||
if (activeSessionId.value) {
|
||||
updateSessionCommandInput(activeSessionId.value, ''); // 清空输入框
|
||||
}
|
||||
@@ -190,7 +185,7 @@ const handleCommandInputKeydown = (event: KeyboardEvent) => {
|
||||
// Handle Ctrl+C when input is empty
|
||||
event.preventDefault();
|
||||
console.log('[CommandInputBar] Ctrl+C detected with empty input. Sending SIGINT.');
|
||||
emit('send-command', '\x03'); // Send ETX character (Ctrl+C)
|
||||
emitWorkspaceEvent('terminal:sendCommand', { command: '\x03' }); // Send ETX character (Ctrl+C)
|
||||
} else if (!event.altKey && event.key === 'Enter') {
|
||||
// Handle regular Enter key press - send current input (empty or not)
|
||||
event.preventDefault(); // Prevent default if needed, e.g., form submission
|
||||
@@ -288,7 +283,7 @@ const closeQuickCommandsModal = () => {
|
||||
// +++ Handler for command execution from the modal +++
|
||||
const handleQuickCommandExecute = (command: string) => {
|
||||
console.log(`[CommandInputBar] Executing quick command: ${command}`);
|
||||
emit('send-command', command); // Emit the command to the parent
|
||||
emitWorkspaceEvent('terminal:sendCommand', { command }); // Emit the command to the parent
|
||||
closeQuickCommandsModal(); // Close the modal after selection
|
||||
};
|
||||
</script>
|
||||
@@ -298,7 +293,7 @@ const handleQuickCommandExecute = (command: string) => {
|
||||
<div class="flex-grow flex items-center bg-transparent relative gap-1 px-2"> <!-- Added px-2 here -->
|
||||
<!-- Clear Terminal Button -->
|
||||
<button
|
||||
@click="emit('clear-terminal')"
|
||||
@click="emitWorkspaceEvent('terminal:clear')"
|
||||
class="flex-shrink-0 flex items-center justify-center w-8 h-8 border border-border/50 rounded-lg text-text-secondary transition-colors duration-200 hover:bg-border hover:text-foreground"
|
||||
:title="t('commandInputBar.clearTerminal', '清空终端')"
|
||||
>
|
||||
|
||||
@@ -10,8 +10,10 @@ import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++
|
||||
import { useSessionStore } from '../stores/session.store'; // +++ 导入会话 Store +++
|
||||
import { useSettingsStore } from '../stores/settings.store'; // +++ 导入设置 Store +++
|
||||
import { storeToRefs } from 'pinia'; // +++ 导入 storeToRefs +++
|
||||
import { useWorkspaceEventEmitter } from '../composables/workspaceEvents'; // +++ 新增导入 +++
|
||||
|
||||
const { t } = useI18n();
|
||||
const emitWorkspaceEvent = useWorkspaceEventEmitter(); // +++ 获取事件发射器 +++
|
||||
const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++
|
||||
const sessionStore = useSessionStore(); // +++ 实例化会话 Store +++
|
||||
const settingsStore = useSettingsStore(); // +++ 实例化设置 Store +++
|
||||
@@ -33,18 +35,7 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
// --- Emits ---
|
||||
const emit = defineEmits<{
|
||||
(e: 'activate-tab', tabId: string): void;
|
||||
(e: 'close-tab', tabId: string): void;
|
||||
(e: 'request-save', tabId: string): void; // 发送保存请求,携带 tabId
|
||||
(e: 'update:content', payload: { tabId: string; content: string }): void; // 用于 v-model 同步
|
||||
(e: 'change-encoding', payload: { tabId: string; encoding: string }): void; // +++ 新增:编码更改事件 +++
|
||||
// +++ 新增:传递右键菜单关闭事件 +++
|
||||
(e: 'close-other-tabs', tabId: string): void;
|
||||
(e: 'close-tabs-to-right', tabId: string): void;
|
||||
(e: 'close-tabs-to-left', tabId: string): void;
|
||||
}>();
|
||||
|
||||
|
||||
|
||||
// --- 计算属性,用于模板绑定 ---
|
||||
@@ -112,7 +103,7 @@ watch(localEditorContent, (newContent) => {
|
||||
if (activeTab.value && newContent !== activeTab.value.content) {
|
||||
// console.log(`[EditorContainer] Emitting update:content for tab ${activeTab.value.id}`);
|
||||
// 只有当内容实际改变时才发出事件
|
||||
emit('update:content', { tabId: activeTab.value.id, content: newContent });
|
||||
emitWorkspaceEvent('editor:updateContent', { tabId: activeTab.value.id, content: newContent });
|
||||
// 注意:isModified 状态应该由 Store 根据 content 和 originalContent 计算
|
||||
}
|
||||
});
|
||||
@@ -199,7 +190,7 @@ const encodingOptions = ref([
|
||||
// --- 事件处理 ---
|
||||
const handleSaveRequest = () => {
|
||||
if (activeTab.value) {
|
||||
emit('request-save', activeTab.value.id); // 发出保存请求事件
|
||||
emitWorkspaceEvent('editor:saveTab', { tabId: activeTab.value.id }); // 发出保存请求事件
|
||||
}
|
||||
};
|
||||
|
||||
@@ -209,7 +200,7 @@ const handleEncodingChange = (event: Event) => {
|
||||
const newEncoding = target.value;
|
||||
if (activeTab.value && newEncoding && newEncoding !== currentSelectedEncoding.value) {
|
||||
console.log(`[EditorContainer] Encoding changed to ${newEncoding} for tab ${activeTab.value.id}`);
|
||||
emit('change-encoding', { tabId: activeTab.value.id, encoding: newEncoding });
|
||||
emitWorkspaceEvent('editor:changeEncoding', { tabId: activeTab.value.id, encoding: newEncoding });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -278,7 +269,7 @@ const handleKeyDown = (event: KeyboardEvent) => {
|
||||
|
||||
const nextTabId = props.tabs[nextIndex]?.id;
|
||||
if (nextTabId) {
|
||||
emit('activate-tab', nextTabId);
|
||||
emitWorkspaceEvent('editor:activateTab', { tabId: nextTabId });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -292,11 +283,11 @@ const handleKeyDown = (event: KeyboardEvent) => {
|
||||
<FileEditorTabs
|
||||
:tabs="orderedTabs"
|
||||
:active-tab-id="props.activeTabId"
|
||||
@activate-tab="(tabId: string) => emit('activate-tab', tabId)"
|
||||
@close-tab="(tabId: string) => emit('close-tab', tabId)"
|
||||
@close-other-tabs="(tabId: string) => emit('close-other-tabs', tabId)"
|
||||
@close-tabs-to-right="(tabId: string) => emit('close-tabs-to-right', tabId)"
|
||||
@close-tabs-to-left="(tabId: string) => emit('close-tabs-to-left', tabId)"
|
||||
@activate-tab="(tabId: string) => emitWorkspaceEvent('editor:activateTab', { tabId })"
|
||||
@close-tab="(tabId: string) => emitWorkspaceEvent('editor:closeTab', { tabId })"
|
||||
@close-other-tabs="(tabId: string) => emitWorkspaceEvent('editor:closeOtherTabs', { tabId })"
|
||||
@close-tabs-to-right="(tabId: string) => emitWorkspaceEvent('editor:closeTabsToRight', { tabId })"
|
||||
@close-tabs-to-left="(tabId: string) => emitWorkspaceEvent('editor:closeTabsToLeft', { tabId })"
|
||||
/>
|
||||
|
||||
<!-- 2. 编辑器头部 (显示当前激活标签信息) -->
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useFileEditorStore } from '../stores/fileEditor.store'; // <-- Import F
|
||||
import { useSettingsStore } from '../stores/settings.store'; // +++ Import SettingsStore +++
|
||||
import { useSidebarResize } from '../composables/useSidebarResize'; // +++ Import useSidebarResize +++
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { defineEmits } from 'vue';
|
||||
// import { defineEmits } from 'vue'; // --- 移除 ---
|
||||
|
||||
// --- Props ---
|
||||
const props = defineProps({
|
||||
@@ -44,39 +44,9 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// Removed terminalManager prop definition
|
||||
});
|
||||
|
||||
// --- Emits ---
|
||||
// *** 新增:声明所有需要转发的事件 (使用对象语法) ***
|
||||
const emit = defineEmits({
|
||||
'sendCommand': null, // (command: string) - No validation needed here for now
|
||||
'terminalInput': null, // (payload: { sessionId: string; data: string })
|
||||
'terminalResize': null, // (payload: { sessionId: string; dims: { cols: number; rows: number } })
|
||||
'closeEditorTab': null, // (tabId: string)
|
||||
'activateEditorTab': null, // (tabId: string)
|
||||
'updateEditorContent': null, // (payload: { tabId: string; content: string })
|
||||
'saveEditorTab': null, // (tabId: string)
|
||||
'connect-request': null, // (id: number)
|
||||
'open-new-session': null, // (id: number)
|
||||
'request-add-connection': null, // ()
|
||||
'request-edit-connection': null, // (conn: any)
|
||||
// *** 修正:更新 terminal-ready 事件的 payload 类型 ***
|
||||
'terminal-ready': (payload: { sessionId: string; terminal: any }) => // 使用 any 简化类型检查,或导入 Terminal
|
||||
typeof payload === 'object' && typeof payload.sessionId === 'string' && typeof payload.terminal === 'object',
|
||||
// *** 新增:声明搜索相关事件 ***
|
||||
'search': null, // (searchTerm: string)
|
||||
'find-next': null, // ()
|
||||
'find-previous': null, // ()
|
||||
'close-search': null, // ()
|
||||
'clear-terminal': null, // () +++ 添加 clear-terminal 事件 +++
|
||||
'change-encoding': null, // +++ 添加 change-encoding 事件 +++
|
||||
// +++ 添加文件编辑器标签页关闭事件 +++
|
||||
'close-other-tabs': null, // (tabId: string)
|
||||
'close-tabs-to-right': null, // (tabId: string)
|
||||
'close-tabs-to-left': null, // (tabId: string)
|
||||
// --- 移除 RDP 事件 ---
|
||||
});
|
||||
|
||||
|
||||
// --- Setup ---
|
||||
const layoutStore = useLayoutStore();
|
||||
@@ -160,17 +130,8 @@ const componentProps = computed(() => {
|
||||
return {
|
||||
sessionId: props.activeSessionId ?? '', // 如果 activeSessionId 为 null,则传递空字符串
|
||||
isActive: true,
|
||||
// *** 添加日志并修正事件处理 ***
|
||||
onReady: (payload: { sessionId: string; terminal: any }) => {
|
||||
console.log(`[LayoutRenderer ${props.activeSessionId}] 收到内部 Terminal 的 'ready' 事件:`, payload); // 添加日志
|
||||
emit('terminal-ready', payload); // 直接转发收到的 payload
|
||||
},
|
||||
onData: (data: string) => emit('terminalInput', { sessionId: props.activeSessionId ?? '', data }), // 包装成 payload,确保 sessionId 不为 null
|
||||
onResize: (dims: { cols: number; rows: number }) => emit('terminalResize', { sessionId: props.activeSessionId ?? '', dims }), // 包装成 payload,确保 sessionId 不为 null
|
||||
// --- 移除事件转发 ---
|
||||
};
|
||||
// --- 添加日志:确认 onReady 是否在 props 中 ---
|
||||
console.log(`[LayoutRenderer ${props.activeSessionId}] Terminal componentProps 计算完成,包含 onReady。`);
|
||||
// -----------------------------------------
|
||||
case 'fileManager':
|
||||
// 仅当有活动会话时才返回实际 props,否则返回空对象
|
||||
if (!currentActiveSession) return {};
|
||||
@@ -208,50 +169,26 @@ const componentProps = computed(() => {
|
||||
activeTabId: props.activeEditorTabId, // 从 WorkspaceView 传入
|
||||
sessionId: props.activeSessionId,
|
||||
class: 'pane-content',
|
||||
// 绑定内部处理器以转发事件 (恢复正确的编辑器事件)
|
||||
onCloseTab: (tabId: string) => emit('closeEditorTab', tabId),
|
||||
onActivateTab: (tabId: string) => emit('activateEditorTab', tabId),
|
||||
'onUpdate:content': (payload: { tabId: string; content: string }) => emit('updateEditorContent', payload), // 注意事件名
|
||||
onRequestSave: (tabId: string) => emit('saveEditorTab', tabId),
|
||||
// +++ 添加:转发 change-encoding 事件 +++
|
||||
onChangeEncoding: (payload: { tabId: string; encoding: string }) => emit('change-encoding', payload),
|
||||
// +++ 添加:转发其他关闭事件 +++
|
||||
onCloseOtherTabs: (tabId: string) => emit('close-other-tabs', tabId),
|
||||
onCloseTabsToRight: (tabId: string) => emit('close-tabs-to-right', tabId),
|
||||
onCloseTabsToLeft: (tabId: string) => emit('close-tabs-to-left', tabId),
|
||||
// --- 移除事件转发 ---
|
||||
};
|
||||
case 'commandBar':
|
||||
// CommandInputBar 需要转发 send-command 事件
|
||||
// searchResultCount 和 currentSearchResultIndex 将在模板中直接从 terminalManager 绑定
|
||||
return {
|
||||
class: 'pane-content',
|
||||
onSendCommand: (command: string) => emit('sendCommand', command),
|
||||
// 转发搜索事件
|
||||
onSearch: (term: string) => emit('search', term),
|
||||
onFindNext: () => emit('find-next'),
|
||||
onFindPrevious: () => emit('find-previous'),
|
||||
onCloseSearch: () => emit('close-search'),
|
||||
onClearTerminal: () => emit('clear-terminal'), // --- 移除日志 ---
|
||||
// --- 移除事件转发 ---
|
||||
};
|
||||
case 'connections':
|
||||
// WorkspaceConnectionList 需要转发 connect-request 等事件
|
||||
return {
|
||||
class: 'pane-content',
|
||||
// 绑定内部处理器以转发事件 (除了 request-add-connection)
|
||||
onConnectRequest: (id: number) => emit('connect-request', id),
|
||||
onOpenNewSession: (id: number) => emit('open-new-session', id),
|
||||
// onRequestAddConnection: () => { ... }, // 移除,将在模板中处理
|
||||
onRequestEditConnection: (conn: any) => emit('request-edit-connection', conn),
|
||||
// --- 移除事件转发 ---
|
||||
};
|
||||
case 'commandHistory':
|
||||
case 'quickCommands':
|
||||
// 这两个视图需要转发 execute-command 事件
|
||||
return {
|
||||
class: 'flex flex-col flex-grow h-full overflow-auto', // 移除 pane-content,保留填充类
|
||||
onExecuteCommand: (command: string) => emit('sendCommand', command), // 复用 sendCommand 事件
|
||||
// --- 移除事件转发 ---
|
||||
};
|
||||
case 'dockerManager':
|
||||
// DockerManager 可能不需要 session 信息,但需要转发事件
|
||||
// DockerManager 可能不需要 session 信息
|
||||
return {
|
||||
class: 'flex-grow h-full overflow-hidden', // <-- 修改:添加 flex-grow 和 h-full,并保留 overflow-hidden
|
||||
// 假设 DockerManager 会发出 'docker-command' 事件
|
||||
@@ -277,30 +214,12 @@ const sidebarProps = computed(() => (paneName: PaneName | null, side: 'left' | '
|
||||
tabs: editorTabsFromStore.value, // Access .value for refs from storeToRefs
|
||||
activeTabId: activeEditorTabIdFromStore.value, // Access .value
|
||||
sessionId: props.activeSessionId,
|
||||
// Event forwarding
|
||||
onCloseTab: (tabId: string) => emit('closeEditorTab', tabId),
|
||||
onActivateTab: (tabId: string) => emit('activateEditorTab', tabId),
|
||||
'onUpdate:content': (payload: { tabId: string; content: string }) => emit('updateEditorContent', payload),
|
||||
onRequestSave: (tabId: string) => emit('saveEditorTab', tabId),
|
||||
// --- 移除事件转发 ---
|
||||
};
|
||||
case 'connections':
|
||||
return {
|
||||
...baseProps,
|
||||
// Event forwarding
|
||||
onConnectRequest: (id: number) => emit('connect-request', id),
|
||||
onOpenNewSession: (id: number) => {
|
||||
console.log(`[LayoutRenderer Sidebar] Forwarding 'open-new-session' for ID: ${id}`);
|
||||
emit('open-new-session', id);
|
||||
},
|
||||
onRequestEditConnection: (conn: any) => {
|
||||
console.log(`[LayoutRenderer Sidebar] Forwarding 'request-edit-connection'`);
|
||||
emit('request-edit-connection', conn);
|
||||
},
|
||||
// Forward 'request-add-connection' from sidebar context
|
||||
onRequestAddConnection: () => {
|
||||
console.log(`[LayoutRenderer Sidebar] Forwarding 'request-add-connection'`);
|
||||
emit('request-add-connection');
|
||||
},
|
||||
// --- 移除事件转发 ---
|
||||
};
|
||||
case 'fileManager':
|
||||
// Only provide props if there's an active session
|
||||
@@ -508,27 +427,6 @@ onMounted(() => {
|
||||
:active-session-id="activeSessionId"
|
||||
:editor-tabs="editorTabs"
|
||||
:active-editor-tab-id="activeEditorTabId"
|
||||
@send-command="emit('sendCommand', $event)"
|
||||
@terminal-input="emit('terminalInput', $event)"
|
||||
@terminal-resize="emit('terminalResize', $event)"
|
||||
@terminal-ready="emit('terminal-ready', $event)"
|
||||
@close-editor-tab="emit('closeEditorTab', $event)"
|
||||
@activate-editor-tab="emit('activateEditorTab', $event)"
|
||||
@update-editor-content="emit('updateEditorContent', $event)"
|
||||
@save-editor-tab="emit('saveEditorTab', $event)"
|
||||
@connect-request="emit('connect-request', $event)"
|
||||
@open-new-session="emit('open-new-session', $event)"
|
||||
@request-add-connection="() => emit('request-add-connection')"
|
||||
@request-edit-connection="emit('request-edit-connection', $event)"
|
||||
@search="emit('search', $event)"
|
||||
@find-next="emit('find-next')"
|
||||
@find-previous="emit('find-previous')"
|
||||
@close-search="emit('close-search')"
|
||||
@clear-terminal="() => emit('clear-terminal')"
|
||||
@change-encoding="emit('change-encoding', $event)"
|
||||
@close-other-tabs="emit('close-other-tabs', $event)"
|
||||
@close-tabs-to-right="emit('close-tabs-to-right', $event)"
|
||||
@close-tabs-to-left="emit('close-tabs-to-left', $event)"
|
||||
class="flex-grow overflow-auto"
|
||||
/>
|
||||
</pane>
|
||||
@@ -597,7 +495,6 @@ onMounted(() => {
|
||||
v-if="layoutNode.component === 'connections'"
|
||||
:is="currentMainComponent"
|
||||
v-bind="componentProps"
|
||||
@request-add-connection="() => emit('request-add-connection')"
|
||||
class="flex-grow overflow-auto"
|
||||
/>
|
||||
<component
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
|
||||
import { Terminal, ITerminalAddon, IDisposable } from 'xterm';
|
||||
import { useAppearanceStore } from '../stores/appearance.store';
|
||||
import { useSettingsStore } from '../stores/settings.store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { Terminal, ITerminalAddon, IDisposable } from 'xterm';
|
||||
import { useAppearanceStore } from '../stores/appearance.store';
|
||||
import { useSettingsStore } from '../stores/settings.store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FitAddon } from '@xterm/addon-fit'; // Updated import path
|
||||
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||
import { SearchAddon, type ISearchOptions } from '@xterm/addon-search';
|
||||
import 'xterm/css/xterm.css';
|
||||
import { SearchAddon, type ISearchOptions } from '@xterm/addon-search';
|
||||
import 'xterm/css/xterm.css';
|
||||
import { useWorkspaceEventEmitter } from '../composables/workspaceEvents'; // +++ 新增导入 +++
|
||||
|
||||
|
||||
// 定义 props 和 emits
|
||||
@@ -18,12 +19,9 @@ const props = defineProps<{
|
||||
options?: object; // xterm 的配置选项
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'data', data: string): void; // 用户输入事件
|
||||
(e: 'resize', dimensions: { cols: number; rows: number }): void; // 终端大小调整事件
|
||||
// *** 更新 ready 事件 payload,包含 searchAddon ***
|
||||
(e: 'ready', payload: { sessionId: string; terminal: Terminal; searchAddon: SearchAddon | null }): void;
|
||||
}>();
|
||||
|
||||
|
||||
const emitWorkspaceEvent = useWorkspaceEventEmitter(); // +++ 获取事件发射器 +++
|
||||
|
||||
const terminalRef = ref<HTMLElement | null>(null); // 终端容器的引用
|
||||
let terminal: Terminal | null = null;
|
||||
@@ -70,7 +68,7 @@ const debouncedEmitResize = debounce((term: Terminal) => {
|
||||
if (term && props.isActive) { // 仅当标签仍处于活动状态时才发送防抖后的 resize
|
||||
const dimensions = { cols: term.cols, rows: term.rows };
|
||||
console.log(`[Terminal ${props.sessionId}] Debounced resize emit (from ResizeObserver):`, dimensions);
|
||||
emit('resize', dimensions);
|
||||
emitWorkspaceEvent('terminal:resize', { sessionId: props.sessionId, dims: dimensions });
|
||||
// *** 新增:尝试在发送 resize 后强制刷新终端显示 ***
|
||||
try {
|
||||
term.refresh(0, term.rows - 1); // Refresh entire viewport
|
||||
@@ -92,7 +90,7 @@ const fitAndEmitResizeNow = (term: Terminal) => {
|
||||
fitAddon?.fit();
|
||||
const dimensions = { cols: term.cols, rows: term.rows };
|
||||
console.log(`[Terminal ${props.sessionId}] Immediate resize emit:`, dimensions);
|
||||
emit('resize', dimensions);
|
||||
emitWorkspaceEvent('terminal:resize', { sessionId: props.sessionId, dims: dimensions });
|
||||
|
||||
// *** 恢复:仅使用 nextTick 触发 window resize ***
|
||||
// 使用 nextTick 确保 fit() 的效果已反映,再触发 resize
|
||||
@@ -162,11 +160,11 @@ onMounted(() => {
|
||||
|
||||
// 适应容器大小
|
||||
fitAddon.fit();
|
||||
emit('resize', { cols: terminal.cols, rows: terminal.rows }); // 触发初始 resize 事件
|
||||
emitWorkspaceEvent('terminal:resize', { sessionId: props.sessionId, dims: { cols: terminal.cols, rows: terminal.rows } }); // 触发初始 resize 事件
|
||||
|
||||
// 监听用户输入
|
||||
terminal.onData((data) => {
|
||||
emit('data', data);
|
||||
emitWorkspaceEvent('terminal:input', { sessionId: props.sessionId, data });
|
||||
});
|
||||
|
||||
// 监听终端大小变化 (通过 ResizeObserver) - 主要处理浏览器窗口大小变化等
|
||||
@@ -244,7 +242,7 @@ onMounted(() => {
|
||||
|
||||
// 触发 ready 事件,传递 sessionId, terminal 和 searchAddon 实例
|
||||
if (terminal) {
|
||||
emit('ready', { sessionId: props.sessionId, terminal: terminal, searchAddon: searchAddon });
|
||||
emitWorkspaceEvent('terminal:ready', { sessionId: props.sessionId, terminal: terminal, searchAddon: searchAddon });
|
||||
}
|
||||
|
||||
// --- 监听并处理选中即复制 ---
|
||||
@@ -359,7 +357,7 @@ onMounted(() => {
|
||||
const text = await navigator.clipboard.readText();
|
||||
if (text) {
|
||||
// 将粘贴的文本发送到后端,模拟用户输入
|
||||
emit('data', text);
|
||||
emitWorkspaceEvent('terminal:input', { sessionId: props.sessionId, data: text });
|
||||
console.log('[Terminal] Pasted via Ctrl+Shift+V');
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -380,7 +378,7 @@ onMounted(() => {
|
||||
const text = await navigator.clipboard.readText();
|
||||
if (text && terminal) {
|
||||
// 将粘贴的文本发送到后端
|
||||
emit('data', text);
|
||||
emitWorkspaceEvent('terminal:input', { sessionId: props.sessionId, data: text });
|
||||
console.log('[Terminal] Pasted via Right Click');
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -9,11 +9,13 @@ import TabBarContextMenu from './TabBarContextMenu.vue';
|
||||
import { useSessionStore } from '../stores/session.store';
|
||||
import { useConnectionsStore, type ConnectionInfo } from '../stores/connections.store';
|
||||
import { useLayoutStore, type PaneName } from '../stores/layout.store';
|
||||
import { useWorkspaceEventEmitter } from '../composables/workspaceEvents'; // +++ 新增导入 +++
|
||||
|
||||
import type { SessionTabInfoWithStatus } from '../stores/session.store';
|
||||
|
||||
|
||||
const { t } = useI18n(); // 初始化 i18n
|
||||
const emitWorkspaceEvent = useWorkspaceEventEmitter(); // +++ 获取事件发射器 +++
|
||||
const layoutStore = useLayoutStore(); // 初始化布局 store
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const { isHeaderVisible } = storeToRefs(layoutStore); // 从 layout store 获取主导航栏可见状态
|
||||
@@ -37,30 +39,21 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
// 定义事件 (使用对象语法修复类型)
|
||||
// 定义事件 (保留 update:sessions 用于 v-model)
|
||||
const emit = defineEmits<{
|
||||
(e: 'activate-session', sessionId: string): void;
|
||||
(e: 'close-session', sessionId: string): void;
|
||||
(e: 'open-layout-configurator'): void;
|
||||
(e: 'request-add-connection-from-popup'): void;
|
||||
(e: 'request-edit-connection-from-popup', connection: any): void; // 保持 any 或使用 ConnectionInfo
|
||||
// + 新增右键菜单事件
|
||||
(e: 'close-other-sessions', sessionId: string): void;
|
||||
(e: 'close-sessions-to-right', sessionId: string): void;
|
||||
(e: 'close-sessions-to-left', sessionId: string): void;
|
||||
(e: 'update:sessions', newSessions: SessionTabInfoWithStatus[]): void; // + Add event for reordering
|
||||
(e: 'update:sessions', newSessions: SessionTabInfoWithStatus[]): void;
|
||||
}>();
|
||||
|
||||
|
||||
const activateSession = (sessionId: string) => {
|
||||
if (sessionId !== props.activeSessionId) {
|
||||
emit('activate-session', sessionId);
|
||||
emitWorkspaceEvent('session:activate', { sessionId });
|
||||
}
|
||||
};
|
||||
|
||||
const closeSession = (event: MouseEvent, sessionId: string) => {
|
||||
event.stopPropagation(); // 阻止事件冒泡到标签点击事件
|
||||
emit('close-session', sessionId);
|
||||
emitWorkspaceEvent('session:close', { sessionId });
|
||||
};
|
||||
|
||||
// --- 本地状态 ---
|
||||
@@ -109,15 +102,15 @@ const handlePopupConnect = (connectionId: number) => {
|
||||
const handleRequestAddFromPopup = () => {
|
||||
console.log('[TabBar] Received request-add-connection from popup component.');
|
||||
showConnectionListPopup.value = false; // 关闭弹窗
|
||||
emit('request-add-connection-from-popup'); // 向上发出事件
|
||||
emitWorkspaceEvent('connection:requestAdd'); // 向上发出事件
|
||||
};
|
||||
|
||||
// 新增:处理从弹窗内部发出的编辑连接请求
|
||||
const handleRequestEditFromPopup = (connection: any) => { // 假设 WorkspaceConnectionList 传递了连接对象
|
||||
const handleRequestEditFromPopup = (connection: ConnectionInfo) => { // 假设 WorkspaceConnectionList 传递了连接对象
|
||||
console.log('[TabBar] Received request-edit-connection from popup component for connection:', connection);
|
||||
showConnectionListPopup.value = false; // 关闭弹窗
|
||||
// 向上发出事件,并携带连接信息
|
||||
emit('request-edit-connection-from-popup', connection);
|
||||
emitWorkspaceEvent('connection:requestEdit', { connectionInfo: connection });
|
||||
};
|
||||
|
||||
// --- 移除 handleRequestRdpFromPopup 方法 ---
|
||||
@@ -166,17 +159,17 @@ const handleContextMenuAction = (payload: { action: string; targetId: string | n
|
||||
|
||||
switch (action) {
|
||||
case 'close':
|
||||
emit('close-session', targetId);
|
||||
emitWorkspaceEvent('session:close', { sessionId: targetId });
|
||||
break;
|
||||
case 'close-others':
|
||||
emit('close-other-sessions', targetId);
|
||||
emitWorkspaceEvent('session:closeOthers', { targetSessionId: targetId });
|
||||
break;
|
||||
case 'close-right':
|
||||
emit('close-sessions-to-right', targetId);
|
||||
emitWorkspaceEvent('session:closeToRight', { targetSessionId: targetId });
|
||||
break;
|
||||
case 'close-left':
|
||||
// 注意:关闭左侧通常不包括当前标签本身
|
||||
emit('close-sessions-to-left', targetId);
|
||||
emitWorkspaceEvent('session:closeToLeft', { targetSessionId: targetId });
|
||||
break;
|
||||
default:
|
||||
console.warn(`[TabBar] Unknown context menu action: ${action}`);
|
||||
@@ -214,7 +207,7 @@ const contextMenuItems = computed(() => {
|
||||
// 新增:处理打开布局配置器的事件
|
||||
const openLayoutConfigurator = () => {
|
||||
console.log('[TabBar] Emitting open-layout-configurator event');
|
||||
emit('open-layout-configurator'); // 发出事件
|
||||
emitWorkspaceEvent('ui:openLayoutConfigurator'); // 发出事件
|
||||
};
|
||||
|
||||
// --- Header Visibility Logic ---
|
||||
|
||||
@@ -10,15 +10,11 @@ import { useSessionStore } from '../stores/session.store';
|
||||
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store';
|
||||
import { useUiNotificationsStore } from '../stores/uiNotifications.store'; // +++ 修正导入大小写 +++
|
||||
import { useSettingsStore } from '../stores/settings.store'; // 新增:导入设置 store
|
||||
import { useWorkspaceEventEmitter } from '../composables/workspaceEvents'; // +++ 新增导入 +++
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits([
|
||||
'connect-request', // 左键单击 - 请求激活或替换当前标签
|
||||
// 'open-new-session', // 中键单击 - 请求在新标签中打开 (已移除)
|
||||
'request-add-connection', // 右键菜单 - 添加
|
||||
'request-edit-connection' // 右键菜单 - 编辑
|
||||
|
||||
]);
|
||||
const emitWorkspaceEvent = useWorkspaceEventEmitter(); // +++ 获取事件发射器 +++
|
||||
|
||||
const { t } = useI18n();
|
||||
// const router = useRouter(); // 不再需要
|
||||
@@ -307,7 +303,7 @@ const handleConnect = (connectionId: number, event?: MouseEvent | KeyboardEvent)
|
||||
closeContextMenu(); // 关闭右键菜单
|
||||
|
||||
// 统一发出 connect-request 事件,让 sessionStore.handleConnectRequest 处理模态框和会话
|
||||
emit('connect-request', connectionId);
|
||||
emitWorkspaceEvent('connection:connect', { connectionId });
|
||||
};
|
||||
|
||||
// --- 移除 closeRdpModal 方法 ---
|
||||
@@ -346,11 +342,11 @@ const handleMenuAction = (action: 'add' | 'edit' | 'delete' | 'clone') => { //
|
||||
if (action === 'add') {
|
||||
console.log('[WorkspaceConnectionList] handleMenuAction called with action: add. Emitting request-add-connection...'); // 添加日志
|
||||
// router.push('/connections/add'); // 改为触发事件
|
||||
emit('request-add-connection');
|
||||
emitWorkspaceEvent('connection:requestAdd');
|
||||
} else if (conn) {
|
||||
if (action === 'edit') {
|
||||
// router.push(`/connections/edit/${conn.id}`); // 改为触发事件
|
||||
emit('request-edit-connection', conn); // 传递整个连接对象
|
||||
emitWorkspaceEvent('connection:requestEdit', { connectionInfo: conn }); // 传递整个连接对象
|
||||
} else if (action === 'delete') {
|
||||
if (confirm(t('connections.prompts.confirmDelete', { name: conn.name || conn.host }))) {
|
||||
connectionsStore.deleteConnection(conn.id);
|
||||
|
||||
Reference in New Issue
Block a user