From 13e2cdde6f3c37e66a6f461180bbb94d630b78d0 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Thu, 4 Jun 2026 13:44:52 +0800 Subject: [PATCH] feat: add "reuse data tab" setting to avoid opening too many tabs when clicking table names Add a configurable option in Settings > Navigation to reuse an existing data tab instead of creating a new one each time a table is clicked in the sidebar. --- .../editor/EditorSettingsDialog.vue | 19 +++++++++++++++++++ .../src/components/sidebar/TreeItem.vue | 15 ++++++++++++++- .../src/composables/useNavigationTargets.ts | 15 ++++++++++++++- apps/desktop/src/i18n/locales/en.ts | 3 +++ apps/desktop/src/i18n/locales/es.ts | 3 +++ apps/desktop/src/i18n/locales/zh-CN.ts | 2 ++ apps/desktop/src/i18n/locales/zh-TW.ts | 2 ++ apps/desktop/src/stores/settingsStore.ts | 4 ++++ 8 files changed, 61 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 5cb1934ae..bbf10d98e 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -119,6 +119,7 @@ const editAutoSelectActiveSidebarNode = ref(settingsStore.editorSettings.autoSel const editDisconnectTabHandlingMode = ref( settingsStore.editorSettings.disconnectTabHandlingMode, ); +const editReuseDataTab = ref(settingsStore.editorSettings.reuseDataTab); const editSidebarHiddenTablePrefixes = ref(settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n")); const editSidebarHideTableComments = ref(settingsStore.editorSettings.sidebarHideTableComments); const editSidebarAllowHorizontalScroll = ref(settingsStore.editorSettings.sidebarAllowHorizontalScroll); @@ -267,6 +268,7 @@ watch( editSidebarObjectDisplay.value = settingsStore.editorSettings.sidebarObjectDisplay; editAutoSelectActiveSidebarNode.value = settingsStore.editorSettings.autoSelectActiveSidebarNode; editDisconnectTabHandlingMode.value = settingsStore.editorSettings.disconnectTabHandlingMode; + editReuseDataTab.value = settingsStore.editorSettings.reuseDataTab; editSidebarHiddenTablePrefixes.value = settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n"); editSidebarHideTableComments.value = settingsStore.editorSettings.sidebarHideTableComments; editSidebarAllowHorizontalScroll.value = settingsStore.editorSettings.sidebarAllowHorizontalScroll; @@ -309,6 +311,7 @@ function hasChanges(): boolean { editSidebarObjectDisplay.value !== settingsStore.editorSettings.sidebarObjectDisplay || editAutoSelectActiveSidebarNode.value !== settingsStore.editorSettings.autoSelectActiveSidebarNode || editDisconnectTabHandlingMode.value !== settingsStore.editorSettings.disconnectTabHandlingMode || + editReuseDataTab.value !== settingsStore.editorSettings.reuseDataTab || editSidebarHideTableComments.value !== settingsStore.editorSettings.sidebarHideTableComments || editSidebarAllowHorizontalScroll.value !== settingsStore.editorSettings.sidebarAllowHorizontalScroll || editExportBatchSize.value !== settingsStore.editorSettings.exportBatchSize || @@ -338,6 +341,7 @@ async function persistSettings() { sidebarObjectDisplay: editSidebarObjectDisplay.value, autoSelectActiveSidebarNode: editAutoSelectActiveSidebarNode.value, disconnectTabHandlingMode: editDisconnectTabHandlingMode.value, + reuseDataTab: editReuseDataTab.value, sidebarHideTableComments: editSidebarHideTableComments.value, sidebarAllowHorizontalScroll: editSidebarAllowHorizontalScroll.value, sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(editSidebarHiddenTablePrefixes.value), @@ -380,6 +384,7 @@ function resetDefaults() { editSidebarObjectDisplay.value = DEFAULT_EDITOR_SETTINGS.sidebarObjectDisplay; editAutoSelectActiveSidebarNode.value = DEFAULT_EDITOR_SETTINGS.autoSelectActiveSidebarNode; editDisconnectTabHandlingMode.value = DEFAULT_EDITOR_SETTINGS.disconnectTabHandlingMode; + editReuseDataTab.value = DEFAULT_EDITOR_SETTINGS.reuseDataTab; editSidebarHideTableComments.value = DEFAULT_EDITOR_SETTINGS.sidebarHideTableComments; editSidebarAllowHorizontalScroll.value = DEFAULT_EDITOR_SETTINGS.sidebarAllowHorizontalScroll; editSidebarHiddenTablePrefixes.value = DEFAULT_EDITOR_SETTINGS.sidebarHiddenTablePrefixes.join("\n"); @@ -1466,6 +1471,20 @@ watch( +
+
+ + + + + + + {{ t("settings.reuseDataTabDescription") }} + + +
+ +
diff --git a/apps/desktop/src/components/sidebar/TreeItem.vue b/apps/desktop/src/components/sidebar/TreeItem.vue index 08d3b6340..05d7725ca 100644 --- a/apps/desktop/src/components/sidebar/TreeItem.vue +++ b/apps/desktop/src/components/sidebar/TreeItem.vue @@ -723,7 +723,20 @@ async function openData() { table: node.label, dbType: config?.db_type, }); - const tabId = queryStore.createTab(node.connectionId, node.database, node.label, "data", node.schema); + const tabId = (() => { + if (settingsStore.editorSettings.reuseDataTab) { + const existing = queryStore.tabs.find( + (tab) => tab.mode === "data" && tab.connectionId === node.connectionId && tab.database === node.database, + ); + if (existing) { + existing.title = node.label; + existing.schema = node.schema; + queryStore.activeTabId = existing.id; + return existing.id; + } + } + return queryStore.createTab(node.connectionId, node.database, node.label, "data", node.schema); + })(); console.info("[DBX][openData:tab-created]", { traceId, tabId, elapsed: elapsed() }); queryStore.setExecuting(tabId, true); diff --git a/apps/desktop/src/composables/useNavigationTargets.ts b/apps/desktop/src/composables/useNavigationTargets.ts index d61a475e1..234db61ae 100644 --- a/apps/desktop/src/composables/useNavigationTargets.ts +++ b/apps/desktop/src/composables/useNavigationTargets.ts @@ -23,7 +23,20 @@ async function openTableTarget(target: NavigationTarget) { connectionStore.activeConnectionId = target.connectionId; const config = connectionStore.getConfig(target.connectionId); const tabTitle = target.schema ? `${target.schema}.${target.tableName}` : target.tableName; - const tabId = queryStore.createTab(target.connectionId, target.database, tabTitle, "data", target.schema); + const tabId = (() => { + if (settingsStore.editorSettings.reuseDataTab) { + const existing = queryStore.tabs.find( + (tab) => tab.mode === "data" && tab.connectionId === target.connectionId && tab.database === target.database, + ); + if (existing) { + existing.title = tabTitle; + existing.schema = target.schema; + queryStore.activeTabId = existing.id; + return existing.id; + } + } + return queryStore.createTab(target.connectionId, target.database, tabTitle, "data", target.schema); + })(); queryStore.setExecuting(tabId, true); try { diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 00bf06b05..a9d4a9b35 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -1637,6 +1637,9 @@ export default { disconnectTabHandlingModeKeepTabsKeepResults: "Do not close related tabs", disconnectTabHandlingModeKeepTabsKeepResultsDescription: "Keep related tabs, SQL text, and current results without extra cleanup.", + reuseDataTab: "Reuse data tab", + reuseDataTabDescription: + "When clicking a table in the sidebar, reuse the existing data tab instead of creating a new one each time.", sidebarHiddenTablePrefixes: "Hidden table name prefixes", sidebarHiddenTablePrefixesDescription: "One prefix per line. Only sidebar table, view, and collection labels are shortened; tooltips and actions still use the full name.", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 5bc153219..eb0ee9f79 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -1530,6 +1530,9 @@ export default { disconnectTabHandlingModeKeepTabsKeepResults: "No cerrar pestañas relacionadas", disconnectTabHandlingModeKeepTabsKeepResultsDescription: "Conserva las pestañas relacionadas, el texto SQL y los resultados actuales sin limpieza adicional.", + reuseDataTab: "Reutilizar pestaña de datos", + reuseDataTabDescription: + "Al hacer clic en una tabla en la barra lateral, reutiliza la pestaña de datos existente en lugar de crear una nueva cada vez.", sidebarHiddenTablePrefixes: "Prefijos ocultos de tablas", sidebarHiddenTablePrefixesDescription: "Un prefijo por linea. Solo acorta etiquetas de tablas, vistas y colecciones en la barra lateral; las acciones y ayudas usan el nombre completo.", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 951d085f4..fe3b2f533 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -1602,6 +1602,8 @@ export default { "关闭表数据、对象浏览和结构等页签,保留 SQL 查询页签和 SQL 文本,同时清理执行状态与结果会话。", disconnectTabHandlingModeKeepTabsKeepResults: "不关闭相关页签", disconnectTabHandlingModeKeepTabsKeepResultsDescription: "保留相关页签、SQL 文本和当前结果,不做额外处理。", + reuseDataTab: "复用数据标签页", + reuseDataTabDescription: "单击侧边栏表名时,复用已有的数据标签页而不是创建新标签页,避免打开过多标签。", sidebarHiddenTablePrefixes: "隐藏表名前缀", sidebarHiddenTablePrefixesDescription: "每行一个前缀,仅影响侧边栏表、视图和集合的显示名称,悬浮提示和实际操作仍使用完整名称。", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index e227107b1..986b4136f 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -1580,6 +1580,8 @@ export default { "關閉資料表資料、物件瀏覽和結構等分頁,保留 SQL 查詢分頁與 SQL 文字,同時清理執行狀態與結果工作階段。", disconnectTabHandlingModeKeepTabsKeepResults: "不關閉相關分頁", disconnectTabHandlingModeKeepTabsKeepResultsDescription: "保留相關分頁、SQL 文字與目前結果,不另外做清理。", + reuseDataTab: "重複使用資料分頁", + reuseDataTabDescription: "點擊側邊欄資料表名稱時,重複使用現有的資料分頁而不是建立新分頁,避免開啟過多分頁。", sidebarHiddenTablePrefixes: "隱藏資料表名稱字首", sidebarHiddenTablePrefixesDescription: "每行一個字首。只縮短側邊欄中的資料表、檢視和集合標籤;工具提示和實際操作仍使用完整名稱。", diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 7d7db8d54..614a97a00 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -214,6 +214,7 @@ export interface EditorSettings { sidebarObjectDisplay: "grouped" | "simple"; autoSelectActiveSidebarNode: boolean; disconnectTabHandlingMode: DisconnectTabHandlingMode; + reuseDataTab: boolean; sidebarHiddenTablePrefixes: string[]; sidebarHideTableComments: boolean; sidebarAllowHorizontalScroll: boolean; @@ -272,6 +273,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { sidebarObjectDisplay: "grouped", autoSelectActiveSidebarNode: false, disconnectTabHandlingMode: "close-tabs", + reuseDataTab: false, sidebarHiddenTablePrefixes: [], sidebarHideTableComments: false, sidebarAllowHorizontalScroll: false, @@ -420,6 +422,7 @@ export function normalizeEditorSettings(settings: Partial, exist (settings as Partial).disconnectTabHandlingMode, (settings as Partial & { closeQueryTabsOnDisconnect?: boolean }).closeQueryTabsOnDisconnect, ), + reuseDataTab: settings.reuseDataTab ?? DEFAULT_EDITOR_SETTINGS.reuseDataTab, sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(settings.sidebarHiddenTablePrefixes), sidebarHideTableComments: settings.sidebarHideTableComments ?? DEFAULT_EDITOR_SETTINGS.sidebarHideTableComments, sidebarAllowHorizontalScroll: @@ -567,6 +570,7 @@ export const useSettingsStore = defineStore("settings", () => { editorSettings.value.disconnectTabHandlingMode = normalizeDisconnectTabHandlingMode( partial.disconnectTabHandlingMode, ); + if (partial.reuseDataTab !== undefined) editorSettings.value.reuseDataTab = partial.reuseDataTab; if (partial.sidebarHiddenTablePrefixes !== undefined) editorSettings.value.sidebarHiddenTablePrefixes = normalizeSidebarHiddenTablePrefixes( partial.sidebarHiddenTablePrefixes,