diff --git a/packages/frontend/src/components/FileEditorContainer.vue b/packages/frontend/src/components/FileEditorContainer.vue index a7c608d..ab32d1d 100644 --- a/packages/frontend/src/components/FileEditorContainer.vue +++ b/packages/frontend/src/components/FileEditorContainer.vue @@ -200,6 +200,17 @@ const handleEncodingChange = (event: Event) => { } }; +// +++ 处理编辑器滚动事件 +++ +const handleEditorScroll = ({ scrollTop, scrollLeft }: { scrollTop: number; scrollLeft: number }) => { + if (activeTab.value) { + emitWorkspaceEvent('editor:updateScrollPosition', { + tabId: activeTab.value.id, + scrollTop, + scrollLeft, + }); + } +}; + // 注意:关闭/最小化按钮现在应该在 WorkspaceView 控制 Pane,而不是这里 // const handleCloseContainer = () => { ... }; @@ -338,6 +349,9 @@ const handleKeyDown = (event: KeyboardEvent) => { theme="vs-dark" class="editor-instance" @request-save="handleSaveRequest" + :initialScrollTop="activeTab?.scrollTop ?? 0" + :initialScrollLeft="activeTab?.scrollLeft ?? 0" + @update:scrollPosition="handleEditorScroll" />
{{ t('fileManager.selectFileToEdit') }}
diff --git a/packages/frontend/src/components/FileEditorOverlay.vue b/packages/frontend/src/components/FileEditorOverlay.vue index e1b045c..3bd6f95 100644 --- a/packages/frontend/src/components/FileEditorOverlay.vue +++ b/packages/frontend/src/components/FileEditorOverlay.vue @@ -46,6 +46,7 @@ const { closeTabsToTheRight, // 修正:移除 Global 后缀 closeTabsToTheLeft, // 修正:移除 Global 后缀 changeEncoding: changeGlobalEncoding, // +++ 全局编码更改 action +++ + updateTabScrollPosition, // 全局滚动位置更新 action } = fileEditorStore; // 会话 Store Actions (用于非共享模式) @@ -59,6 +60,7 @@ const { closeTabsToTheRightInSession, closeTabsToTheLeftInSession, changeEncodingInSession, // +++ 会话编码更改 action +++ + updateTabScrollPositionInSession, // 会话滚动位置更新 action } = sessionStore; // --- 移除本地文件状态 --- @@ -390,6 +392,26 @@ const handleEncodingChange = (event: Event) => { } }; +// +++ 处理编辑器滚动事件 +++ +const handleEditorScroll = ({ scrollTop, scrollLeft }: { scrollTop: number; scrollLeft: number }) => { + const currentActiveTab = activeTab.value; + if (!currentActiveTab) return; + + if (shareFileEditorTabsBoolean.value) { + // 全局 Store + updateTabScrollPosition(currentActiveTab.id, scrollTop, scrollLeft); + } else { + // 非共享模式需要 sessionId + const sessionId = popupFileInfo.value?.sessionId; + if (sessionId) { + // 会话 Store + updateTabScrollPositionInSession(sessionId, currentActiveTab.id, scrollTop, scrollLeft); + } else { + console.error("[FileEditorOverlay] 无法更新滚动位置:非共享模式下缺少 sessionId。"); + } + } +}; + // 关闭弹窗 (保持不变) const handleCloseContainer = () => { // 关闭前不再检查本地修改状态,因为没有本地状态了 @@ -551,6 +573,9 @@ onBeforeUnmount(() => { theme="vs-dark" class="editor-instance" @request-save="handleSaveRequest" + :initialScrollTop="activeTab?.scrollTop ?? 0" + :initialScrollLeft="activeTab?.scrollLeft ?? 0" + @update:scrollPosition="handleEditorScroll" />
{{ t('fileManager.selectFileToEdit') }}
diff --git a/packages/frontend/src/components/MonacoEditor.vue b/packages/frontend/src/components/MonacoEditor.vue index 4660f5e..78fc986 100644 --- a/packages/frontend/src/components/MonacoEditor.vue +++ b/packages/frontend/src/components/MonacoEditor.vue @@ -9,25 +9,33 @@ import * as monaco from 'monaco-editor'; const localFontSize = ref(14); // 本地字体大小状态,默认 14 const props = defineProps({ - modelValue: { + modelValue: { type: String, default: '', }, language: { type: String, - default: 'plaintext', + default: 'plaintext', }, theme: { type: String, - default: 'vs-dark', + default: 'vs-dark', }, readOnly: { type: Boolean, default: false, - } + }, + initialScrollTop: { // 新增 prop + type: Number, + default: 0, + }, + initialScrollLeft: { // 新增 prop + type: Number, + default: 0, + }, }); -const emit = defineEmits(['update:modelValue', 'request-save']); +const emit = defineEmits(['update:modelValue', 'request-save', 'update:scrollPosition']); // 新增 emit const editorContainer = ref(null); let editorInstance: monaco.editor.IStandaloneCodeEditor | null = null; @@ -74,7 +82,30 @@ onMounted(() => { emit('request-save'); }, }); + + // 应用初始滚动位置 + if (props.initialScrollTop > 0 || props.initialScrollLeft > 0) { + editorInstance.setScrollPosition({ + scrollTop: props.initialScrollTop, + scrollLeft: props.initialScrollLeft, + }); + } + // 监听滚动事件 + editorInstance.onDidScrollChange((e) => { + if (editorInstance) { + // 只有当滚动是由用户操作或实际视口变化引起时才发出 + // setScrollPosition 也会触发此事件,需要避免循环 + // 一个简单的检查是,如果事件中的滚动值与 props 中的初始值不同,则认为是有效滚动 + // 但更好的方式是父组件在设置初始值后才开始监听此事件,或此组件内部处理 + // 为简单起见,我们直接 emit + emit('update:scrollPosition', { + scrollTop: editorInstance.getScrollTop(), + scrollLeft: editorInstance.getScrollLeft(), + }); + } + }); + editorInstance.onDidChangeModelContent(() => { if (editorInstance) { diff --git a/packages/frontend/src/composables/workspaceEvents.ts b/packages/frontend/src/composables/workspaceEvents.ts index a9085ee..1e9d390 100644 --- a/packages/frontend/src/composables/workspaceEvents.ts +++ b/packages/frontend/src/composables/workspaceEvents.ts @@ -22,7 +22,8 @@ export type WorkspaceEventPayloads = { 'editor:closeOtherTabs': { tabId: string }; 'editor:closeTabsToRight': { tabId: string }; 'editor:closeTabsToLeft': { tabId: string }; - + 'editor:updateScrollPosition': { tabId: string; scrollTop: number; scrollLeft: number }; // 新增编辑器滚动事件 + // Connection Events 'connection:connect': { connectionId: number }; // 来自 WorkspaceConnectionList 或其他地方 'connection:openNewSession': { connectionId: number }; // 来自 WorkspaceConnectionList diff --git a/packages/frontend/src/stores/fileEditor.store.ts b/packages/frontend/src/stores/fileEditor.store.ts index be03293..54351df 100644 --- a/packages/frontend/src/stores/fileEditor.store.ts +++ b/packages/frontend/src/stores/fileEditor.store.ts @@ -31,6 +31,8 @@ export interface FileTab { saveStatus: SaveStatus; saveError: string | null; isModified: boolean; + scrollTop?: number; // 编辑器垂直滚动位置 + scrollLeft?: number; // 编辑器水平滚动位置 } // --- 辅助函数 (移到外部并导出) --- @@ -183,6 +185,8 @@ export const useFileEditorStore = defineStore('fileEditor', () => { saveStatus: 'idle', saveError: null, isModified: false, + scrollTop: 0, // 初始化滚动位置 + scrollLeft: 0, // 初始化滚动位置 }; tabs.value.set(tabId, newTab); // setActiveTab(tabId); // 移除同步激活 @@ -552,6 +556,15 @@ export const useFileEditorStore = defineStore('fileEditor', () => { // } }; + // +++ 更新标签页滚动位置 +++ + const updateTabScrollPosition = (tabId: string, scrollTop: number, scrollLeft: number) => { + const tab = tabs.value.get(tabId); + if (tab) { + tab.scrollTop = scrollTop; + tab.scrollLeft = scrollLeft; + } + }; + // 移除旧的 updateContent,因为它只更新活动标签页 // const updateContent = (newContent: string) => { ... }; @@ -618,5 +631,6 @@ export const useFileEditorStore = defineStore('fileEditor', () => { changeEncoding, // +++ 暴露更改编码的方法 +++ triggerPopup, // 暴露新的触发方法 // setEditorVisibility, // 移除 + updateTabScrollPosition, // +++ 暴露更新滚动位置的方法 +++ }; }); diff --git a/packages/frontend/src/stores/session.store.ts b/packages/frontend/src/stores/session.store.ts index d817ba9..7daa6de 100644 --- a/packages/frontend/src/stores/session.store.ts +++ b/packages/frontend/src/stores/session.store.ts @@ -95,6 +95,8 @@ export const useSessionStore = defineStore('session', () => { editorActions.closeTabsToTheRightInSession(sessionId, targetTabId); const closeTabsToTheLeftInSession = (sessionId: string, targetTabId: string) => editorActions.closeTabsToTheLeftInSession(sessionId, targetTabId); + const updateTabScrollPositionInSession = (sessionId: string, tabId: string, scrollTop: number, scrollLeft: number) => + editorActions.updateTabScrollPositionInSession(sessionId, tabId, scrollTop, scrollLeft); // Command Input Actions const updateSessionCommandInput = (sessionId: string, content: string) => @@ -136,6 +138,7 @@ export const useSessionStore = defineStore('session', () => { closeOtherTabsInSession, closeTabsToTheRightInSession, closeTabsToTheLeftInSession, + updateTabScrollPositionInSession, // +++ 导出新的 action +++ openRdpModal, closeRdpModal, openVncModal, diff --git a/packages/frontend/src/stores/session/actions/editorActions.ts b/packages/frontend/src/stores/session/actions/editorActions.ts index 8c71695..1ec9fbf 100644 --- a/packages/frontend/src/stores/session/actions/editorActions.ts +++ b/packages/frontend/src/stores/session/actions/editorActions.ts @@ -297,6 +297,26 @@ export const closeTabsToTheRightInSession = (sessionId: string, targetTabId: str idsToClose.forEach(id => closeEditorTabInSession(sessionId, id)); }; +export const updateTabScrollPositionInSession = ( + sessionId: string, + tabId: string, + scrollTop: number, + scrollLeft: number +) => { + const session = sessions.value.get(sessionId); + if (!session) { + console.error(`[EditorActions] 尝试在不存在的会话 ${sessionId} 中更新标签页 ${tabId} 的滚动位置`); + return; + } + const tab = session.editorTabs.value.find(t => t.id === tabId); + if (tab) { + tab.scrollTop = scrollTop; + tab.scrollLeft = scrollLeft; + } else { + console.warn(`[EditorActions] 尝试更新会话 ${sessionId} 中不存在的标签页 ${tabId} 的滚动位置`); + } +}; + export const closeTabsToTheLeftInSession = (sessionId: string, targetTabId: string) => { const session = sessions.value.get(sessionId); if (!session) return; diff --git a/packages/frontend/src/views/WorkspaceView.vue b/packages/frontend/src/views/WorkspaceView.vue index 31cf551..219ed18 100644 --- a/packages/frontend/src/views/WorkspaceView.vue +++ b/packages/frontend/src/views/WorkspaceView.vue @@ -144,7 +144,8 @@ onMounted(() => { subscribeToWorkspaceEvents('editor:closeOtherTabs', (payload) => handleCloseOtherEditorTabs(payload.tabId)); subscribeToWorkspaceEvents('editor:closeTabsToRight', (payload) => handleCloseEditorTabsToRight(payload.tabId)); subscribeToWorkspaceEvents('editor:closeTabsToLeft', (payload) => handleCloseEditorTabsToLeft(payload.tabId)); - + subscribeToWorkspaceEvents('editor:updateScrollPosition', handleEditorScrollPositionUpdate); // +++ 订阅滚动位置更新事件 +++ + // 移除对 connection:connect 事件的监听,以避免重复创建会话 // subscribeToWorkspaceEvents('connection:connect', (payload) => handleConnectRequest(payload.connectionId)); subscribeToWorkspaceEvents('connection:openNewSession', (payload) => handleOpenNewSession(payload.connectionId)); @@ -188,7 +189,8 @@ onBeforeUnmount(() => { unsubscribeFromWorkspaceEvents('editor:closeOtherTabs', (payload) => handleCloseOtherEditorTabs(payload.tabId)); unsubscribeFromWorkspaceEvents('editor:closeTabsToRight', (payload) => handleCloseEditorTabsToRight(payload.tabId)); unsubscribeFromWorkspaceEvents('editor:closeTabsToLeft', (payload) => handleCloseEditorTabsToLeft(payload.tabId)); - + unsubscribeFromWorkspaceEvents('editor:updateScrollPosition', handleEditorScrollPositionUpdate); // +++ 取消订阅滚动位置更新事件 +++ + // 移除对 connection:connect 事件的监听,以避免重复创建会话 // unsubscribeFromWorkspaceEvents('connection:connect', (payload) => handleConnectRequest(payload.connectionId)); unsubscribeFromWorkspaceEvents('connection:openNewSession', (payload) => handleOpenNewSession(payload.connectionId)); @@ -551,6 +553,22 @@ const handleCloseEditorTab = (tabId: string) => { } } }; + + // +++ 处理编辑器滚动位置更新事件 (由 FileEditorContainer 发出) +++ + const handleEditorScrollPositionUpdate = (payload: { tabId: string; scrollTop: number; scrollLeft: number }) => { + const { tabId, scrollTop, scrollLeft } = payload; + if (shareFileEditorTabsBoolean.value) { + fileEditorStore.updateTabScrollPosition(tabId, scrollTop, scrollLeft); + } else { + const currentActiveSession = activeSession.value; + if (currentActiveSession) { + // 假设 tabId 在当前活动会话的编辑器标签中是唯一的 + sessionStore.updateTabScrollPositionInSession(currentActiveSession.sessionId, tabId, scrollTop, scrollLeft); + } else { + console.warn('[WorkspaceView] Cannot update editor scroll position: No active session in independent mode for tab:', tabId); + } + } + }; // --- 连接列表操作处理 (用于 WorkspaceConnectionList) --- const handleConnectRequest = (id: number) => {