统一生成器与 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.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
-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.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();
|
||||||
|
|||||||
+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