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.
This commit is contained in:
t8y2 2026-06-04 13:44:52 +08:00
parent 2a59304e02
commit 13e2cdde6f
8 changed files with 61 additions and 2 deletions

View File

@ -119,6 +119,7 @@ const editAutoSelectActiveSidebarNode = ref(settingsStore.editorSettings.autoSel
const editDisconnectTabHandlingMode = ref<DisconnectTabHandlingMode>(
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(
</Button>
</div>
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="flex items-center gap-2">
<Label for="reuse-data-tab">{{ t("settings.reuseDataTab") }}</Label>
<Tooltip>
<TooltipTrigger as-child>
<CircleHelp class="h-3.5 w-3.5 cursor-help text-muted-foreground hover:text-foreground" />
</TooltipTrigger>
<TooltipContent class="max-w-[320px] text-xs leading-relaxed" side="top" align="start">
{{ t("settings.reuseDataTabDescription") }}
</TooltipContent>
</Tooltip>
</div>
<Switch id="reuse-data-tab" v-model="editReuseDataTab" />
</div>
<div class="space-y-2">
<Label>{{ t("settings.sidebarObjectDisplay") }}</Label>
<div class="grid grid-cols-2 gap-2">

View File

@ -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);

View File

@ -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 {

View File

@ -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.",

View File

@ -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.",

View File

@ -1602,6 +1602,8 @@ export default {
"关闭表数据、对象浏览和结构等页签,保留 SQL 查询页签和 SQL 文本,同时清理执行状态与结果会话。",
disconnectTabHandlingModeKeepTabsKeepResults: "不关闭相关页签",
disconnectTabHandlingModeKeepTabsKeepResultsDescription: "保留相关页签、SQL 文本和当前结果,不做额外处理。",
reuseDataTab: "复用数据标签页",
reuseDataTabDescription: "单击侧边栏表名时,复用已有的数据标签页而不是创建新标签页,避免打开过多标签。",
sidebarHiddenTablePrefixes: "隐藏表名前缀",
sidebarHiddenTablePrefixesDescription:
"每行一个前缀,仅影响侧边栏表、视图和集合的显示名称,悬浮提示和实际操作仍使用完整名称。",

View File

@ -1580,6 +1580,8 @@ export default {
"關閉資料表資料、物件瀏覽和結構等分頁,保留 SQL 查詢分頁與 SQL 文字,同時清理執行狀態與結果工作階段。",
disconnectTabHandlingModeKeepTabsKeepResults: "不關閉相關分頁",
disconnectTabHandlingModeKeepTabsKeepResultsDescription: "保留相關分頁、SQL 文字與目前結果,不另外做清理。",
reuseDataTab: "重複使用資料分頁",
reuseDataTabDescription: "點擊側邊欄資料表名稱時,重複使用現有的資料分頁而不是建立新分頁,避免開啟過多分頁。",
sidebarHiddenTablePrefixes: "隱藏資料表名稱字首",
sidebarHiddenTablePrefixesDescription:
"每行一個字首。只縮短側邊欄中的資料表、檢視和集合標籤;工具提示和實際操作仍使用完整名稱。",

View File

@ -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<EditorSettings>, exist
(settings as Partial<EditorSettings>).disconnectTabHandlingMode,
(settings as Partial<EditorSettings> & { 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,