统一生成器与 Git 同步通知
This commit is contained in:
@@ -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,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<DamagedRow> 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<DamagedRow> 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();
|
||||
}
|
||||
|
||||
// ==================== 数据行类 ====================
|
||||
|
||||
+18
-55
@@ -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();
|
||||
|
||||
+23
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user