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) => {