From 31f39555ab8dbf9e4717c7903e0d554b1ee9f330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Mon, 12 Jan 2026 06:05:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=98=B6=E6=AE=B5=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/file-editor/EditorPane.vue | 70 ++- .../file-editor/EditorStatusBar.vue | 1 - .../components/file-editor/EditorToolbar.vue | 7 + .../components/file-editor/FileEditorView.vue | 28 +- web/src/components/file-editor/FileTree.vue | 560 +++++++++++++++--- 5 files changed, 575 insertions(+), 91 deletions(-) diff --git a/web/src/components/file-editor/EditorPane.vue b/web/src/components/file-editor/EditorPane.vue index 643857f0..8ab86e3b 100644 --- a/web/src/components/file-editor/EditorPane.vue +++ b/web/src/components/file-editor/EditorPane.vue @@ -24,7 +24,6 @@ const tabsContainerRef = ref() // 标签页滚轮横向滚动 function handleTabsWheel(e: WheelEvent) { if (tabsContainerRef.value) { - e.preventDefault() tabsContainerRef.value.scrollLeft += e.deltaY } } @@ -155,11 +154,18 @@ function handleDragOver(e: DragEvent, index: number) { if (e.dataTransfer) { e.dataTransfer.dropEffect = 'move' } - dragOverIndex.value = index + // 只在值变化时更新,避免高频触发导致闪烁 + if (dragOverIndex.value !== index) { + dragOverIndex.value = index + } } -function handleDragLeave() { - dragOverIndex.value = null +function handleDragLeave(e: DragEvent) { + // 检查是否离开了整个 tabs 容器,而不是在标签页之间移动 + const relatedTarget = e.relatedTarget as HTMLElement | null + if (!relatedTarget || !tabsContainerRef.value?.contains(relatedTarget)) { + dragOverIndex.value = null + } } function handleDrop(e: DragEvent, toIndex: number) { @@ -176,6 +182,28 @@ function handleDragEnd() { dragOverIndex.value = null } +// 尾部放置区域的拖拽处理 +function handleDragOverEnd(e: DragEvent) { + e.preventDefault() + if (e.dataTransfer) { + e.dataTransfer.dropEffect = 'move' + } + const endIndex = editorStore.tabs.length + if (dragOverIndex.value !== endIndex) { + dragOverIndex.value = endIndex + } +} + +function handleDropEnd(e: DragEvent) { + e.preventDefault() + if (dragIndex.value !== null && dragIndex.value !== editorStore.tabs.length - 1) { + // 移动到最后 + editorStore.reorderTabs(dragIndex.value, editorStore.tabs.length - 1) + } + dragIndex.value = null + dragOverIndex.value = null +} + // 右键菜单 const contextMenuOptions = computed(() => [ { @@ -318,7 +346,7 @@ defineExpose({
-
+
@@ -352,6 +380,15 @@ defineExpose({
+ +
@@ -361,11 +398,7 @@ defineExpose({

{{ $gettext('Select a file to edit') }}

-
+
@@ -391,12 +424,14 @@ defineExpose({ flex-direction: column; height: 100%; overflow: hidden; + min-width: 0; /* 允许在 flex 布局中收缩 */ } .tabs-bar { flex-shrink: 0; border-bottom: 1px solid v-bind('themeVars.borderColor'); background: v-bind('themeVars.cardColor'); + overflow: hidden; } .tabs-container { @@ -418,7 +453,9 @@ defineExpose({ cursor: pointer; border-right: 1px solid v-bind('themeVars.borderColor'); white-space: nowrap; - transition: background-color 0.2s, opacity 0.2s; + transition: + background-color 0.2s, + opacity 0.2s; position: relative; user-select: none; @@ -450,6 +487,15 @@ defineExpose({ } } +.tab-drop-end { + width: 20px; + flex-shrink: 0; + + &.drag-over { + border-left: 2px solid v-bind('themeVars.primaryColor'); + } +} + .tab-name { font-size: 13px; max-width: 150px; diff --git a/web/src/components/file-editor/EditorStatusBar.vue b/web/src/components/file-editor/EditorStatusBar.vue index f7a6017d..a8bad625 100644 --- a/web/src/components/file-editor/EditorStatusBar.vue +++ b/web/src/components/file-editor/EditorStatusBar.vue @@ -83,7 +83,6 @@ function handleIndentChange(value: { tabSize: number; insertSpaces: boolean }) {
- {{ $gettext('Path') }}: {{ editorStore.activeTab.path }} diff --git a/web/src/components/file-editor/EditorToolbar.vue b/web/src/components/file-editor/EditorToolbar.vue index a38fc928..f8e2f5fd 100644 --- a/web/src/components/file-editor/EditorToolbar.vue +++ b/web/src/components/file-editor/EditorToolbar.vue @@ -157,6 +157,13 @@ function handleToggleWordWrap() { const current = editorStore.settings.wordWrap editorStore.updateSettings({ wordWrap: current === 'on' ? 'off' : 'on' }) } + +// 暴露方法供外部调用 +defineExpose({ + save: handleSave, + saveAll: handleSaveAll, + refresh: handleRefresh +}) @@ -471,4 +889,8 @@ defineExpose({ height: 100%; overflow: auto; } + +.search-empty { + padding: 40px 20px; +}