feat(connection): 支持设置默认数据库

统一新建查询的默认数据库选择逻辑,减少重复手动选择。
This commit is contained in:
Bacon2994
2026-05-07 16:49:56 +08:00
parent d4d0bb726e
commit f9d6edb136
10 changed files with 183 additions and 10 deletions
+7 -2
View File
@@ -57,6 +57,7 @@ import {
import type { AiMessage } from "@/lib/api";
import type { ConnectionConfig, QueryTab } from "@/types/database";
import { useDatabaseOptions } from "@/composables/useDatabaseOptions";
import { resolveDefaultDatabase } from "@/lib/defaultDatabase";
const { t } = useI18n();
const settings = useSettingsStore();
@@ -143,12 +144,16 @@ async function changeConnection(connectionId: string) {
connectionStore.activeConnectionId = connectionId;
const tab = props.tab;
if (tab) {
queryStore.updateConnection(tab.id, connectionId, conn.database || "");
queryStore.updateConnection(tab.id, connectionId, resolveDefaultDatabase(conn, []));
} else {
queryStore.createTab(connectionId, conn.database || "");
queryStore.createTab(connectionId, resolveDefaultDatabase(conn, []));
}
try {
await loadDatabaseOptions(connectionId);
const database = resolveDefaultDatabase(conn, allDbOptions.value[connectionId] || []);
if (tab) {
queryStore.updateDatabase(tab.id, database);
}
} catch (e: any) {
toast(t("connection.connectFailed", { message: e?.message || String(e) }), 5000);
}
+16
View File
@@ -6,6 +6,7 @@ import {
Loader2,
Square,
Database,
Check,
Table2,
AlignLeft,
GitBranch,
@@ -21,6 +22,7 @@ import { useConnectionStore } from "@/stores/connectionStore";
import { useDatabaseOptions } from "@/composables/useDatabaseOptions";
import { useSchemaOptions } from "@/composables/useSchemaOptions";
import { connectionIconType } from "@/lib/connectionPresentation";
import { isDefaultDatabase } from "@/lib/defaultDatabase";
import { connectionDisplayName } from "@/lib/tabPresentation";
import type { QueryTab, ConnectionConfig } from "@/types/database";
@@ -40,6 +42,8 @@ const emit = defineEmits<{
changeConnection: [connectionId: string];
changeDatabase: [database: string];
changeSchema: [schema: string | undefined];
setDefaultDatabase: [];
clearDefaultDatabase: [];
}>();
const { t } = useI18n();
@@ -67,6 +71,8 @@ const activeSchemaOptions = computed(() => {
return getSchemaOptionsForDb(connection.id, props.activeTab.database);
});
const isActiveDatabaseDefault = computed(() => isDefaultDatabase(props.activeConnection, activeDatabaseValue.value));
function databaseDisplayName(database: string): string {
const connection = props.activeConnection;
if (connection?.db_type === "redis" && database !== "") return `db${database}`;
@@ -210,6 +216,16 @@ function databaseDisplayName(database: string): string {
</SelectItem>
</SelectContent>
</Select>
<Button
v-if="activeDatabaseValue"
variant="ghost"
size="sm"
class="h-6 px-2 text-[11px]"
@click="isActiveDatabaseDefault ? emit('clearDefaultDatabase') : emit('setDefaultDatabase')"
>
<Check v-if="isActiveDatabaseDefault" class="h-3 w-3" />
{{ isActiveDatabaseDefault ? t("editor.defaultDatabase") : t("editor.setDefaultDatabase") }}
</Button>
</div>
<div v-if="showSchemaSelector" class="flex items-center gap-1">
<Layers class="h-3.5 w-3.5 shrink-0" />
+60 -1
View File
@@ -51,8 +51,10 @@ import {
import { useConnectionStore } from "@/stores/connectionStore";
import { useQueryStore } from "@/stores/queryStore";
import { useToast } from "@/composables/useToast";
import { useDatabaseOptions } from "@/composables/useDatabaseOptions";
import type { DatabaseType, QueryResult, TreeNode, TreeNodeType } from "@/types/database";
import * as api from "@/lib/api";
import { resolveDefaultDatabase } from "@/lib/defaultDatabase";
import {
DATABASE_EXPORT_PAGE_SIZE,
DATABASE_EXPORT_ROW_LIMIT,
@@ -80,12 +82,14 @@ import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
import ConnectionErrorIndicator from "@/components/connection/ConnectionErrorIndicator.vue";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
const { t } = useI18n();
const connectionStore = useConnectionStore();
const queryStore = useQueryStore();
const { toast } = useToast();
const { getDatabaseOptions } = useDatabaseOptions();
const props = defineProps<{
node: TreeNode;
@@ -295,12 +299,39 @@ async function newQuery() {
try {
await connectionStore.ensureConnected(node.connectionId);
connectionStore.activeConnectionId = node.connectionId;
queryStore.createTab(node.connectionId, node.database || "", undefined, "query");
if (node.database) {
queryStore.createTab(node.connectionId, node.database, undefined, "query");
return;
}
const connection = connectionStore.getConfig(node.connectionId);
if (!connection) return;
const options = await getDatabaseOptions(node.connectionId);
queryStore.createTab(node.connectionId, resolveDefaultDatabase(connection, options), undefined, "query");
} catch (e: any) {
toast(t("connection.connectFailed", { message: e?.message || String(e) }), 5000);
}
}
async function setNodeAsDefaultDatabase() {
const node = props.node;
if (!node.connectionId || !node.database) return;
try {
await connectionStore.setDefaultDatabase(node.connectionId, node.database);
} catch (e: any) {
toast(t("connection.saveFailed", { message: e?.message || String(e) }), 5000);
}
}
async function clearNodeDefaultDatabase() {
const node = props.node;
if (!node.connectionId) return;
try {
await connectionStore.clearDefaultDatabase(node.connectionId);
} catch (e: any) {
toast(t("connection.saveFailed", { message: e?.message || String(e) }), 5000);
}
}
async function refresh() {
const node = props.node;
node.isExpanded = false;
@@ -818,6 +849,13 @@ const canOpenFieldLineage = computed(() => {
);
});
const isPinned = computed(() => props.node.pinned || connectionStore.isTreeNodePinned(props.node.id));
const isNodeDefaultDatabase = computed(
() =>
(props.node.type === "database" || props.node.type === "redis-db" || props.node.type === "mongo-db") &&
!!props.node.connectionId &&
!!props.node.database &&
connectionStore.isDefaultDatabase(props.node.connectionId, props.node.database),
);
const hasTypeMenu = computed(() => {
const t = props.node.type;
return (
@@ -1047,6 +1085,9 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
@vue:mounted="($event: any) => $event.el.focus()"
/>
<span v-else class="min-w-0 flex-1 truncate">{{ isGroupLabel(node) ? t(node.label) : node.label }}</span>
<Badge v-if="isNodeDefaultDatabase" variant="secondary" class="h-4 px-1.5 text-[10px]">
{{ t("editor.defaultDatabase") }}
</Badge>
<span v-if="columnComment" class="truncate text-muted-foreground/60 text-[10px] max-w-[40%]">{{
columnComment
}}</span>
@@ -1165,6 +1206,12 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
<ContextMenuItem @click="newQuery">
<TerminalSquare class="w-4 h-4" /> {{ t("contextMenu.newQuery") }}
</ContextMenuItem>
<ContextMenuItem v-if="node.type === 'database' && !isNodeDefaultDatabase" @click="setNodeAsDefaultDatabase">
<Database class="w-4 h-4" /> {{ t("contextMenu.setDefaultDatabase") }}
</ContextMenuItem>
<ContextMenuItem v-if="node.type === 'database' && isNodeDefaultDatabase" @click="clearNodeDefaultDatabase">
<Database class="w-4 h-4" /> {{ t("contextMenu.clearDefaultDatabase") }}
</ContextMenuItem>
<ContextMenuItem v-if="canCreateTable" @click="createTable">
<Plus class="w-4 h-4" /> {{ t("contextMenu.createTable") }}
</ContextMenuItem>
@@ -1194,6 +1241,18 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
</ContextMenuItem>
</template>
<template v-if="node.type === 'redis-db' || node.type === 'mongo-db'">
<ContextMenuItem @click="newQuery">
<TerminalSquare class="w-4 h-4" /> {{ t("contextMenu.newQuery") }}
</ContextMenuItem>
<ContextMenuItem v-if="!isNodeDefaultDatabase" @click="setNodeAsDefaultDatabase">
<Database class="w-4 h-4" /> {{ t("contextMenu.setDefaultDatabase") }}
</ContextMenuItem>
<ContextMenuItem v-if="isNodeDefaultDatabase" @click="clearNodeDefaultDatabase">
<Database class="w-4 h-4" /> {{ t("contextMenu.clearDefaultDatabase") }}
</ContextMenuItem>
</template>
<template v-if="node.type === 'table' || node.type === 'view'">
<ContextMenuItem @click="openData">
<TableProperties class="w-4 h-4" /> {{ t("contextMenu.viewData") }}