This commit is contained in:
Baobhan Sith
2025-04-28 18:40:58 +08:00
parent 6f791897cf
commit 6062e99e3e
7 changed files with 122 additions and 45 deletions
+12
View File
@@ -7,6 +7,7 @@ import { useSettingsStore } from './stores/settings.store';
import { useAppearanceStore } from './stores/appearance.store'; import { useAppearanceStore } from './stores/appearance.store';
import { useLayoutStore } from './stores/layout.store'; import { useLayoutStore } from './stores/layout.store';
import { useFocusSwitcherStore } from './stores/focusSwitcher.store'; // +++ 导入焦点切换 Store +++ import { useFocusSwitcherStore } from './stores/focusSwitcher.store'; // +++ 导入焦点切换 Store +++
import { useSessionStore } from './stores/session.store'; // +++ 导入 Session Store +++
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
// 导入通知显示组件 // 导入通知显示组件
import UINotificationDisplay from './components/UINotificationDisplay.vue'; import UINotificationDisplay from './components/UINotificationDisplay.vue';
@@ -16,6 +17,8 @@ import FileEditorOverlay from './components/FileEditorOverlay.vue';
import StyleCustomizer from './components/StyleCustomizer.vue'; import StyleCustomizer from './components/StyleCustomizer.vue';
// +++ 导入焦点切换配置器组件 +++ // +++ 导入焦点切换配置器组件 +++
import FocusSwitcherConfigurator from './components/FocusSwitcherConfigurator.vue'; import FocusSwitcherConfigurator from './components/FocusSwitcherConfigurator.vue';
// +++ 导入 RDP 模态框组件 +++
import RemoteDesktopModal from './components/RemoteDesktopModal.vue';
const { t } = useI18n(); const { t } = useI18n();
const authStore = useAuthStore(); const authStore = useAuthStore();
@@ -23,11 +26,13 @@ const settingsStore = useSettingsStore();
const appearanceStore = useAppearanceStore(); const appearanceStore = useAppearanceStore();
const layoutStore = useLayoutStore(); const layoutStore = useLayoutStore();
const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++ const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++
const sessionStore = useSessionStore(); // +++ 实例化 Session Store +++
const { isAuthenticated } = storeToRefs(authStore); const { isAuthenticated } = storeToRefs(authStore);
const { showPopupFileEditorBoolean } = storeToRefs(settingsStore); const { showPopupFileEditorBoolean } = storeToRefs(settingsStore);
const { isStyleCustomizerVisible } = storeToRefs(appearanceStore); const { isStyleCustomizerVisible } = storeToRefs(appearanceStore);
const { isLayoutVisible, isHeaderVisible } = storeToRefs(layoutStore); // 添加 isHeaderVisible const { isLayoutVisible, isHeaderVisible } = storeToRefs(layoutStore); // 添加 isHeaderVisible
const { isConfiguratorVisible: isFocusSwitcherVisible } = storeToRefs(focusSwitcherStore); const { isConfiguratorVisible: isFocusSwitcherVisible } = storeToRefs(focusSwitcherStore);
const { isRdpModalOpen, rdpConnectionInfo } = storeToRefs(sessionStore); // +++ 获取 RDP 状态 +++
const route = useRoute(); const route = useRoute();
const navRef = ref<HTMLElement | null>(null); const navRef = ref<HTMLElement | null>(null);
@@ -293,6 +298,13 @@ const isElementVisibleAndFocusable = (element: HTMLElement): boolean => {
@close="focusSwitcherStore.toggleConfigurator(false)" @close="focusSwitcherStore.toggleConfigurator(false)"
/> />
<!-- +++ 条件渲染 RDP 模态框 +++ -->
<RemoteDesktopModal
v-if="isRdpModalOpen"
:connection="rdpConnectionInfo"
@close="sessionStore.closeRdpModal()"
/>
</div> </div>
</template> </template>
@@ -163,9 +163,11 @@ const componentProps = computed(() => {
if (!currentActiveSession) return {}; if (!currentActiveSession) return {};
// 传递 instanceId (使用布局节点的 ID), sessionId, dbConnectionId // 传递 instanceId (使用布局节点的 ID), sessionId, dbConnectionId
// 移除 sftpManager 和 wsDeps // 移除 sftpManager 和 wsDeps
// +++ 提供 instanceId 的备用值 +++
const instanceId = props.layoutNode.id || `fm-main-${props.activeSessionId ?? 'unknown'}`;
return { return {
sessionId: props.activeSessionId ?? '', // 确保 sessionId 不为 null sessionId: props.activeSessionId ?? '', // 确保 sessionId 不为 null
instanceId: props.layoutNode.id, // 使用布局节点 ID 作为实例 ID instanceId: instanceId, // 使用计算出的 instanceId (包含备用值)
dbConnectionId: currentActiveSession.connectionId, dbConnectionId: currentActiveSession.connectionId,
// sftpManager: currentActiveSession.sftpManager, // 移除 sftpManager,因为它现在由 FileManager 内部管理 // sftpManager: currentActiveSession.sftpManager, // 移除 sftpManager,因为它现在由 FileManager 内部管理
wsDeps: { // 恢复 wsDeps wsDeps: { // 恢复 wsDeps
@@ -40,7 +40,8 @@ const MIN_MODAL_HEIGHT = 768;
// Dynamically construct WebSocket URL based on environment // Dynamically construct WebSocket URL based on environment
let backendBaseUrl: string; let backendBaseUrl: string;
const LOCAL_BACKEND_URL = 'ws://localhost:18112' // const LOCAL_BACKEND_URL = 'ws://localhost:18112'
const LOCAL_BACKEND_URL = 'ws://localhost:8081'
// Determine WebSocket URL based on hostname // Determine WebSocket URL based on hostname
if (window.location.hostname === 'localhost') { if (window.location.hostname === 'localhost') {
@@ -5,6 +5,7 @@ import { useRoute } from 'vue-router'; // 导入 useRoute
import { storeToRefs } from 'pinia'; // 导入 storeToRefs import { storeToRefs } from 'pinia'; // 导入 storeToRefs
import WorkspaceConnectionListComponent from './WorkspaceConnectionList.vue'; // 导入连接列表组件 import WorkspaceConnectionListComponent from './WorkspaceConnectionList.vue'; // 导入连接列表组件
import { useSessionStore } from '../stores/session.store'; // 导入 session store import { useSessionStore } from '../stores/session.store'; // 导入 session store
import { useConnectionsStore, type ConnectionInfo } from '../stores/connections.store'; // +++ 导入 connections store 和类型 +++
import { useLayoutStore, type PaneName } from '../stores/layout.store'; // 导入布局 store 和类型 import { useLayoutStore, type PaneName } from '../stores/layout.store'; // 导入布局 store 和类型
// 导入会话状态类型 // 导入会话状态类型
import type { SessionTabInfoWithStatus } from '../stores/session.store'; // 导入更新后的类型 import type { SessionTabInfoWithStatus } from '../stores/session.store'; // 导入更新后的类型
@@ -14,6 +15,7 @@ import type { SessionTabInfoWithStatus } from '../stores/session.store'; // 导
// --- Setup --- // --- Setup ---
const { t } = useI18n(); // 初始化 i18n const { t } = useI18n(); // 初始化 i18n
const layoutStore = useLayoutStore(); // 初始化布局 store const layoutStore = useLayoutStore(); // 初始化布局 store
const connectionsStore = useConnectionsStore(); // +++ 获取 connections store 实例 +++
const { isHeaderVisible } = storeToRefs(layoutStore); // 从 layout store 获取主导航栏可见状态 const { isHeaderVisible } = storeToRefs(layoutStore); // 从 layout store 获取主导航栏可见状态
const route = useRoute(); // 获取路由实例 const route = useRoute(); // 获取路由实例
@@ -61,8 +63,13 @@ const togglePopup = () => {
// 处理从弹出列表中选择连接的事件 // 处理从弹出列表中选择连接的事件
const handlePopupConnect = (connectionId: number) => { const handlePopupConnect = (connectionId: number) => {
console.log(`[TabBar] Popup connect request for ID: ${connectionId}`); console.log(`[TabBar] Popup connect request for ID: ${connectionId}`);
// 使用 sessionStore 的方法来处理连接请求(它现在总是新建标签) // +++ 修复:传递 ConnectionInfo 而不是 ID +++
sessionStore.handleConnectRequest(connectionId); const connectionInfo = connectionsStore.connections.find(c => c.id === connectionId);
if (connectionInfo) {
sessionStore.handleConnectRequest(connectionInfo);
} else {
console.error(`[TabBar] handlePopupConnect: 未找到 ID 为 ${connectionId} 的连接信息。`);
}
showConnectionListPopup.value = false; // 关闭弹出窗口 showConnectionListPopup.value = false; // 关闭弹出窗口
}; };
+50 -13
View File
@@ -1,6 +1,7 @@
import { ref, computed, shallowRef, type Ref } from 'vue'; // 导入 shallowRef import { ref, computed, shallowRef, type Ref } from 'vue'; // 导入 shallowRef
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; // +++ 导入 useRouter +++
import { useConnectionsStore, type ConnectionInfo } from './connections.store'; import { useConnectionsStore, type ConnectionInfo } from './connections.store';
// 导入文件编辑器相关的类型 // 导入文件编辑器相关的类型
import type { FileTab, FileInfo } from './fileEditor.store'; // 导入 FileTab 和 FileInfo import type { FileTab, FileInfo } from './fileEditor.store'; // 导入 FileTab 和 FileInfo
@@ -86,12 +87,17 @@ export const useSessionStore = defineStore('session', () => {
// --- 依赖 --- // --- 依赖 ---
const { t } = useI18n(); const { t } = useI18n();
const connectionsStore = useConnectionsStore(); const connectionsStore = useConnectionsStore();
const router = useRouter(); // +++ 获取 router 实例 +++
// --- State --- // --- State ---
// 使用 shallowRef 避免深度响应性问题,保留管理器实例内部的响应性 // 使用 shallowRef 避免深度响应性问题,保留管理器实例内部的响应性
const sessions = shallowRef<Map<string, SessionState>>(new Map()); const sessions = shallowRef<Map<string, SessionState>>(new Map());
const activeSessionId = ref<string | null>(null); const activeSessionId = ref<string | null>(null);
// --- RDP Modal State ---
const isRdpModalOpen = ref(false);
const rdpConnectionInfo = ref<ConnectionInfo | null>(null);
// --- Getters --- // --- Getters ---
const sessionTabs = computed(() => { const sessionTabs = computed(() => {
return Array.from(sessions.value.values()).map(session => ({ return Array.from(sessions.value.values()).map(session => ({
@@ -364,14 +370,22 @@ export const useSessionStore = defineStore('session', () => {
/** /**
* 处理连接列表的左键点击 * 处理连接列表的左键点击
* 优先级 1: 如果点击的是当前活动标签且断开,则重连该标签 * 处理来自 UI 的连接请求(例如点击连接列表或仪表盘)
* 优先级 2: 其他所有情况(非活动标签、活动且已连接标签、新连接),总是打开新标签 * - 如果是 RDP 连接,直接打开 RDP 模态框
* - 如果是非 RDP 连接:
* - 如果点击的是当前活动且断开的会话,则尝试重连。
* - 否则,打开一个新的会话标签页并导航到 Workspace。
*/ */
const handleConnectRequest = (connectionId: number | string) => { const handleConnectRequest = (connection: ConnectionInfo) => {
const connIdStr = String(connectionId); console.log(`[SessionStore] handleConnectRequest called for connection: ${connection.name} (ID: ${connection.id}, Type: ${connection.type})`);
console.log(`[SessionStore] handleConnectRequest called for ID: ${connIdStr}`);
let activeAndDisconnected = false; // 标记是否满足最高优先级条件 if (connection.type === 'RDP') {
// RDP: 直接打开模态框
openRdpModal(connection);
} else {
// 非 RDP (e.g., SSH): 处理会话和导航
const connIdStr = String(connection.id);
let activeAndDisconnected = false;
// 检查是否点击了当前活动且断开的会话 // 检查是否点击了当前活动且断开的会话
if (activeSessionId.value) { if (activeSessionId.value) {
@@ -384,26 +398,30 @@ export const useSessionStore = defineStore('session', () => {
// 满足最高优先级:重连当前活动会话 // 满足最高优先级:重连当前活动会话
console.log(`[SessionStore] 活动会话 ${activeSessionId.value} 已断开或出错,尝试重连...`); console.log(`[SessionStore] 活动会话 ${activeSessionId.value} 已断开或出错,尝试重连...`);
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
// --- 修改:根据环境确定 WebSocket 主机 ---
let wsHost = window.location.hostname; let wsHost = window.location.hostname;
if (window.location.hostname === 'localhost') { if (window.location.hostname === 'localhost') {
wsHost = 'localhost:3001'; // 本地调试时硬编码 wsHost = 'localhost:3001';
console.log('[SessionStore handleConnectRequest] Using hardcoded localhost:3001 for WebSocket reconnect.'); console.log('[SessionStore handleConnectRequest] Using hardcoded localhost:3001 for WebSocket reconnect.');
} else { } else {
console.log(`[SessionStore handleConnectRequest] Using hostname: ${wsHost}`); console.log(`[SessionStore handleConnectRequest] Using hostname: ${wsHost}`);
} }
// --- 结束修改 --- const wsUrl = `${protocol}//${wsHost}/ws/`;
const wsUrl = `${protocol}//${wsHost}/ws/`; // 使用 wsHost 并添加 /ws/ 路径 console.log(`[SessionStore handleConnectRequest] Generated WebSocket URL for reconnect: ${wsUrl}`);
console.log(`[SessionStore handleConnectRequest] Generated WebSocket URL for reconnect: ${wsUrl}`); // 添加日志
currentActiveSession.wsManager.connect(wsUrl); currentActiveSession.wsManager.connect(wsUrl);
// 重连后,确保激活该会话(如果它不是活动会话)并导航
activateSession(activeSessionId.value); // 确保激活
router.push({ name: 'Workspace' }); // +++ 添加跳转 +++
} }
} }
} }
// 如果不满足最高优先级条件,则总是打开新会话 // 如果不满足重连条件,则总是打开新会话并导航
if (!activeAndDisconnected) { if (!activeAndDisconnected) {
console.log(`[SessionStore] 不满足重连条件或点击了其他连接,将打开新会话 for ID: ${connIdStr}`); console.log(`[SessionStore] 不满足重连条件或点击了其他连接,将打开新会话 for ID: ${connIdStr}`);
openNewSession(connIdStr); openNewSession(connIdStr); // openNewSession 会自动激活新会话
// 导航到 Workspace,让 WorkspaceView 处理新激活的会话
router.push({ name: 'Workspace' }); // +++ 添加跳转 +++
}
} }
}; };
@@ -634,10 +652,26 @@ export const useSessionStore = defineStore('session', () => {
} }
}; };
// --- RDP Modal Actions ---
const openRdpModal = (connection: ConnectionInfo) => {
console.log(`[SessionStore] Opening RDP modal for connection: ${connection.name} (ID: ${connection.id})`);
rdpConnectionInfo.value = connection;
isRdpModalOpen.value = true;
};
const closeRdpModal = () => {
console.log('[SessionStore] Closing RDP modal.');
isRdpModalOpen.value = false;
rdpConnectionInfo.value = null; // 清除连接信息
};
return { return {
// State // State
sessions, sessions,
activeSessionId, activeSessionId,
isRdpModalOpen, // 导出 RDP 模态框状态
rdpConnectionInfo, // 导出 RDP 连接信息
// Getters // Getters
sessionTabs, sessionTabs,
sessionTabsWithStatus, // 导出新的 getter sessionTabsWithStatus, // 导出新的 getter
@@ -657,5 +691,8 @@ export const useSessionStore = defineStore('session', () => {
setActiveEditorTabInSession, setActiveEditorTabInSession,
updateFileContentInSession, // 导出更新内容 Action updateFileContentInSession, // 导出更新内容 Action
saveFileInSession, // 导出保存文件 Action saveFileInSession, // 导出保存文件 Action
// --- RDP Modal Actions ---
openRdpModal, // 导出打开 RDP 模态框 Action
closeRdpModal, // 导出关闭 RDP 模态框 Action
}; };
}); });
@@ -84,11 +84,9 @@ onMounted(async () => {
// --- 方法 --- // --- 方法 ---
// 修改函数签名,接受 ConnectionInfo 类型 // 修改函数签名,接受 ConnectionInfo 类型
const connectTo = (connection: ConnectionInfo) => { const connectTo = (connection: ConnectionInfo) => {
console.log(`[Dashboard] connectTo called for ID: ${connection.id}`); console.log(`[Dashboard] connectTo called for connection: ${connection.name} (ID: ${connection.id}, Type: ${connection.type})`);
// 调用 session store 处理连接请求 // 将连接处理逻辑委托给 sessionStore
sessionStore.handleConnectRequest(connection.id); sessionStore.handleConnectRequest(connection);
// 跳转到工作区
router.push({ name: 'Workspace' });
}; };
+23 -3
View File
@@ -3,6 +3,7 @@ import { onMounted, onBeforeUnmount, computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useLayoutStore } from '../stores/layout.store'; // *** layoutStore *** import { useLayoutStore } from '../stores/layout.store'; // *** layoutStore ***
import { useConnectionsStore } from '../stores/connections.store'; // +++ connectionsStore +++
// 使 // 使
import AddConnectionFormComponent from '../components/AddConnectionForm.vue'; import AddConnectionFormComponent from '../components/AddConnectionForm.vue';
import TerminalTabBar from '../components/TerminalTabBar.vue'; import TerminalTabBar from '../components/TerminalTabBar.vue';
@@ -24,6 +25,7 @@ const settingsStore = useSettingsStore();
const fileEditorStore = useFileEditorStore(); const fileEditorStore = useFileEditorStore();
const layoutStore = useLayoutStore(); // *** layoutStore *** const layoutStore = useLayoutStore(); // *** layoutStore ***
const commandHistoryStore = useCommandHistoryStore(); const commandHistoryStore = useCommandHistoryStore();
const connectionsStore = useConnectionsStore(); // +++ connectionsStore +++
const { isHeaderVisible } = storeToRefs(layoutStore); // *** isHeaderVisible *** const { isHeaderVisible } = storeToRefs(layoutStore); // *** isHeaderVisible ***
// --- Store Getters --- // --- Store Getters ---
@@ -162,7 +164,13 @@ onBeforeUnmount(() => {
if (terminalManager.terminalInstance?.value) { if (terminalManager.terminalInstance?.value) {
terminalManager.terminalInstance.value.writeln(`\r\n\x1b[33m${t('workspace.terminal.reconnectingMsg')}\x1b[0m`); terminalManager.terminalInstance.value.writeln(`\r\n\x1b[33m${t('workspace.terminal.reconnectingMsg')}\x1b[0m`);
} }
sessionStore.handleConnectRequest(currentSession.connectionId); // +++ ConnectionInfo ID +++
const connectionInfo = connectionsStore.connections.find(c => c.id === Number(currentSession.connectionId));
if (connectionInfo) {
sessionStore.handleConnectRequest(connectionInfo);
} else {
console.error(`[WorkspaceView] handleSendCommand: 未找到 ID 为 ${currentSession.connectionId} 的连接信息。`);
}
return; return;
} }
@@ -195,7 +203,13 @@ onBeforeUnmount(() => {
} else { } else {
console.warn(`[WorkspaceView] 无法写入重连提示,terminalInstance 不可用。`); console.warn(`[WorkspaceView] 无法写入重连提示,terminalInstance 不可用。`);
} }
sessionStore.handleConnectRequest(session.connectionId); // +++ ConnectionInfo ID +++
const connectionInfo = connectionsStore.connections.find(c => c.id === Number(session.connectionId));
if (connectionInfo) {
sessionStore.handleConnectRequest(connectionInfo);
} else {
console.error(`[WorkspaceView] handleTerminalInput: 未找到 ID 为 ${session.connectionId} 的连接信息。`);
}
} else { } else {
manager.handleTerminalData(data); manager.handleTerminalData(data);
} }
@@ -342,7 +356,13 @@ const handleCloseEditorTab = (tabId: string) => {
// --- ( WorkspaceConnectionList) --- // --- ( WorkspaceConnectionList) ---
const handleConnectRequest = (id: number) => { const handleConnectRequest = (id: number) => {
console.log(`[WorkspaceView] Received 'connect-request' event for ID: ${id}`); console.log(`[WorkspaceView] Received 'connect-request' event for ID: ${id}`);
sessionStore.handleConnectRequest(id); // +++ ConnectionInfo ID +++
const connectionInfo = connectionsStore.connections.find(c => c.id === id);
if (connectionInfo) {
sessionStore.handleConnectRequest(connectionInfo);
} else {
console.error(`[WorkspaceView] handleConnectRequest: 未找到 ID 为 ${id} 的连接信息。`);
}
}; };
const handleOpenNewSession = (id: number) => { const handleOpenNewSession = (id: number) => {
console.log(`[WorkspaceView] Received 'open-new-session' event for ID: ${id}`); console.log(`[WorkspaceView] Received 'open-new-session' event for ID: ${id}`);