From 96d2f0dd1c73e843f8479408703e55913f156768 Mon Sep 17 00:00:00 2001 From: GIWTO <40267153+GIWTO@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:38:10 +0800 Subject: [PATCH] feat(transfer): swap source and target connections --- .../transfer/DataTransferDialog.vue | 88 ++++++++++++++++++- apps/desktop/src/i18n/locales/en.ts | 1 + apps/desktop/src/i18n/locales/es.ts | 1 + apps/desktop/src/i18n/locales/it.ts | 1 + apps/desktop/src/i18n/locales/ja.ts | 1 + apps/desktop/src/i18n/locales/ko.ts | 1 + apps/desktop/src/i18n/locales/pt-BR.ts | 1 + apps/desktop/src/i18n/locales/zh-CN.ts | 1 + apps/desktop/src/i18n/locales/zh-TW.ts | 1 + 9 files changed, 94 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/components/transfer/DataTransferDialog.vue b/apps/desktop/src/components/transfer/DataTransferDialog.vue index e38534cb4a..699cee4a6b 100644 --- a/apps/desktop/src/components/transfer/DataTransferDialog.vue +++ b/apps/desktop/src/components/transfer/DataTransferDialog.vue @@ -651,6 +651,88 @@ function resetState(cancelTaskLoad = true) { savedConfigSnapshot.value = ""; } +/** + * 交换源和目标两侧:连接、Catalog、数据库、Schema 的选择随各自一侧整体互换, + * 一次点击即可反转传输方向。对象树只属于源端,旧选择作废,按新源端重新加载。 + */ +function swapSourceAndTarget() { + const sourceState = { + connectionId: sourceConnectionId.value, + catalog: sourceCatalog.value, + catalogs: sourceCatalogs.value, + database: sourceDatabase.value, + databases: sourceDatabases.value, + schema: sourceSchema.value, + schemas: sourceSchemas.value, + }; + const targetState = { + connectionId: targetConnectionId.value, + catalog: targetCatalog.value, + catalogs: targetCatalogs.value, + database: targetDatabase.value, + databases: targetDatabases.value, + schema: targetSchema.value, + schemas: targetSchemas.value, + }; + + if (sourceState.connectionId === targetState.connectionId && sourceState.catalog === targetState.catalog && sourceState.database === targetState.database && sourceState.schema === targetState.schema) { + // 两侧选择完全相同(含都未选择)时交换没有意义 + return; + } + + // 若正在加载已保存任务,先取消,避免任务加载链覆盖交换后的状态 + taskLoadTracker.cancel(); + pendingSourceSchemaPrefill.value = ""; + pendingTargetSchemaPrefill.value = ""; + pendingSelectedTablesPrefill.value = null; + pendingSelectedObjectsPrefill.value = null; + + // 只在值确实变化时设置一次性跳过标记:标记由对应 watcher 消费, + // 若值未变则 watcher 不会触发,残留标记会误吞后续一次真实变更。 + if (sourceConnectionId.value !== targetState.connectionId) { + skipSourceWatch.value = true; + sourceConnectionId.value = targetState.connectionId; + } + if (sourceCatalog.value !== targetState.catalog) { + skipSourceCatalogWatch.value = true; + sourceCatalog.value = targetState.catalog; + } + sourceCatalogs.value = targetState.catalogs; + if (sourceDatabase.value !== targetState.database) { + skipSourceDatabaseWatch.value = true; + sourceDatabase.value = targetState.database; + } + sourceDatabases.value = targetState.databases; + if (sourceSchema.value !== targetState.schema) { + skipSourceSchemaWatch.value = true; + sourceSchema.value = targetState.schema; + } + sourceSchemas.value = targetState.schemas; + + if (targetConnectionId.value !== sourceState.connectionId) { + skipTargetWatch.value = true; + targetConnectionId.value = sourceState.connectionId; + } + if (targetCatalog.value !== sourceState.catalog) { + skipTargetCatalogWatch.value = true; + targetCatalog.value = sourceState.catalog; + } + targetCatalogs.value = sourceState.catalogs; + if (targetDatabase.value !== sourceState.database) { + skipTargetDatabaseWatch.value = true; + targetDatabase.value = sourceState.database; + } + targetDatabases.value = sourceState.databases; + targetSchema.value = sourceState.schema; + targetSchemas.value = sourceState.schemas; + + // 对象树始终跟随源端:新源端是旧目标端,旧选择已无意义,清空后重新加载 + objectGroups.value = {}; + selectedObjects.value = {}; + objectSearch.value = ""; + void loadObjects(); +} + async function startTransfer() { if (!canStart.value || isSubmitting.value) return; isSubmitting.value = true; @@ -1037,9 +1119,11 @@ async function saveConfigTask() { - +