From c2cc9f22cfe996ee1349b1d04f42ac2e45c3546e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Thu, 2 Jul 2026 10:34:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(connection):=20=E5=A4=8D=E5=88=B6=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E6=97=B6=E4=BF=9D=E6=8C=81=E5=9C=A8=E5=90=8C=E4=B8=80?= =?UTF-8?q?=E7=BB=84=E5=86=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 `addConnection` 方法以支持指定目标组 ID - 更新 `duplicateConnection` 函数以传递目标组 ID - 添加测试用例以验证复制连接的功能 --- .../src/components/sidebar/TreeItem.vue | 2 +- apps/desktop/src/stores/connectionStore.ts | 9 +++- .../connectionStoreGroupedConnect.test.ts | 53 +++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/components/sidebar/TreeItem.vue b/apps/desktop/src/components/sidebar/TreeItem.vue index dcb82b50f..742dfb409 100644 --- a/apps/desktop/src/components/sidebar/TreeItem.vue +++ b/apps/desktop/src/components/sidebar/TreeItem.vue @@ -1459,7 +1459,7 @@ async function duplicateConnection() { const config = connectionStore.getConfig(connId); if (!config) return; const newConfig = { ...config, id: uuid(), name: `${config.name} (Copy)` }; - await connectionStore.addConnection(newConfig); + await connectionStore.addConnection(newConfig, connectionStore.groupIdForConnection(connId)); toast(t("connection.duplicated"), 2000); } diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index 7833931b4..7e48c5e5f 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -9,6 +9,7 @@ import { emptyLayout, appendConnectionToLayout, removeConnectionFromSidebarLayout, + findConnectionLocation, createGroup as createGroupOp, renameGroup as renameGroupOp, deleteGroup as deleteGroupOp, @@ -994,7 +995,7 @@ export const useConnectionStore = defineStore("connection", () => { if (scope === "root") rebuildTreeNodes(); } - async function addConnection(config: ConnectionConfig) { + async function addConnection(config: ConnectionConfig, targetGroupId?: string | null) { const normalized = normalizeConnection(config); const existing = connections.value.findIndex((c) => c.id === normalized.id); const nextConnections = [...connections.value]; @@ -1002,7 +1003,8 @@ export const useConnectionStore = defineStore("connection", () => { nextConnections[existing] = normalized; } else { nextConnections.push(normalized); - sidebarLayout.value = appendConnectionToLayout(sidebarLayout.value, normalized.id, newConnectionGroupId.value); + const groupId = targetGroupId !== undefined ? targetGroupId : newConnectionGroupId.value; + sidebarLayout.value = appendConnectionToLayout(sidebarLayout.value, normalized.id, groupId); } await persistConnections(nextConnections); connections.value = nextConnections; @@ -4057,6 +4059,9 @@ export const useConnectionStore = defineStore("connection", () => { moveConnectionToGroup(connectionId: string, groupId: string | null) { updateLayoutAndRebuild(moveConnectionToGroupOp(sidebarLayout.value, connectionId, groupId)); }, + groupIdForConnection(connectionId: string): string | null { + return findConnectionLocation(sidebarLayout.value, connectionId)?.groupId ?? null; + }, reorderSidebarEntry(draggedId: string, targetId: string, position: DropPosition) { updateLayoutAndRebuild(reorderEntryOp(sidebarLayout.value, draggedId, targetId, position)); }, diff --git a/packages/app-tests/connectionStoreGroupedConnect.test.ts b/packages/app-tests/connectionStoreGroupedConnect.test.ts index 039a0f124..da02b3700 100644 --- a/packages/app-tests/connectionStoreGroupedConnect.test.ts +++ b/packages/app-tests/connectionStoreGroupedConnect.test.ts @@ -100,6 +100,59 @@ test("connecting a grouped connection updates it in place instead of adding a ro } }); +test("duplicating a grouped connection keeps the copy in the same group", async () => { + const originalFetch = globalThis.fetch; + const storage = installMemoryStorage(); + const originalConnection = conn("conn-1", "Grouped MySQL"); + let savedConnections: ConnectionConfig[] = [originalConnection]; + let savedLayout: SidebarLayout | null = { + groups: [{ id: "group-1", name: "Group", collapsed: false }], + order: [{ type: "group", id: "group-1", connectionIds: ["conn-1"] }], + }; + + globalThis.fetch = (async (input, init) => { + const url = String(input); + if (url === "/api/connection/list") { + return new Response(JSON.stringify(savedConnections), { status: 200, headers: { "Content-Type": "application/json" } }); + } + if (url === "/api/layout/sidebar") { + if (init?.method === "POST") { + savedLayout = JSON.parse(String(init.body ?? "null")); + return new Response("null", { status: 200, headers: { "Content-Type": "application/json" } }); + } + return new Response(JSON.stringify(savedLayout), { status: 200, headers: { "Content-Type": "application/json" } }); + } + if (url === "/api/connection/save") { + savedConnections = JSON.parse(String(init?.body ?? "[]")); + return new Response("null", { status: 200, headers: { "Content-Type": "application/json" } }); + } + return new Response("null", { status: 200, headers: { "Content-Type": "application/json" } }); + }) as typeof fetch; + + try { + setActivePinia(createPinia()); + const store = useConnectionStore(); + await store.initFromDisk(); + + const copy = { ...originalConnection, id: "conn-copy", name: "Grouped MySQL (Copy)" }; + await store.addConnection(copy, store.groupIdForConnection(originalConnection.id)); + + assert.deepEqual( + store.treeNodes.map((node) => node.id), + ["group-1"], + ); + assert.equal(store.treeNodes[0].type, "connection-group"); + assert.deepEqual( + store.treeNodes[0].children?.map((node) => node.id), + ["conn-1", "conn-copy"], + ); + assert.equal(countConnectionNodes(store.treeNodes, "conn-copy"), 1); + } finally { + globalThis.fetch = originalFetch; + storage.restore(); + } +}); + test("importing grouped dbx connections remaps exported layout to new connection ids", async () => { const originalFetch = globalThis.fetch; const storage = installMemoryStorage();