diff --git a/src/main/java/com/example/workutils/biz/git/GitSyncWindow.java b/src/main/java/com/example/workutils/biz/git/GitSyncWindow.java index 087e865..a95332f 100644 --- a/src/main/java/com/example/workutils/biz/git/GitSyncWindow.java +++ b/src/main/java/com/example/workutils/biz/git/GitSyncWindow.java @@ -13,6 +13,8 @@ import com.example.workutils.system.ui.Theme; import com.example.workutils.system.ui.async.AsyncOperationController; import com.example.workutils.system.ui.async.LoadingButtonBinder; import com.example.workutils.system.ui.async.OperationStatusPane; +import com.example.workutils.system.ui.notification.FxNotificationCenter; +import com.example.workutils.system.ui.notification.NotificationCenter; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -46,6 +48,7 @@ public class GitSyncWindow { private final FileOperationHelper fileOperationHelper; private final RunGitSyncUseCase syncUseCase; private final AsyncOperationController syncOperation; + private final NotificationCenter notificationCenter; // Tab 1: 项目列表 private TableView projectTable; @@ -76,6 +79,7 @@ public class GitSyncWindow { this.syncOperation = new AsyncOperationController<>( AppExecutors.io(), Platform::runLater); this.stage = new Stage(); + this.notificationCenter = new FxNotificationCenter(stage); buildUI(); } @@ -444,13 +448,9 @@ public class GitSyncWindow { private void recloneSingle(DamagedRow row) { String cloneUrl = row.getCloneUrl(); if (cloneUrl == null || cloneUrl.isEmpty()) { - Platform.runLater(() -> { - Alert alert = new Alert(Alert.AlertType.WARNING); - alert.setTitle("无法重新拉取"); - alert.setHeaderText(row.getName()); - alert.setContentText("无法获取远程仓库 URL,请检查项目路径配置。"); - alert.showAndWait(); - }); + notificationCenter.warning( + "无法重新拉取", + row.getName() + "\n无法获取远程仓库 URL,请检查项目路径配置。"); return; } @@ -471,37 +471,35 @@ public class GitSyncWindow { private void recloneAll() { if (damagedRows.isEmpty()) return; - Alert confirm = new Alert(Alert.AlertType.CONFIRMATION); - confirm.setTitle("确认"); - confirm.setHeaderText(String.format("全部重新拉取 %d 个仓库?", damagedRows.size())); - confirm.setContentText("将删除本地目录并重新 clone,不可撤销。"); - confirm.showAndWait().ifPresent(r -> { - if (r == ButtonType.OK) { - List snapshot = new ArrayList<>(damagedRows); - damagedRows.clear(); - logArea.appendText("\n========== 开始批量重拉 ==========\n"); - new Thread(() -> { - int ok = 0, fail = 0; - for (DamagedRow row : snapshot) { - String cloneUrl = row.getCloneUrl(); - if (cloneUrl == null || cloneUrl.isEmpty()) { - fail++; - continue; - } - Platform.runLater(() -> - logArea.appendText("重拉 " + row.getName() + " ...\n")); - if (plugin.recloneOne(row.getDamagedRepo(), cloneUrl)) { - ok++; - } else { - fail++; - } - } - int finalOk = ok, finalFail = fail; - Platform.runLater(() -> - logArea.appendText(String.format("批量重拉完成: 成功 %d, 失败 %d\n", finalOk, finalFail))); - }, "Reclone-All").start(); + if (!notificationCenter.confirm( + "确认", + String.format("全部重新拉取 %d 个仓库?", damagedRows.size()), + "将删除本地目录并重新 clone,不可撤销。")) { + return; + } + List snapshot = new ArrayList<>(damagedRows); + damagedRows.clear(); + logArea.appendText("\n========== 开始批量重拉 ==========\n"); + new Thread(() -> { + int ok = 0, fail = 0; + for (DamagedRow row : snapshot) { + String cloneUrl = row.getCloneUrl(); + if (cloneUrl == null || cloneUrl.isEmpty()) { + fail++; + continue; + } + Platform.runLater(() -> + logArea.appendText("重拉 " + row.getName() + " ...\n")); + if (plugin.recloneOne(row.getDamagedRepo(), cloneUrl)) { + ok++; + } else { + fail++; + } } - }); + int finalOk = ok, finalFail = fail; + Platform.runLater(() -> + logArea.appendText(String.format("批量重拉完成: 成功 %d, 失败 %d\n", finalOk, finalFail))); + }, "Reclone-All").start(); } // ==================== 数据行类 ==================== diff --git a/src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java b/src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java index 9c6583b..743a99c 100644 --- a/src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java +++ b/src/main/java/com/example/workutils/biz/mybatisgenerator/MyBatisGeneratorWindow.java @@ -18,6 +18,8 @@ import com.example.workutils.system.ui.Theme; import com.example.workutils.system.ui.async.AsyncOperationController; import com.example.workutils.system.ui.async.LoadingButtonBinder; import com.example.workutils.system.ui.async.OperationStatusPane; +import com.example.workutils.system.ui.notification.FxNotificationCenter; +import com.example.workutils.system.ui.notification.NotificationCenter; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -65,6 +67,7 @@ public class MyBatisGeneratorWindow { private final GenerationPlanner generationPlanner; private final AsyncOperationController> queryOperation; private final AsyncOperationController generateOperation; + private final NotificationCenter notificationCenter; // ---- UI 组件 ---- private TextField patternField; @@ -133,6 +136,7 @@ public class MyBatisGeneratorWindow { this.generateOperation = new AsyncOperationController<>( AppExecutors.io(), Platform::runLater); this.stage = new Stage(); + this.notificationCenter = new FxNotificationCenter(stage); buildUI(); } @@ -625,9 +629,10 @@ public class MyBatisGeneratorWindow { "正在查询数据库表", context -> queryTablesUseCase.execute(request), tables -> applyQueriedTables(request, tables), - error -> showAlert("查询失败: " + error.getMessage())); + error -> notificationCenter.warning( + "查询失败", error.getMessage())); } catch (IllegalArgumentException error) { - showAlert(error.getMessage()); + notificationCenter.warning("提示", error.getMessage()); } } @@ -635,7 +640,8 @@ public class MyBatisGeneratorWindow { QueryTablesRequest request, List tables) { tableItems.clear(); if (tables.isEmpty()) { - showAlert("未找到匹配通配符 [" + request.pattern() + "] 的表"); + notificationCenter.warning( + "提示", "未找到匹配通配符 [" + request.pattern() + "] 的表"); } else { for (String table : tables) { tableItems.add(new TableItem(table, true)); @@ -657,10 +663,12 @@ public class MyBatisGeneratorWindow { generateOperation.start( "正在生成代码", context -> generateArtifactsUseCase.execute(request), - result -> showInfo("生成结果", formatGenerationResult(result)), - error -> showAlert("生成失败: " + error.getMessage())); + result -> notificationCenter.info( + "生成结果", formatGenerationResult(result)), + error -> notificationCenter.warning( + "生成失败", error.getMessage())); } catch (RuntimeException error) { - showAlert(error.getMessage()); + notificationCenter.warning("提示", error.getMessage()); } } @@ -680,12 +688,10 @@ public class MyBatisGeneratorWindow { } private boolean confirmGeneration(GenerationPlan plan) { - Alert alert = new Alert(Alert.AlertType.CONFIRMATION); - alert.setTitle("确认生成"); - alert.setHeaderText("即将为 " + plan.tables().size() + " 个表生成代码"); - alert.setContentText(buildGenerationSummary(plan)); - alert.initOwner(stage); - return alert.showAndWait().filter(ButtonType.OK::equals).isPresent(); + return notificationCenter.confirm( + "确认生成", + "即将为 " + plan.tables().size() + " 个表生成代码", + buildGenerationSummary(plan)); } private String buildGenerationSummary(GenerationPlan plan) { @@ -765,49 +771,6 @@ public class MyBatisGeneratorWindow { return box; } - // ==================== 对话框 ==================== - - private void showAlert(String msg) { - Platform.runLater(() -> { - Alert alert = new Alert(Alert.AlertType.WARNING); - alert.setTitle("提示"); - alert.setHeaderText(null); - alert.setContentText(msg); - alert.initOwner(stage); - alert.showAndWait(); - }); - } - - private void showInfo(String title, String msg) { - Platform.runLater(() -> { - Stage dialog = new Stage(); - dialog.initOwner(stage); - dialog.setTitle(title); - dialog.setResizable(true); - - TextArea textArea = new TextArea(msg); - textArea.setEditable(false); - textArea.setWrapText(true); - textArea.setFont(Font.font("Consolas", properties.getFontMono())); - textArea.setStyle("-fx-background-color: " + Theme.toWeb(Theme.BG_BODY) + ";"); - - Button closeBtn = StyledButton.secondary("关闭", Theme.base(14)); - closeBtn.setOnAction(e -> dialog.close()); - - HBox btnBar = new HBox(closeBtn); - btnBar.setAlignment(Pos.CENTER_RIGHT); - btnBar.setPadding(new Insets(Theme.S2)); - btnBar.setStyle("-fx-background-color: " + Theme.toWeb(Theme.BG_SURFACE) + ";"); - - VBox root = new VBox(textArea, btnBar); - VBox.setVgrow(textArea, Priority.ALWAYS); - - Scene scene = new Scene(root, 580, 420); - dialog.setScene(scene); - dialog.showAndWait(); - }); - } - private void close() { closeLatch.countDown(); stage.close(); diff --git a/src/test/java/com/example/workutils/system/ui/notification/WindowNotificationDependencyTest.java b/src/test/java/com/example/workutils/system/ui/notification/WindowNotificationDependencyTest.java new file mode 100644 index 0000000..8bc9af2 --- /dev/null +++ b/src/test/java/com/example/workutils/system/ui/notification/WindowNotificationDependencyTest.java @@ -0,0 +1,23 @@ +package com.example.workutils.system.ui.notification; + +import com.example.workutils.biz.git.GitSyncWindow; +import com.example.workutils.biz.mybatisgenerator.MyBatisGeneratorWindow; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class WindowNotificationDependencyTest { + + @ParameterizedTest + @ValueSource(classes = {MyBatisGeneratorWindow.class, GitSyncWindow.class}) + void targetWindowDependsOnNotificationCenter(Class windowType) { + boolean hasNotificationCenter = Arrays.stream(windowType.getDeclaredFields()) + .anyMatch(field -> field.getType() == NotificationCenter.class); + + assertTrue(hasNotificationCenter, + () -> windowType.getSimpleName() + " must depend on NotificationCenter"); + } +}