统一生成器与 Git 同步通知

This commit is contained in:
张家训
2026-07-31 19:23:59 +08:00
parent fe3db58bfe
commit 9c82c8f638
3 changed files with 76 additions and 92 deletions
@@ -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<RunGitSyncUseCase.Result> syncOperation;
private final NotificationCenter notificationCenter;
// Tab 1: 项目列表
private TableView<ProjectRow> 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,12 +471,12 @@ 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) {
if (!notificationCenter.confirm(
"确认",
String.format("全部重新拉取 %d 个仓库?", damagedRows.size()),
"将删除本地目录并重新 clone,不可撤销。")) {
return;
}
List<DamagedRow> snapshot = new ArrayList<>(damagedRows);
damagedRows.clear();
logArea.appendText("\n========== 开始批量重拉 ==========\n");
@@ -501,8 +501,6 @@ public class GitSyncWindow {
logArea.appendText(String.format("批量重拉完成: 成功 %d, 失败 %d\n", finalOk, finalFail)));
}, "Reclone-All").start();
}
});
}
// ==================== 数据行类 ====================
@@ -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<List<String>> queryOperation;
private final AsyncOperationController<GenerationResult> 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<String> 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();
@@ -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");
}
}