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() { - +
- +
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 1ad132a863..e21e32e99e 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -5219,6 +5219,7 @@ export default { searchCatalog: "Search catalog...", sourceDatabase: "Source Database", sourceSchema: "Source Schema", + swap: "Swap source and target", targetConnection: "Target Connection", targetDatabase: "Target Database", targetSchema: "Target Schema", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 25e3bae76b..680e1c9145 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -5014,6 +5014,7 @@ export default withEnglishFallback({ overallProgress: "Progreso general", dataTransfer: "Transferencia de datos", sourceSchema: "Esquema de origen", + swap: "Intercambiar origen y destino", targetSchema: "Esquema de destino", runInBackground: "Ejecutar en segundo plano", newTask: "Nueva transferencia", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index f428a5f696..f2a0951e9d 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -4977,6 +4977,7 @@ export default withEnglishFallback({ sourceConnection: "Connessione Sorgente", sourceDatabase: "Database Sorgente", sourceSchema: "Schema Sorgente", + swap: "Inverti sorgente e destinazione", targetConnection: "Connessione Destinazione", targetDatabase: "Database Destinazione", targetSchema: "Schema Destinazione", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index ba0e0e7b98..5fe62eb138 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -5006,6 +5006,7 @@ export default withEnglishFallback({ sourceConnection: "ソース接続", sourceDatabase: "ソースデータベース", sourceSchema: "ソーススキーマ", + swap: "ソースとターゲットを入れ替え", targetConnection: "ターゲット接続", targetDatabase: "ターゲットデータベース", targetSchema: "ターゲットスキーマ", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index c4b6b19f59..913af444d5 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -4644,6 +4644,7 @@ export default withEnglishFallback({ sourceConnection: "소스 연결", sourceDatabase: "소스 데이터베이스", sourceSchema: "소스 스키마", + swap: "소스와 대상 교체", targetConnection: "대상 연결", targetDatabase: "대상 데이터베이스", targetSchema: "대상 스키마", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index cc1f84cc2e..be5c825b5a 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -4979,6 +4979,7 @@ export default withEnglishFallback({ sourceConnection: "Conexão de origem", sourceDatabase: "Banco de dados de origem", sourceSchema: "Schema de origem", + swap: "Trocar origem e destino", targetConnection: "Conexão de destino", targetDatabase: "Banco de dados de destino", targetSchema: "Schema de destino", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index d8014a3089..7bb71fcb14 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -5204,6 +5204,7 @@ export default withEnglishFallback({ searchCatalog: "搜索 Catalog...", sourceDatabase: "源数据库", sourceSchema: "源 Schema", + swap: "交换源和目标", targetConnection: "目标连接", targetDatabase: "目标数据库", targetSchema: "目标 Schema", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index b457e0d825..5fd1dec30d 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -4303,6 +4303,7 @@ export default withEnglishFallback({ sourceConnection: "來源連線", sourceDatabase: "來源資料庫", sourceSchema: "來源 Schema", + swap: "交換來源與目標", targetConnection: "目標連線", targetDatabase: "目標資料庫", targetSchema: "目標 Schema",