From 0e6617c8176adb893a7fd61ec33bac1956a4fd4b Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 8 May 2026 19:24:13 +0800 Subject: [PATCH] feat: support create/drop database and schema from sidebar context menu Add context menu actions for creating and dropping databases and schemas directly from the sidebar tree. Supports MySQL, PostgreSQL, SQL Server, ClickHouse, Oracle, and other server-based databases. --- src/components/sidebar/TreeItem.vue | 186 ++++++++++++++++++++++++++++ src/i18n/locales/en.ts | 16 +++ src/i18n/locales/zh-CN.ts | 14 +++ src/lib/databaseCapabilities.ts | 13 ++ 4 files changed, 229 insertions(+) diff --git a/src/components/sidebar/TreeItem.vue b/src/components/sidebar/TreeItem.vue index e2bd2736bc..6606d83483 100644 --- a/src/components/sidebar/TreeItem.vue +++ b/src/components/sidebar/TreeItem.vue @@ -73,6 +73,7 @@ import { FIELD_LINEAGE_SUPPORTED_TYPES, TREE_SCHEMA_TYPES, PG_LIKE_STRUCTURE_TYPES, + CREATE_DATABASE_SUPPORTED_TYPES, isSchemaAware, usesFetchFirst, } from "@/lib/databaseCapabilities"; @@ -378,6 +379,13 @@ const showTruncateTableConfirm = ref(false); const showDuplicateDialog = ref(false); const duplicateTableName = ref(""); +const showCreateDatabaseDialog = ref(false); +const createDatabaseName = ref(""); +const showDropDatabaseConfirm = ref(false); +const showCreateSchemaDialog = ref(false); +const createSchemaName = ref(""); +const showDropSchemaConfirm = ref(false); + const isTableNotView = computed(() => props.node.type === "table"); const supportsTruncate = computed(() => { @@ -395,6 +403,26 @@ const canCreateTable = computed(() => { ); }); +const canCreateDatabase = computed(() => { + const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; + return props.node.type === "connection" && !!config && CREATE_DATABASE_SUPPORTED_TYPES.has(config.db_type); +}); + +const canDropDatabase = computed(() => { + const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; + return props.node.type === "database" && !!config && CREATE_DATABASE_SUPPORTED_TYPES.has(config.db_type); +}); + +const canCreateSchema = computed(() => { + const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; + return props.node.type === "database" && !!config && TREE_SCHEMA_TYPES.has(config.db_type); +}); + +const canDropSchema = computed(() => { + const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; + return props.node.type === "schema" && !!config && TREE_SCHEMA_TYPES.has(config.db_type); +}); + function buildDropTableSql(): string { return `DROP TABLE ${qualifiedTableName(props.node.label, props.node.schema)};`; } @@ -463,6 +491,93 @@ async function confirmTruncateTable() { } } +function buildDropDatabaseSql(): string { + return `DROP DATABASE ${quoteIdent(props.node.label)};`; +} + +function buildDropSchemaSql(): string { + const dbType = currentDatabaseType(); + const name = quoteIdent(props.node.label); + if (dbType === "postgres" || dbType === "gaussdb") return `DROP SCHEMA ${name} CASCADE;`; + return `DROP SCHEMA ${name};`; +} + +function openCreateDatabaseDialog() { + createDatabaseName.value = ""; + showCreateDatabaseDialog.value = true; +} + +async function confirmCreateDatabase() { + const node = props.node; + const name = createDatabaseName.value.trim(); + if (!name || !node.connectionId) return; + showCreateDatabaseDialog.value = false; + try { + await connectionStore.ensureConnected(node.connectionId); + const sql = `CREATE DATABASE ${quoteIdent(name)};`; + await api.executeQuery(node.connectionId, "", sql); + toast(t("contextMenu.createDatabaseSuccess", { name }), 3000); + await connectionStore.loadDatabases(node.connectionId); + } catch (e: any) { + toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); + } +} + +function dropDatabase() { + showDropDatabaseConfirm.value = true; +} + +async function confirmDropDatabase() { + const node = props.node; + if (!node.connectionId) return; + try { + await connectionStore.ensureConnected(node.connectionId); + await api.executeQuery(node.connectionId, "", buildDropDatabaseSql()); + toast(t("contextMenu.dropDatabaseSuccess", { name: node.label }), 3000); + await connectionStore.loadDatabases(node.connectionId); + } catch (e: any) { + toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); + } +} + +function openCreateSchemaDialog() { + createSchemaName.value = ""; + showCreateSchemaDialog.value = true; +} + +async function confirmCreateSchema() { + const node = props.node; + const name = createSchemaName.value.trim(); + if (!name || !node.connectionId || !node.database) return; + showCreateSchemaDialog.value = false; + try { + await connectionStore.ensureConnected(node.connectionId); + const sql = `CREATE SCHEMA ${quoteIdent(name)};`; + await api.executeQuery(node.connectionId, node.database, sql); + toast(t("contextMenu.createSchemaSuccess", { name }), 3000); + await connectionStore.loadSchemas(node.connectionId, node.database); + } catch (e: any) { + toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); + } +} + +function dropSchema() { + showDropSchemaConfirm.value = true; +} + +async function confirmDropSchema() { + const node = props.node; + if (!node.connectionId || !node.database) return; + try { + await connectionStore.ensureConnected(node.connectionId); + await api.executeQuery(node.connectionId, node.database, buildDropSchemaSql()); + toast(t("contextMenu.dropSchemaSuccess", { name: node.label }), 3000); + await connectionStore.loadSchemas(node.connectionId, node.database); + } catch (e: any) { + toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); + } +} + function duplicateStructure() { duplicateTableName.value = `${props.node.label}_copy`; showDuplicateDialog.value = true; @@ -1163,6 +1278,9 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr {{ t("sqlFile.title") }} + + {{ t("contextMenu.createDatabase") }} + @@ -1228,6 +1346,9 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr {{ t("contextMenu.createTable") }} + + {{ t("contextMenu.createSchema") }} + {{ t("sqlFile.title") }} @@ -1252,6 +1373,15 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr {{ t("contextMenu.exportDatabase") }} + diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 49cde113ba..a3d5e2cb18 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -451,6 +451,22 @@ export default { tableOperationFailed: "Operation failed: {message}", duplicateNameTitle: "Duplicate Structure", duplicateNamePlaceholder: "New table name", + createDatabase: "Create Database", + dropDatabase: "Drop Database", + confirmDropDatabaseTitle: "Drop Database", + confirmDropDatabaseMessage: + 'Are you sure you want to drop database "{name}"? This will permanently delete the database and all its data.', + createDatabaseSuccess: 'Database "{name}" created', + dropDatabaseSuccess: 'Database "{name}" dropped', + createDatabaseNamePlaceholder: "Database name", + createSchema: "Create Schema", + dropSchema: "Drop Schema", + confirmDropSchemaTitle: "Drop Schema", + confirmDropSchemaMessage: + 'Are you sure you want to drop schema "{name}"? This will permanently delete the schema and all its objects.', + createSchemaSuccess: 'Schema "{name}" created', + dropSchemaSuccess: 'Schema "{name}" dropped', + createSchemaNamePlaceholder: "Schema name", }, tree: { columns: "Columns", diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index d872778797..f66a07a477 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -442,6 +442,20 @@ export default { tableOperationFailed: "操作失败:{message}", duplicateNameTitle: "复制表结构", duplicateNamePlaceholder: "新表名", + createDatabase: "新建数据库", + dropDatabase: "删除数据库", + confirmDropDatabaseTitle: "删除数据库", + confirmDropDatabaseMessage: "确定要删除数据库「{name}」吗?这将永久删除该数据库及其所有数据。", + createDatabaseSuccess: "数据库「{name}」已创建", + dropDatabaseSuccess: "数据库「{name}」已删除", + createDatabaseNamePlaceholder: "数据库名称", + createSchema: "新建 Schema", + dropSchema: "删除 Schema", + confirmDropSchemaTitle: "删除 Schema", + confirmDropSchemaMessage: "确定要删除 Schema「{name}」吗?这将永久删除该 Schema 及其所有对象。", + createSchemaSuccess: "Schema「{name}」已创建", + dropSchemaSuccess: "Schema「{name}」已删除", + createSchemaNamePlaceholder: "Schema 名称", }, tree: { columns: "字段", diff --git a/src/lib/databaseCapabilities.ts b/src/lib/databaseCapabilities.ts index 8aa555a483..62080b4a30 100644 --- a/src/lib/databaseCapabilities.ts +++ b/src/lib/databaseCapabilities.ts @@ -52,6 +52,19 @@ export const TABLE_IMPORT_SUPPORTED_TYPES = new Set([ export const TABLE_STRUCTURE_SUPPORTED_TYPES = new Set(["mysql", "postgres", "sqlite", "sqlserver"]); +export const CREATE_DATABASE_SUPPORTED_TYPES = new Set([ + "mysql", + "postgres", + "sqlserver", + "clickhouse", + "oracle", + "dameng", + "gaussdb", + "doris", + "starrocks", + "redshift", +]); + export const FIELD_LINEAGE_SUPPORTED_TYPES = new Set([ "mysql", "postgres",