统一生成器与 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.AsyncOperationController;
import com.example.workutils.system.ui.async.LoadingButtonBinder; import com.example.workutils.system.ui.async.LoadingButtonBinder;
import com.example.workutils.system.ui.async.OperationStatusPane; 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.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
@@ -46,6 +48,7 @@ public class GitSyncWindow {
private final FileOperationHelper fileOperationHelper; private final FileOperationHelper fileOperationHelper;
private final RunGitSyncUseCase syncUseCase; private final RunGitSyncUseCase syncUseCase;
private final AsyncOperationController<RunGitSyncUseCase.Result> syncOperation; private final AsyncOperationController<RunGitSyncUseCase.Result> syncOperation;
private final NotificationCenter notificationCenter;
// Tab 1: 项目列表 // Tab 1: 项目列表
private TableView<ProjectRow> projectTable; private TableView<ProjectRow> projectTable;
@@ -76,6 +79,7 @@ public class GitSyncWindow {
this.syncOperation = new AsyncOperationController<>( this.syncOperation = new AsyncOperationController<>(
AppExecutors.io(), Platform::runLater); AppExecutors.io(), Platform::runLater);
this.stage = new Stage(); this.stage = new Stage();
this.notificationCenter = new FxNotificationCenter(stage);
buildUI(); buildUI();
} }
@@ -444,13 +448,9 @@ public class GitSyncWindow {
private void recloneSingle(DamagedRow row) { private void recloneSingle(DamagedRow row) {
String cloneUrl = row.getCloneUrl(); String cloneUrl = row.getCloneUrl();
if (cloneUrl == null || cloneUrl.isEmpty()) { if (cloneUrl == null || cloneUrl.isEmpty()) {
Platform.runLater(() -> { notificationCenter.warning(
Alert alert = new Alert(Alert.AlertType.WARNING); "无法重新拉取",
alert.setTitle("无法重新拉取"); row.getName() + "\n无法获取远程仓库 URL,请检查项目路径配置。");
alert.setHeaderText(row.getName());
alert.setContentText("无法获取远程仓库 URL,请检查项目路径配置。");
alert.showAndWait();
});
return; return;
} }
@@ -471,37 +471,35 @@ public class GitSyncWindow {
private void recloneAll() { private void recloneAll() {
if (damagedRows.isEmpty()) return; if (damagedRows.isEmpty()) return;
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION); if (!notificationCenter.confirm(
confirm.setTitle("确认"); "确认",
confirm.setHeaderText(String.format("全部重新拉取 %d 个仓库?", damagedRows.size())); String.format("全部重新拉取 %d 个仓库?", damagedRows.size()),
confirm.setContentText("将删除本地目录并重新 clone,不可撤销。"); "将删除本地目录并重新 clone,不可撤销。")) {
confirm.showAndWait().ifPresent(r -> { return;
if (r == ButtonType.OK) { }
List<DamagedRow> snapshot = new ArrayList<>(damagedRows); List<DamagedRow> snapshot = new ArrayList<>(damagedRows);
damagedRows.clear(); damagedRows.clear();
logArea.appendText("\n========== 开始批量重拉 ==========\n"); logArea.appendText("\n========== 开始批量重拉 ==========\n");
new Thread(() -> { new Thread(() -> {
int ok = 0, fail = 0; int ok = 0, fail = 0;
for (DamagedRow row : snapshot) { for (DamagedRow row : snapshot) {
String cloneUrl = row.getCloneUrl(); String cloneUrl = row.getCloneUrl();
if (cloneUrl == null || cloneUrl.isEmpty()) { if (cloneUrl == null || cloneUrl.isEmpty()) {
fail++; fail++;
continue; continue;
} }
Platform.runLater(() -> Platform.runLater(() ->
logArea.appendText("重拉 " + row.getName() + " ...\n")); logArea.appendText("重拉 " + row.getName() + " ...\n"));
if (plugin.recloneOne(row.getDamagedRepo(), cloneUrl)) { if (plugin.recloneOne(row.getDamagedRepo(), cloneUrl)) {
ok++; ok++;
} else { } else {
fail++; fail++;
} }
}
int finalOk = ok, finalFail = fail;
Platform.runLater(() ->
logArea.appendText(String.format("批量重拉完成: 成功 %d, 失败 %d\n", finalOk, finalFail)));
}, "Reclone-All").start();
} }
}); int finalOk = ok, finalFail = fail;
Platform.runLater(() ->
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.AsyncOperationController;
import com.example.workutils.system.ui.async.LoadingButtonBinder; import com.example.workutils.system.ui.async.LoadingButtonBinder;
import com.example.workutils.system.ui.async.OperationStatusPane; 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.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
@@ -65,6 +67,7 @@ public class MyBatisGeneratorWindow {
private final GenerationPlanner generationPlanner; private final GenerationPlanner generationPlanner;
private final AsyncOperationController<List<String>> queryOperation; private final AsyncOperationController<List<String>> queryOperation;
private final AsyncOperationController<GenerationResult> generateOperation; private final AsyncOperationController<GenerationResult> generateOperation;
private final NotificationCenter notificationCenter;
// ---- UI 组件 ---- // ---- UI 组件 ----
private TextField patternField; private TextField patternField;
@@ -133,6 +136,7 @@ public class MyBatisGeneratorWindow {
this.generateOperation = new AsyncOperationController<>( this.generateOperation = new AsyncOperationController<>(
AppExecutors.io(), Platform::runLater); AppExecutors.io(), Platform::runLater);
this.stage = new Stage(); this.stage = new Stage();
this.notificationCenter = new FxNotificationCenter(stage);
buildUI(); buildUI();
} }
@@ -625,9 +629,10 @@ public class MyBatisGeneratorWindow {
"正在查询数据库表", "正在查询数据库表",
context -> queryTablesUseCase.execute(request), context -> queryTablesUseCase.execute(request),
tables -> applyQueriedTables(request, tables), tables -> applyQueriedTables(request, tables),
error -> showAlert("查询失败: " + error.getMessage())); error -> notificationCenter.warning(
"查询失败", error.getMessage()));
} catch (IllegalArgumentException error) { } catch (IllegalArgumentException error) {
showAlert(error.getMessage()); notificationCenter.warning("提示", error.getMessage());
} }
} }
@@ -635,7 +640,8 @@ public class MyBatisGeneratorWindow {
QueryTablesRequest request, List<String> tables) { QueryTablesRequest request, List<String> tables) {
tableItems.clear(); tableItems.clear();
if (tables.isEmpty()) { if (tables.isEmpty()) {
showAlert("未找到匹配通配符 [" + request.pattern() + "] 的表"); notificationCenter.warning(
"提示", "未找到匹配通配符 [" + request.pattern() + "] 的表");
} else { } else {
for (String table : tables) { for (String table : tables) {
tableItems.add(new TableItem(table, true)); tableItems.add(new TableItem(table, true));
@@ -657,10 +663,12 @@ public class MyBatisGeneratorWindow {
generateOperation.start( generateOperation.start(
"正在生成代码", "正在生成代码",
context -> generateArtifactsUseCase.execute(request), context -> generateArtifactsUseCase.execute(request),
result -> showInfo("生成结果", formatGenerationResult(result)), result -> notificationCenter.info(
error -> showAlert("生成失败: " + error.getMessage())); "生成结果", formatGenerationResult(result)),
error -> notificationCenter.warning(
"生成失败", error.getMessage()));
} catch (RuntimeException error) { } catch (RuntimeException error) {
showAlert(error.getMessage()); notificationCenter.warning("提示", error.getMessage());
} }
} }
@@ -680,12 +688,10 @@ public class MyBatisGeneratorWindow {
} }
private boolean confirmGeneration(GenerationPlan plan) { private boolean confirmGeneration(GenerationPlan plan) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION); return notificationCenter.confirm(
alert.setTitle("确认生成"); "确认生成",
alert.setHeaderText("即将为 " + plan.tables().size() + " 个表生成代码"); "即将为 " + plan.tables().size() + " 个表生成代码",
alert.setContentText(buildGenerationSummary(plan)); buildGenerationSummary(plan));
alert.initOwner(stage);
return alert.showAndWait().filter(ButtonType.OK::equals).isPresent();
} }
private String buildGenerationSummary(GenerationPlan plan) { private String buildGenerationSummary(GenerationPlan plan) {
@@ -765,49 +771,6 @@ public class MyBatisGeneratorWindow {
return box; 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() { private void close() {
closeLatch.countDown(); closeLatch.countDown();
stage.close(); 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");
}
}