SECTIONS = new LinkedHashMap<>();
static {
@@ -68,13 +68,6 @@ public class ExternalConfigManager {
SECTIONS.put("===== 数据文件路径 =====", new String[]{
KEY_PROJECT_PATHS, KEY_DAMAGED_REPOS
});
- SECTIONS.put("===== MyBatis 数据库连接(覆盖内置 mybatis-generator.properties)=====", new String[]{
- KEY_MYBATIS_DRIVER, KEY_MYBATIS_URL, KEY_MYBATIS_USERNAME,
- KEY_MYBATIS_PASSWORD
- });
- SECTIONS.put("===== MyBatis 代码生成包路径 =====", new String[]{
- KEY_MYBATIS_MODEL_PKG, KEY_MYBATIS_DAO_PKG, KEY_MYBATIS_SQLMAPPER_PKG
- });
SECTIONS.put("===== 字体大小(显示设置) =====", new String[]{
KEY_FONT_SECTION_TITLE, KEY_FONT_FIELD_LABEL, KEY_FONT_BUTTON,
KEY_FONT_BODY, KEY_FONT_MONO
diff --git a/src/main/java/com/example/workutils/system/config/SettingsService.java b/src/main/java/com/example/workutils/system/config/SettingsService.java
index e19c1f8..8ddf959 100644
--- a/src/main/java/com/example/workutils/system/config/SettingsService.java
+++ b/src/main/java/com/example/workutils/system/config/SettingsService.java
@@ -22,6 +22,7 @@ public class SettingsService {
private final WorkUtilsProperties properties;
private final ExternalConfigManager configManager;
+ private final DatasourceConfigManager datasourceConfigManager;
/**
* 打开系统设置 GUI 窗口
@@ -30,7 +31,7 @@ public class SettingsService {
*/
public void showSettings(Scanner scanner, Runnable onConfigChanged) {
try {
- SettingsWindow window = new SettingsWindow(properties, configManager, onConfigChanged);
+ SettingsWindow window = new SettingsWindow(properties, configManager, datasourceConfigManager, onConfigChanged);
window.showAndWait();
} catch (Exception e) {
log.error("打开设置窗口失败,回退到控制台模式", e);
diff --git a/src/main/java/com/example/workutils/system/config/SettingsWindow.java b/src/main/java/com/example/workutils/system/config/SettingsWindow.java
index 8191427..a74aac0 100644
--- a/src/main/java/com/example/workutils/system/config/SettingsWindow.java
+++ b/src/main/java/com/example/workutils/system/config/SettingsWindow.java
@@ -8,31 +8,36 @@ import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
-import javafx.scene.layout.*;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.DirectoryChooser;
import javafx.stage.Screen;
import javafx.stage.Stage;
import lombok.extern.slf4j.Slf4j;
+import java.awt.Desktop;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.List;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
/**
- * 系统设置可视化窗口(JavaFX)
+ * 系统设置可视化窗口(JavaFX)—— 重构版
+ *
+ * 每个 Tab 直接指向 data 目录下的配置文件,用系统默认编辑器打开编辑,
+ * 各配置文件独立管理,编辑后点击「重新加载」生效。
*
* Tab 页组织:
*
- * - 系统路径 — 基础目录、导出目录、数据文件路径
- * - MyBatis 数据库 — 覆盖 classpath 内的 mybatis-generator.properties
- * - 项目管理 — 查看/编辑项目路径文件、损坏仓库缓存
+ * - 系统路径 — 打开 workutils.config(系统路径 + 字体设置)
+ * - MyBatis 数据库 — 打开 data/mybatis-generator.properties
+ * - 项目路径 — 打开 data/project_paths.txt + 批量字符替换工具
+ * - 多数据源 — 打开 data/datasources.json
+ * - 损坏缓存 — 查看 data/damaged_repos.cache(只读)
+ * - 显示设置 — 字体大小滑块预览
*
*/
@Slf4j
@@ -40,26 +45,14 @@ public class SettingsWindow {
private final WorkUtilsProperties properties;
private final ExternalConfigManager configManager;
+ private final DatasourceConfigManager datasourceConfigManager;
private final Runnable onConfigChanged;
private Stage stage;
- // --- 系统路径 Tab ---
- private TextField tfBaseDir, tfExportDir, tfProjectPathsFile, tfDamagedReposFile;
- private Label lblConfigFile;
-
- // --- MyBatis Tab ---
- private TextField tfDbDriver, tfDbUrl, tfDbUsername;
- private PasswordField pfDbPassword;
- private TextField tfModelPkg, tfDaoPkg, tfSqlMapperPkg;
-
- // --- 项目管理 Tab ---
- private TextArea taProjectPaths;
- private TextArea taDamagedCache;
- private Label lblProjectPathsFile, lblDamagedCacheFile;
-
- // --- 项目路径替换 ---
+ // --- 项目路径替换工具 ---
private TextField tfReplaceParentDir, tfReplaceAccount;
+ private TextArea taProjectPathsPreview;
// --- 字体大小 ---
private int fontSectionTitle;
@@ -77,9 +70,11 @@ public class SettingsWindow {
public SettingsWindow(WorkUtilsProperties properties,
ExternalConfigManager configManager,
+ DatasourceConfigManager datasourceConfigManager,
Runnable onConfigChanged) {
this.properties = properties;
this.configManager = configManager;
+ this.datasourceConfigManager = datasourceConfigManager;
this.onConfigChanged = onConfigChanged;
this.fontSectionTitle = properties.getFontSectionTitle();
this.fontFieldLabel = properties.getFontFieldLabel();
@@ -109,7 +104,6 @@ public class SettingsWindow {
}
});
latch.await();
- // 关闭后显式释放 stage,减少 JavaFX 对 System.in 的干扰
Platform.runLater(() -> {
if (stage != null) {
stage.hide();
@@ -124,13 +118,15 @@ public class SettingsWindow {
createPathTab(),
createMyBatisTab(),
createProjectTab(),
+ createDatasourceTab(),
+ createDamagedCacheTab(),
createDisplayTab()
);
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
VBox root = new VBox(10);
root.setPadding(new Insets(12));
- root.getChildren().addAll(tabPane, createBottomButtons());
+ root.getChildren().add(tabPane);
return new Scene(root, screen.getWidth() * 0.75, screen.getHeight() * 0.75);
}
@@ -139,179 +135,274 @@ public class SettingsWindow {
return Screen.getPrimary().getVisualBounds();
}
+ // ==================== 通用 Tab 构建器 ====================
+
+ /**
+ * 创建一个标准配置 Tab:文件路径 + 打开/重新加载按钮
+ */
+ private Tab createConfigFileTab(String tabName, String fileDescription,
+ Path filePath, Runnable onReload,
+ boolean isReadOnly, String extraHint) {
+ VBox box = new VBox(12);
+ box.setPadding(new Insets(15));
+
+ // 标题
+ Label title = new Label(fileDescription);
+ title.setStyle("-fx-font-weight: bold; -fx-font-size: " + fontSectionTitle + "px;");
+ titleLabels.add(title);
+ box.getChildren().add(title);
+
+ // 文件路径
+ Label pathLabel = new Label(filePath.toAbsolutePath().toString());
+ pathLabel.setStyle("-fx-font-family: 'Consolas'; -fx-font-size: " + fontMono + "px; "
+ + "-fx-background-color: #f5f5f5; -fx-padding: 8; -fx-background-radius: 4;");
+ pathLabel.setWrapText(true);
+ pathLabel.setMaxWidth(Double.MAX_VALUE);
+ box.getChildren().add(pathLabel);
+
+ if (!Files.exists(filePath)) {
+ Label notExist = new Label("⚠ 文件尚未创建,点击下方按钮将自动创建空文件。");
+ notExist.setStyle("-fx-text-fill: #e67e22; -fx-font-size: " + fontBody + "px;");
+ hintLabels.add(notExist);
+ box.getChildren().add(notExist);
+ }
+
+ if (isReadOnly) {
+ Label readOnly = new Label("⚠ 此文件为只读记录,由系统自动维护。");
+ readOnly.setStyle("-fx-text-fill: #c0392b; -fx-font-size: " + fontBody + "px;");
+ hintLabels.add(readOnly);
+ box.getChildren().add(readOnly);
+ }
+
+ // 按钮行
+ HBox btnRow = new HBox(10);
+ btnRow.setAlignment(Pos.CENTER_LEFT);
+
+ Button btnOpen = new Button(isReadOnly ? "用记事本查看" : "用记事本打开");
+ btnOpen.setFont(font(fontButton));
+ btnOpen.setStyle("-fx-padding: 10 20; -fx-background-radius: 4; -fx-cursor: hand;");
+ allButtons.add(btnOpen);
+ btnOpen.setOnAction(e -> openFileInEditor(filePath));
+ btnRow.getChildren().add(btnOpen);
+
+ if (onReload != null) {
+ Button btnReload = new Button("重新加载");
+ btnReload.setFont(font(fontButton));
+ btnReload.setStyle("-fx-padding: 10 20; -fx-background-radius: 4; -fx-cursor: hand;");
+ allButtons.add(btnReload);
+ btnReload.setOnAction(e -> {
+ onReload.run();
+ showAlert(AlertType.INFORMATION, "已重新加载: " + fileDescription);
+ });
+ btnRow.getChildren().add(btnReload);
+ }
+
+ box.getChildren().add(btnRow);
+
+ // 提示
+ String hint = isReadOnly
+ ? "此文件由系统自动维护,仅可查看。"
+ : "用外部编辑器编辑后,点击「重新加载」使配置生效,无需重启。";
+ if (extraHint != null && !extraHint.isEmpty()) {
+ hint = hint + "\n" + extraHint;
+ }
+ Label hintLabel = new Label(hint);
+ hintLabel.setStyle("-fx-text-fill: #666; -fx-font-size: " + fontBody + "px;");
+ hintLabels.add(hintLabel);
+ box.getChildren().add(hintLabel);
+
+ Tab tab = new Tab(tabName);
+ tab.setContent(box);
+ tab.setClosable(false);
+ return tab;
+ }
+
// ==================== Tab 1: 系统路径 ====================
private Tab createPathTab() {
- GridPane grid = new GridPane();
- grid.setHgap(10);
- grid.setVgap(12);
- grid.setPadding(new Insets(15));
- // 三列:标签 | 输入框(自动伸缩) | 按钮
- ColumnConstraints colLabel = new ColumnConstraints();
- colLabel.setPercentWidth(18);
- ColumnConstraints colField = new ColumnConstraints();
- colField.setPercentWidth(72);
- ColumnConstraints colBtn = new ColumnConstraints();
- colBtn.setPercentWidth(10);
- grid.getColumnConstraints().addAll(colLabel, colField, colBtn);
-
- int row = 0;
-
- // 节标题
- addTitleLabel(grid, row++, "系统路径", 3);
- tfBaseDir = addBrowseRow(grid, row++, "基础目录:", properties.getBaseDirectory());
- tfExportDir = addBrowseRow(grid, row++, "导出目录:", properties.getExportDirectory());
-
- grid.add(new Separator(), 0, row++, 3, 1);
-
- Label secData = addTitleLabel(grid, row++, "数据文件路径", 3);
- secData.setStyle(secData.getStyle() + "-fx-padding: 8 0 8 0;");
-
- tfProjectPathsFile = addBrowseRow(grid, row++, "项目路径文件:",
- properties.getProjectPathsFilePath().toAbsolutePath().toString());
- tfDamagedReposFile = addBrowseRow(grid, row++, "损坏缓存文件:",
- properties.getDamagedReposFilePath().toAbsolutePath().toString());
-
- grid.add(new Separator(), 0, row++, 3, 1);
- lblConfigFile = new Label("配置文件: " + configManager.getConfigFile().toAbsolutePath()
- + (configManager.exists() ? " (已存在)" : " (不存在)"));
- lblConfigFile.setStyle("-fx-text-fill: gray; -fx-font-size: " + fontBody + "px;");
- hintLabels.add(lblConfigFile);
- grid.add(lblConfigFile, 0, row, 3, 1);
-
- row++;
- Label hint = new Label("提示: 编辑后点击「保存到 workutils.config」生效,无需重启。");
- hint.setStyle("-fx-text-fill: #666; -fx-font-size: " + fontBody + "px;");
- hintLabels.add(hint);
- grid.add(hint, 0, row, 3, 1);
-
- Tab tab = new Tab("系统路径");
- tab.setContent(grid);
- tab.setClosable(false);
- return tab;
+ return createConfigFileTab(
+ "系统路径",
+ "系统路径配置",
+ configManager.getConfigFile(),
+ this::reloadWorkutilsConfig,
+ false,
+ "编辑 workutils.config 中的 base-directory、export-directory 等属性。"
+ + "\n字体大小请在「显示设置」Tab 中调整。"
+ );
}
// ==================== Tab 2: MyBatis 数据库 ====================
private Tab createMyBatisTab() {
- VBox wrapper = new VBox(12);
- wrapper.setPadding(new Insets(15));
-
- Label hint = new Label("以下配置将覆盖 JAR 内置的 mybatis-generator.properties,留空则使用内置值。");
- hint.setStyle("-fx-text-fill: #666; -fx-font-size: " + fontBody + "px;");
- hintLabels.add(hint);
- wrapper.getChildren().add(hint);
-
- GridPane grid = new GridPane();
- grid.setHgap(10);
- grid.setVgap(10);
- ColumnConstraints colLbl = new ColumnConstraints();
- colLbl.setPercentWidth(22);
- ColumnConstraints colFld = new ColumnConstraints();
- colFld.setPercentWidth(78);
- grid.getColumnConstraints().addAll(colLbl, colFld);
-
- int row = 0;
-
- addTitleLabel(grid, row++, "数据库连接", 2);
-
- tfDbDriver = addField(grid, row++, "数据库驱动:", "");
- tfDbUrl = addField(grid, row++, "数据库 URL:", "");
- tfDbUsername = addField(grid, row++, "用户名:", "");
- pfDbPassword = new PasswordField();
- textInputs.add(pfDbPassword);
- HBox.setHgrow(pfDbPassword, Priority.ALWAYS);
- GridPane.setHgrow(pfDbPassword, Priority.ALWAYS);
- addLabeled(grid, row++, "密码:", pfDbPassword);
-
- grid.add(new Separator(), 0, row++, 2, 1);
-
- addTitleLabel(grid, row++, "代码生成包路径", 2);
-
- tfModelPkg = addField(grid, row++, "Model 包路径:", "");
- tfDaoPkg = addField(grid, row++, "Mapper 包路径:", "");
- tfSqlMapperPkg = addField(grid, row++, "SQL XML 包路径:", "");
-
- wrapper.getChildren().add(grid);
-
- Button btnLoadBuiltin = new Button("从 data/mybatis-generator.properties 加载");
- btnLoadBuiltin.setFont(font(fontButton));
- btnLoadBuiltin.setStyle("-fx-padding: 6 14;");
- allButtons.add(btnLoadBuiltin);
- btnLoadBuiltin.setOnAction(e -> loadMyBatisFromClasspath());
- wrapper.getChildren().add(btnLoadBuiltin);
-
- refreshMyBatisFields();
-
- Tab tab = new Tab("MyBatis 数据库");
- tab.setContent(wrapper);
- tab.setClosable(false);
- return tab;
+ return createConfigFileTab(
+ "MyBatis 数据库",
+ "MyBatis 生成器数据库配置",
+ properties.getMybatisGeneratorFilePath(),
+ this::reloadMyBatisConfig,
+ false,
+ "编辑 data/mybatis-generator.properties:db_driver、db_url、db_username、db_password、\n"
+ + "model_package、dao_package、sqlmapper_package 等属性。"
+ );
}
- // ==================== Tab 3: 项目管理 ====================
+ // ==================== Tab 3: 项目路径 ====================
private Tab createProjectTab() {
- TabPane innerTabs = new TabPane();
- innerTabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
+ VBox box = new VBox(12);
+ box.setPadding(new Insets(15));
- // 子Tab: 项目路径
- VBox ppBox = new VBox(10);
- ppBox.setPadding(new Insets(10));
- lblProjectPathsFile = new Label();
- lblProjectPathsFile.setStyle("-fx-text-fill: gray; -fx-font-size: " + fontBody + "px;");
- hintLabels.add(lblProjectPathsFile);
- taProjectPaths = new TextArea();
- taProjectPaths.setPrefRowCount(14);
- taProjectPaths.setStyle("-fx-font-size: " + fontMono + "px;");
- textInputs.add(taProjectPaths);
- taProjectPaths.setPromptText("# 每行一个项目\n# 格式: 项目路径\n# 格式: 项目路径=BASE_URL\n# # 开头为注释");
- Button btnLoadPP = addSmallButton("从文件加载");
- btnLoadPP.setOnAction(e -> loadProjectPaths());
- Button btnSavePP = addSmallButton("保存到文件");
- btnSavePP.setOnAction(e -> saveProjectPaths());
- Button btnOpenPP = addSmallButton("用记事本打开");
- btnOpenPP.setOnAction(e -> openInNotepad(properties.getProjectPathsFilePath()));
+ Path filePath = properties.getProjectPathsFilePath();
- HBox ppBtns = new HBox(10, btnLoadPP, btnSavePP, btnOpenPP);
- ppBox.getChildren().addAll(lblProjectPathsFile, taProjectPaths, ppBtns, createReplaceTool());
+ // 标题
+ Label title = new Label("项目路径配置");
+ title.setStyle("-fx-font-weight: bold; -fx-font-size: " + fontSectionTitle + "px;");
+ titleLabels.add(title);
+ box.getChildren().add(title);
- Tab ppTab = new Tab("项目路径");
- ppTab.setContent(ppBox);
+ // 文件路径
+ Label pathLabel = new Label(filePath.toAbsolutePath().toString());
+ pathLabel.setStyle("-fx-font-family: 'Consolas'; -fx-font-size: " + fontMono + "px; "
+ + "-fx-background-color: #f5f5f5; -fx-padding: 8; -fx-background-radius: 4;");
+ pathLabel.setWrapText(true);
+ pathLabel.setMaxWidth(Double.MAX_VALUE);
+ box.getChildren().add(pathLabel);
- // 子Tab: 损坏仓库缓存
- VBox dcBox = new VBox(10);
- dcBox.setPadding(new Insets(10));
- lblDamagedCacheFile = new Label();
- lblDamagedCacheFile.setStyle("-fx-text-fill: gray; -fx-font-size: " + fontBody + "px;");
- hintLabels.add(lblDamagedCacheFile);
- taDamagedCache = new TextArea();
- taDamagedCache.setPrefRowCount(14);
- taDamagedCache.setStyle("-fx-font-size: " + fontMono + "px;");
- textInputs.add(taDamagedCache);
- taDamagedCache.setEditable(false);
- Button btnLoadDC = addSmallButton("刷新");
- btnLoadDC.setOnAction(e -> loadDamagedCache());
- Button btnClearDC = addSmallButton("清理缓存");
- btnClearDC.setOnAction(e -> clearDamagedCache());
- btnClearDC.setStyle("-fx-text-fill: red; -fx-cursor: hand;");
+ // 按钮行
+ HBox btnRow = new HBox(10);
+ btnRow.setAlignment(Pos.CENTER_LEFT);
- HBox dcBtns = new HBox(10, btnLoadDC, btnClearDC);
- dcBox.getChildren().addAll(lblDamagedCacheFile, taDamagedCache, dcBtns);
+ Button btnOpen = new Button("用记事本打开");
+ btnOpen.setFont(font(fontButton));
+ btnOpen.setStyle("-fx-padding: 10 20; -fx-background-radius: 4; -fx-cursor: hand;");
+ allButtons.add(btnOpen);
+ btnOpen.setOnAction(e -> openFileInEditor(filePath));
+ btnRow.getChildren().add(btnOpen);
- Tab dcTab = new Tab("损坏仓库缓存");
- dcTab.setContent(dcBox);
+ Button btnReload = new Button("重新加载");
+ btnReload.setFont(font(fontButton));
+ btnReload.setStyle("-fx-padding: 10 20; -fx-background-radius: 4; -fx-cursor: hand;");
+ allButtons.add(btnReload);
+ btnReload.setOnAction(e -> {
+ reloadProjectPathsPreview();
+ showAlert(AlertType.INFORMATION, "已重新加载项目路径。");
+ });
+ btnRow.getChildren().add(btnReload);
- innerTabs.getTabs().addAll(ppTab, dcTab);
- refreshProjectTab();
+ box.getChildren().add(btnRow);
- Tab tab = new Tab("项目管理");
- tab.setContent(innerTabs);
+ // 提示
+ Label hint = new Label("用外部编辑器编辑 project_paths.txt 后,点击「重新加载」使配置生效。");
+ hint.setStyle("-fx-text-fill: #666; -fx-font-size: " + fontBody + "px;");
+ hintLabels.add(hint);
+ box.getChildren().add(hint);
+
+ // --- 批量字符替换工具 ---
+ box.getChildren().add(createReplaceTool());
+
+ Tab tab = new Tab("项目路径");
+ tab.setContent(box);
tab.setClosable(false);
return tab;
}
- // ==================== Tab 4: 显示设置 ====================
+ // ==================== Tab 4: 多数据源 ====================
+
+ private Tab createDatasourceTab() {
+ return createConfigFileTab(
+ "多数据源",
+ "多数据源配置",
+ properties.getDatasourcesFilePath(),
+ () -> datasourceConfigManager.reload(),
+ false,
+ "编辑 data/datasources.json 中的多套数据源列表。\n"
+ + "格式为 JSON 数组,每项包含 name、db_driver、db_url、db_username、db_password、\n"
+ + "model_package、dao_package、sqlmapper_package 等字段。"
+ );
+ }
+
+ // ==================== Tab 5: 损坏缓存 ====================
+
+ private Tab createDamagedCacheTab() {
+ VBox box = new VBox(12);
+ box.setPadding(new Insets(15));
+
+ Path filePath = properties.getDamagedReposFilePath();
+
+ // 标题
+ Label title = new Label("损坏仓库缓存");
+ title.setStyle("-fx-font-weight: bold; -fx-font-size: " + fontSectionTitle + "px;");
+ titleLabels.add(title);
+ box.getChildren().add(title);
+
+ // 文件路径
+ Label pathLabel = new Label(filePath.toAbsolutePath().toString());
+ pathLabel.setStyle("-fx-font-family: 'Consolas'; -fx-font-size: " + fontMono + "px; "
+ + "-fx-background-color: #f5f5f5; -fx-padding: 8; -fx-background-radius: 4;");
+ pathLabel.setWrapText(true);
+ pathLabel.setMaxWidth(Double.MAX_VALUE);
+ box.getChildren().add(pathLabel);
+
+ if (!Files.exists(filePath)) {
+ Label notExist = new Label("⚠ 缓存文件为空,暂无损坏记录。");
+ notExist.setStyle("-fx-text-fill: #e67e22; -fx-font-size: " + fontBody + "px;");
+ hintLabels.add(notExist);
+ box.getChildren().add(notExist);
+ }
+
+ Label readOnly = new Label("⚠ 此文件由系统自动维护,记录同步过程中损坏的 Git 仓库。");
+ readOnly.setStyle("-fx-text-fill: #c0392b; -fx-font-size: " + fontBody + "px;");
+ hintLabels.add(readOnly);
+ box.getChildren().add(readOnly);
+
+ // 按钮行
+ HBox btnRow = new HBox(10);
+ btnRow.setAlignment(Pos.CENTER_LEFT);
+
+ Button btnView = new Button("用记事本查看");
+ btnView.setFont(font(fontButton));
+ btnView.setStyle("-fx-padding: 10 20; -fx-background-radius: 4; -fx-cursor: hand;");
+ allButtons.add(btnView);
+ btnView.setOnAction(e -> openFileInEditor(filePath));
+ btnRow.getChildren().add(btnView);
+
+ Button btnClear = new Button("清理损坏缓存");
+ btnClear.setFont(font(fontButton));
+ btnClear.setStyle("-fx-text-fill: #c0392b; -fx-padding: 10 20; "
+ + "-fx-background-radius: 4; -fx-cursor: hand;");
+ allButtons.add(btnClear);
+ btnClear.setOnAction(e -> {
+ Alert confirm = new Alert(AlertType.CONFIRMATION);
+ confirm.setTitle("确认清理");
+ confirm.setHeaderText("清除损坏仓库缓存?");
+ confirm.setContentText("缓存清除后,下次同步时将重新扫描所有仓库。");
+ confirm.showAndWait().ifPresent(r -> {
+ if (r == ButtonType.OK) {
+ try {
+ Files.deleteIfExists(filePath);
+ showAlert(AlertType.INFORMATION, "损坏仓库缓存已清理。");
+ } catch (IOException ex) {
+ showAlert(AlertType.ERROR, "清理失败: " + ex.getMessage());
+ }
+ }
+ });
+ });
+ btnRow.getChildren().add(btnClear);
+
+ box.getChildren().add(btnRow);
+
+ // 提示
+ Label hint = new Label("此文件由系统自动维护。清除后下次同步时将重新扫描所有仓库。");
+ hint.setStyle("-fx-text-fill: #666; -fx-font-size: " + fontBody + "px;");
+ hintLabels.add(hint);
+ box.getChildren().add(hint);
+
+ Tab tab = new Tab("损坏缓存");
+ tab.setContent(box);
+ tab.setClosable(false);
+ return tab;
+ }
+
+ // ==================== Tab 6: 显示设置 ====================
private Tab createDisplayTab() {
VBox root = new VBox(12);
@@ -323,14 +414,13 @@ public class SettingsWindow {
titleLabels.add(title);
root.getChildren().add(title);
- Label note = new Label("可调范围 14px – 24px,点击「保存到 workutils.config」后全局生效");
+ Label note = new Label("可调范围 16px – 32px,点击「保存字体设置」后写入 workutils.config 并全局生效");
note.setStyle("-fx-text-fill: #666; -fx-font-size: " + fontBody + "px;");
hintLabels.add(note);
root.getChildren().add(note);
root.getChildren().add(new Separator());
- // 每个字体类别一行
root.getChildren().add(createFontRow("节标题(如: 系统路径、数据库连接)",
fontSectionTitle, f -> { fontSectionTitle = f; applyAllFontSizes(); }));
root.getChildren().add(createFontRow("字段标签(如: 基础目录、数据库 URL)",
@@ -339,409 +429,128 @@ public class SettingsWindow {
fontButton, f -> { fontButton = f; applyAllFontSizes(); }));
root.getChildren().add(createFontRow("正文/提示(如: 配置说明、状态信息)",
fontBody, f -> { fontBody = f; applyAllFontSizes(); }));
- root.getChildren().add(createFontRow("等宽/代码(如: 数据库连接字符串、TextArea 内容)",
+ root.getChildren().add(createFontRow("等宽/代码(如: 文件路径、TextArea 内容)",
fontMono, f -> { fontMono = f; applyAllFontSizes(); }));
+ root.getChildren().add(new Separator());
+
+ Button btnSaveFont = new Button("保存字体设置");
+ btnSaveFont.setFont(font(fontButton));
+ btnSaveFont.setStyle("-fx-padding: 10 24; -fx-background-radius: 4; -fx-cursor: hand;");
+ allButtons.add(btnSaveFont);
+ btnSaveFont.setOnAction(e -> saveFontSettings());
+ root.getChildren().add(btnSaveFont);
+
Tab tab = new Tab("显示设置");
tab.setContent(root);
tab.setClosable(false);
return tab;
}
- private HBox createFontRow(String description, int initialValue,
- java.util.function.IntConsumer onChanged) {
- HBox row = new HBox(12);
- row.setAlignment(Pos.CENTER_LEFT);
- row.setPadding(new Insets(6, 0, 6, 0));
+ // ==================== 重新加载逻辑 ====================
- // 左侧名称
- Label nameLabel = new Label(description);
- nameLabel.setPrefWidth(320);
- nameLabel.setFont(font(14));
-
- // 滑块 14-24
- Slider slider = new Slider(14, 24, initialValue);
- slider.setPrefWidth(180);
- slider.setMajorTickUnit(2);
- slider.setMinorTickCount(1);
- slider.setShowTickMarks(true);
- slider.setShowTickLabels(false);
- slider.setSnapToTicks(true);
-
- // 当前值标签
- Label valLabel = new Label(String.valueOf(initialValue) + "px");
- valLabel.setPrefWidth(40);
- valLabel.setFont(font(14));
- valLabel.setStyle("-fx-font-weight: bold; -fx-text-fill: #4a90d9;");
-
- // 预览文字
- Label preview = new Label("预览 Abc 123");
- preview.setFont(font(initialValue));
- // 根据类别使用不同预览样式
- if (description.contains("节标题")) {
- preview.setStyle("-fx-font-weight: bold;");
- titleLabels.add(preview);
- } else if (description.contains("提示")) {
- preview.setStyle("-fx-text-fill: #666;");
- hintLabels.add(preview);
- } else if (description.contains("等宽")) {
- preview.setFont(Font.font("Consolas", initialValue));
+ /** 重新加载 workutils.config */
+ private void reloadWorkutilsConfig() {
+ if (!configManager.exists()) {
+ showAlert(AlertType.INFORMATION, "workutils.config 不存在,当前使用默认值。");
+ return;
}
+ Properties external = configManager.load();
+ properties.mergeExternal(external);
+ if (onConfigChanged != null) onConfigChanged.run();
+ log.info("已重新加载 workutils.config");
+ }
- // 滑块变化时更新
- slider.valueProperty().addListener((obs, oldVal, newVal) -> {
- int v = (int) Math.round(newVal.doubleValue());
- if (slider.isValueChanging() || Math.abs(newVal.doubleValue() - oldVal.doubleValue()) > 0.1) {
- valLabel.setText(v + "px");
- preview.setFont(description.contains("等宽")
- ? Font.font("Consolas", v)
- : font(v));
- onChanged.accept(v);
+ /** 重新加载 MyBatis 配置(从 mybatis-generator.properties 读取) */
+ private void reloadMyBatisConfig() {
+ Path file = properties.getMybatisGeneratorFilePath();
+ if (!Files.exists(file)) {
+ showAlert(AlertType.INFORMATION, "mybatis-generator.properties 不存在。");
+ return;
+ }
+ try {
+ Properties props = new Properties();
+ try (java.io.InputStream is = new java.io.FileInputStream(file.toFile())) {
+ props.load(is);
}
- });
-
- row.getChildren().addAll(nameLabel, slider, valLabel, preview);
- return row;
- }
-
- /** 将所有字体大小重新应用到已创建的 UI 组件上 */
- private void applyAllFontSizes() {
- for (Label lbl : titleLabels) {
- lbl.setFont(font(fontSectionTitle));
- }
- for (Label lbl : fieldLabels) {
- lbl.setFont(font(fontFieldLabel));
- }
- for (ButtonBase btn : allButtons) {
- btn.setFont(font(fontButton));
- }
- for (Label lbl : hintLabels) {
- lbl.setFont(font(fontBody));
+ properties.getMybatisOverrides().clear();
+ String[] keys = {
+ ExternalConfigManager.KEY_MYBATIS_DRIVER,
+ ExternalConfigManager.KEY_MYBATIS_URL,
+ ExternalConfigManager.KEY_MYBATIS_USERNAME,
+ ExternalConfigManager.KEY_MYBATIS_PASSWORD,
+ ExternalConfigManager.KEY_MYBATIS_MODEL_PKG,
+ ExternalConfigManager.KEY_MYBATIS_DAO_PKG,
+ ExternalConfigManager.KEY_MYBATIS_SQLMAPPER_PKG
+ };
+ for (String key : keys) {
+ String val = props.getProperty(key);
+ if (val != null && !val.isEmpty()) {
+ properties.getMybatisOverrides().put(key, val);
+ }
+ }
+ log.info("已重新加载 MyBatis 配置: {} 项", properties.getMybatisOverrides().size());
+ } catch (IOException e) {
+ showAlert(AlertType.ERROR, "加载 MyBatis 配置失败: " + e.getMessage());
}
}
- private HBox createBottomButtons() {
- Button btnSave = new Button("保存到 workutils.config");
- btnSave.setFont(font(fontButton + 1));
- btnSave.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; -fx-font-weight: bold; "
- + "-fx-padding: 8 20; -fx-background-radius: 4; -fx-cursor: hand;");
- btnSave.setOnAction(e -> saveConfig());
+ // ==================== 字体设置保存 ====================
- Button btnReload = addSmallButton("重新加载");
- btnReload.setStyle("-fx-padding: 8 16; -fx-background-radius: 4; -fx-cursor: hand;");
- btnReload.setOnAction(e -> reloadConfig());
-
- Button btnReset = addSmallButton("重置为默认值");
- btnReset.setStyle("-fx-text-fill: #c0392b; -fx-padding: 8 16; "
- + "-fx-background-radius: 4; -fx-cursor: hand;");
- btnReset.setOnAction(e -> resetConfig());
-
- Region spacer = new Region();
- HBox.setHgrow(spacer, Priority.ALWAYS);
-
- HBox box = new HBox(10, btnSave, btnReload, btnReset, spacer);
- box.setAlignment(Pos.CENTER_LEFT);
- box.setPadding(new Insets(8, 10, 8, 10));
- box.setStyle("-fx-border-color: #e0e0e0; -fx-border-width: 1 0 0 0;");
- return box;
- }
-
- // ==================== 保存/加载/重置 ====================
-
- private void saveConfig() {
+ private void saveFontSettings() {
Properties props = new Properties();
-
- putIfNotEmpty(props, ExternalConfigManager.KEY_BASE_DIR, tfBaseDir.getText());
- putIfNotEmpty(props, ExternalConfigManager.KEY_EXPORT_DIR, tfExportDir.getText());
- putIfNotEmpty(props, ExternalConfigManager.KEY_PROJECT_PATHS, tfProjectPathsFile.getText());
- putIfNotEmpty(props, ExternalConfigManager.KEY_DAMAGED_REPOS, tfDamagedReposFile.getText());
-
- putIfNotEmpty(props, ExternalConfigManager.KEY_MYBATIS_DRIVER, tfDbDriver.getText());
- putIfNotEmpty(props, ExternalConfigManager.KEY_MYBATIS_URL, tfDbUrl.getText());
- putIfNotEmpty(props, ExternalConfigManager.KEY_MYBATIS_USERNAME, tfDbUsername.getText());
- putIfNotEmpty(props, ExternalConfigManager.KEY_MYBATIS_PASSWORD, pfDbPassword.getText());
- putIfNotEmpty(props, ExternalConfigManager.KEY_MYBATIS_MODEL_PKG, tfModelPkg.getText());
- putIfNotEmpty(props, ExternalConfigManager.KEY_MYBATIS_DAO_PKG, tfDaoPkg.getText());
- putIfNotEmpty(props, ExternalConfigManager.KEY_MYBATIS_SQLMAPPER_PKG, tfSqlMapperPkg.getText());
-
- // 字体大小
props.setProperty(ExternalConfigManager.KEY_FONT_SECTION_TITLE, String.valueOf(fontSectionTitle));
props.setProperty(ExternalConfigManager.KEY_FONT_FIELD_LABEL, String.valueOf(fontFieldLabel));
props.setProperty(ExternalConfigManager.KEY_FONT_BUTTON, String.valueOf(fontButton));
props.setProperty(ExternalConfigManager.KEY_FONT_BODY, String.valueOf(fontBody));
props.setProperty(ExternalConfigManager.KEY_FONT_MONO, String.valueOf(fontMono));
- if (props.isEmpty()) {
- showAlert(AlertType.WARNING, "没有需要保存的配置项,请至少填写一项。");
- return;
- }
-
try {
configManager.saveSectioned(props);
- properties.mergeExternal(props);
+ Properties external = configManager.load();
+ properties.mergeExternal(external);
if (onConfigChanged != null) onConfigChanged.run();
- showAlert(AlertType.INFORMATION, "配置已保存到:\n" + configManager.getConfigFile().toAbsolutePath()
+ showAlert(AlertType.INFORMATION, "字体设置已保存到:\n" + configManager.getConfigFile().toAbsolutePath()
+ "\n\n修改已生效,无需重启。");
} catch (Exception ex) {
showAlert(AlertType.ERROR, "保存失败: " + ex.getMessage());
}
}
- private void reloadConfig() {
- if (!configManager.exists()) {
- showAlert(AlertType.INFORMATION, "外部配置文件不存在,当前使用 application.yml 默认值。");
- refreshAllFields();
- return;
- }
- Properties external = configManager.load();
- properties.mergeExternal(external);
- if (onConfigChanged != null) onConfigChanged.run();
- refreshAllFields();
- showAlert(AlertType.INFORMATION, "已从 workutils.config 重新加载配置。");
- }
+ // ==================== 项目路径替换工具 ====================
- private void resetConfig() {
- Alert confirm = new Alert(AlertType.CONFIRMATION);
- confirm.setTitle("确认重置");
- confirm.setHeaderText("重置为 application.yml 默认值?");
- confirm.setContentText("这会丢弃 workutils.config 中的所有自定义配置。");
- confirm.showAndWait().ifPresent(r -> {
- if (r == ButtonType.OK) {
- properties.resetToDefaults();
- configManager.delete();
- if (onConfigChanged != null) onConfigChanged.run();
- refreshAllFields();
- showAlert(AlertType.INFORMATION, "已重置为默认值,外部配置文件已删除。");
- }
- });
- }
-
- // ==================== 字段刷新 ====================
-
- private void refreshAllFields() {
- tfBaseDir.setText(nvl(properties.getBaseDirectory()));
- tfExportDir.setText(nvl(properties.getExportDirectory()));
- tfProjectPathsFile.setText(properties.getProjectPathsFilePath().toAbsolutePath().toString());
- tfDamagedReposFile.setText(properties.getDamagedReposFilePath().toAbsolutePath().toString());
- refreshMyBatisFields();
- refreshProjectTab();
- lblConfigFile.setText("配置文件: " + configManager.getConfigFile().toAbsolutePath()
- + (configManager.exists() ? " (已存在)" : " (不存在)"));
- }
-
- private void refreshMyBatisFields() {
- Properties builtin = loadBuiltinMyBatisProps();
- tfDbDriver.setText(extOr(ExternalConfigManager.KEY_MYBATIS_DRIVER, builtin, "db_driver"));
- tfDbUrl.setText(extOr(ExternalConfigManager.KEY_MYBATIS_URL, builtin, "db_url"));
- tfDbUsername.setText(extOr(ExternalConfigManager.KEY_MYBATIS_USERNAME, builtin, "db_username"));
- pfDbPassword.setText(properties.getMybatisOverrides().getOrDefault(
- ExternalConfigManager.KEY_MYBATIS_PASSWORD, ""));
-
- tfModelPkg.setText(extOr(ExternalConfigManager.KEY_MYBATIS_MODEL_PKG, builtin, "model_package"));
- tfDaoPkg.setText(extOr(ExternalConfigManager.KEY_MYBATIS_DAO_PKG, builtin, "dao_package"));
- tfSqlMapperPkg.setText(extOr(ExternalConfigManager.KEY_MYBATIS_SQLMAPPER_PKG, builtin, "sqlmapper_package"));
- }
-
- private String extOr(String extKey, Properties builtin, String builtinKey) {
- String ext = properties.getMybatisOverrides().get(extKey);
- if (ext != null && !ext.isEmpty()) return ext;
- return builtin.getProperty(builtinKey, "");
- }
-
- private void refreshProjectTab() {
- Path ppFile = properties.getProjectPathsFilePath();
- lblProjectPathsFile.setText("文件: " + ppFile.toAbsolutePath()
- + (Files.exists(ppFile) ? " (存在)" : " (不存在)"));
- loadProjectPaths();
-
- Path dcFile = properties.getDamagedReposFilePath();
- lblDamagedCacheFile.setText("文件: " + dcFile.toAbsolutePath()
- + (Files.exists(dcFile) ? " (存在)" : " (不存在)"));
- loadDamagedCache();
- }
-
- // --- 从内置配置加载 ---
-
- private void loadMyBatisFromClasspath() {
- Properties builtin = loadBuiltinMyBatisProps();
- if (builtin.isEmpty()) {
- showAlert(AlertType.WARNING, "未找到 mybatis-generator.properties 配置文件。"
- + "\n请检查 data/mybatis-generator.properties 是否存在。");
- return;
- }
- tfDbDriver.setText(builtin.getProperty("db_driver", ""));
- tfDbUrl.setText(builtin.getProperty("db_url", ""));
- tfDbUsername.setText(builtin.getProperty("db_username", ""));
- pfDbPassword.setText(builtin.getProperty("db_password", ""));
- tfModelPkg.setText(builtin.getProperty("model_package", ""));
- tfDaoPkg.setText(builtin.getProperty("dao_package", ""));
- tfSqlMapperPkg.setText(builtin.getProperty("sqlmapper_package", ""));
- showAlert(AlertType.INFORMATION, "已从内置配置加载。点击「保存到 workutils.config」可导出。");
- }
-
- private Properties loadBuiltinMyBatisProps() {
- Properties props = new Properties();
- // 1. 先从 classpath 加载内置默认值
- try (InputStream is = getClass().getClassLoader()
- .getResourceAsStream("mybatis-generator.properties")) {
- if (is != null) {
- props.load(is);
- }
- } catch (IOException ignored) {
- }
- // 2. 再用 data/ 外部配置覆盖
- Path dataFile = properties.getMybatisGeneratorFilePath();
- if (Files.exists(dataFile)) {
- try (InputStream is = new FileInputStream(dataFile.toFile())) {
- Properties externalProps = new Properties();
- externalProps.load(is);
- props.putAll(externalProps);
- } catch (IOException ignored) {
- }
- }
- return props;
- }
-
- // --- 项目路径 ---
-
- private void loadProjectPaths() {
+ /** 从文件加载项目路径到预览 TextArea */
+ private void loadProjectPathsToPreview() {
+ if (taProjectPathsPreview == null) return;
Path file = properties.getProjectPathsFilePath();
if (Files.exists(file)) {
try {
- taProjectPaths.setText(String.join("\n", Files.readAllLines(file)));
+ taProjectPathsPreview.setText(String.join("\n", Files.readAllLines(file)));
} catch (IOException e) {
- taProjectPaths.setText("# 读取失败: " + e.getMessage());
+ taProjectPathsPreview.setText("# 读取失败: " + e.getMessage());
}
} else {
- taProjectPaths.setText("# 文件不存在\n# 格式: 项目路径\n# 格式: 项目路径=BASE_URL");
+ taProjectPathsPreview.setText("# 文件不存在\n# 格式: 项目路径\n# 格式: 项目路径=BASE_URL");
}
}
- private void saveProjectPaths() {
+ private void reloadProjectPathsPreview() {
+ if (taProjectPathsPreview != null) {
+ loadProjectPathsToPreview();
+ }
+ }
+
+ /** 保存替换后的内容到文件 */
+ private void saveProjectPathsFromPreview() {
Path file = properties.getProjectPathsFilePath();
try {
Files.createDirectories(file.getParent());
- Files.write(file, taProjectPaths.getText().getBytes("UTF-8"));
- showAlert(AlertType.INFORMATION, "项目路径文件已保存。");
+ Files.write(file, taProjectPathsPreview.getText().getBytes("UTF-8"));
} catch (IOException e) {
showAlert(AlertType.ERROR, "保存失败: " + e.getMessage());
}
}
- // --- 损坏缓存 ---
-
- private void loadDamagedCache() {
- Path file = properties.getDamagedReposFilePath();
- if (Files.exists(file)) {
- try {
- List lines = Files.readAllLines(file);
- taDamagedCache.setText(lines.isEmpty() ? "(空)" : String.join("\n", lines));
- } catch (IOException e) {
- taDamagedCache.setText("读取失败: " + e.getMessage());
- }
- } else {
- taDamagedCache.setText("(文件不存在,尚未产生损坏记录)");
- }
- }
-
- private void clearDamagedCache() {
- Alert confirm = new Alert(AlertType.CONFIRMATION);
- confirm.setTitle("确认清理");
- confirm.setHeaderText("清除损坏仓库缓存?");
- confirm.setContentText("缓存清除后,下次同步时将重新扫描所有仓库。");
- confirm.showAndWait().ifPresent(r -> {
- if (r == ButtonType.OK) {
- try {
- Files.deleteIfExists(properties.getDamagedReposFilePath());
- taDamagedCache.setText("(已清理)");
- showAlert(AlertType.INFORMATION, "损坏仓库缓存已清理。");
- } catch (IOException e) {
- showAlert(AlertType.ERROR, "清理失败: " + e.getMessage());
- }
- }
- });
- }
-
- // ==================== 工具方法 ====================
-
- private Font font(int size) {
- return Font.font(size);
- }
-
- /** 添加节标题 Label,自动加入 titleLabels 列表 */
- private Label addTitleLabel(GridPane grid, int row, String text, int colSpan) {
- Label lbl = new Label(text);
- lbl.setStyle("-fx-font-weight: bold; -fx-font-size: " + fontSectionTitle + "px; -fx-padding: 0 0 8 0;");
- titleLabels.add(lbl);
- grid.add(lbl, 0, row, colSpan, 1);
- return lbl;
- }
-
- /** 添加小按钮,自动加入 allButtons 列表 */
- private Button addSmallButton(String text) {
- Button btn = new Button(text);
- btn.setFont(font(fontButton));
- allButtons.add(btn);
- return btn;
- }
-
- private TextField addBrowseRow(GridPane grid, int row, String label, String initialValue) {
- TextField tf = new TextField(initialValue != null ? initialValue : "");
- HBox.setHgrow(tf, Priority.ALWAYS);
- GridPane.setHgrow(tf, Priority.ALWAYS);
- textInputs.add(tf);
-
- Label lbl = new Label(label);
- lbl.setFont(font(fontFieldLabel));
- fieldLabels.add(lbl);
-
- Button btn = new Button("浏览...");
- btn.setFont(font(fontButton));
- allButtons.add(btn);
- btn.setOnAction(e -> browseDir(tf));
-
- grid.add(lbl, 0, row);
- grid.add(tf, 1, row);
- grid.add(btn, 2, row);
- return tf;
- }
-
- private TextField addField(GridPane grid, int row, String label, String initialValue) {
- TextField tf = new TextField(initialValue);
- HBox.setHgrow(tf, Priority.ALWAYS);
- GridPane.setHgrow(tf, Priority.ALWAYS);
- textInputs.add(tf);
- addLabeled(grid, row, label, tf);
- return tf;
- }
-
- private void addLabeled(GridPane grid, int row, String label, Control field) {
- Label lbl = new Label(label);
- lbl.setFont(font(fontFieldLabel));
- fieldLabels.add(lbl);
- grid.add(lbl, 0, row);
- grid.add(field, 1, row);
- }
-
- private void browseDir(TextField target) {
- DirectoryChooser chooser = new DirectoryChooser();
- chooser.setTitle("选择目录");
- String current = target.getText();
- if (current != null && !current.isEmpty()) {
- File f = new File(current);
- if (f.exists()) {
- if (f.isFile()) f = f.getParentFile();
- chooser.setInitialDirectory(f);
- }
- }
- File selected = chooser.showDialog(stage);
- if (selected != null) {
- target.setText(selected.getAbsolutePath());
- }
- }
-
- // ==================== 项目路径替换工具 ====================
-
private VBox createReplaceTool() {
VBox box = new VBox(8);
box.setPadding(new Insets(8, 0, 0, 0));
@@ -786,7 +595,25 @@ public class SettingsWindow {
btnApplyAccount.setOnAction(e -> applyReplaceAccount());
accountRow.getChildren().addAll(lblAccount, tfReplaceAccount, btnDetectAccount, btnApplyAccount);
- box.getChildren().addAll(parentRow, accountRow);
+ // 预览区
+ taProjectPathsPreview = new TextArea();
+ taProjectPathsPreview.setPrefRowCount(6);
+ taProjectPathsPreview.setStyle("-fx-font-size: " + fontMono + "px;");
+ taProjectPathsPreview.setPromptText("# 每行一个项目\n# 格式: 项目路径\n# 格式: 项目路径=BASE_URL\n# # 开头为注释");
+ textInputs.add(taProjectPathsPreview);
+
+ HBox previewBtns = new HBox(10);
+ Button btnLoadFile = addSmallButton("从文件加载");
+ btnLoadFile.setOnAction(e -> loadProjectPathsToPreview());
+ Button btnSaveFile = addSmallButton("保存到文件");
+ btnSaveFile.setOnAction(e -> {
+ saveProjectPathsFromPreview();
+ showAlert(AlertType.INFORMATION, "项目路径文件已保存。");
+ });
+ previewBtns.getChildren().addAll(btnLoadFile, btnSaveFile);
+
+ box.getChildren().addAll(parentRow, accountRow, new Separator(), taProjectPathsPreview, previewBtns);
+ loadProjectPathsToPreview();
return box;
}
@@ -797,13 +624,11 @@ public class SettingsWindow {
showAlert(AlertType.WARNING, "项目路径为空,无法检测父目录。");
return;
}
- // 取 / 分割后的第一个非空部分作为父目录,如 D:/bestpay-srdcloud/ft-xxx 中的 D:/bestpay-srdcloud
int slashIdx = firstPath.indexOf('/');
if (slashIdx < 0) {
tfReplaceParentDir.setText(firstPath);
return;
}
- // 找到第一个 / 后,继续找 /ft-xxx 的 /,即项目目录前一个 /
int nextSlash = firstPath.indexOf('/', slashIdx + 1);
if (nextSlash > 0) {
tfReplaceParentDir.setText(firstPath.substring(0, nextSlash));
@@ -822,7 +647,6 @@ public class SettingsWindow {
if (f.exists()) {
chooser.setInitialDirectory(f);
} else {
- // 尝试取已存在的上级目录
File parent = f.getParentFile();
while (parent != null && !parent.exists()) {
parent = parent.getParentFile();
@@ -845,11 +669,9 @@ public class SettingsWindow {
showAlert(AlertType.WARNING, "未找到带远程 URL 的项目路径,请确保路径格式为 path=URL。");
return;
}
- // 格式: path=https://account@code.srdcloud.cn/...
int eqIdx = firstLine.indexOf('=');
if (eqIdx < 0) return;
String url = firstLine.substring(eqIdx + 1).trim();
- // 提取 @ 前面的账号:https://xxx → 取 // 和 @ 之间的部分
int atIdx = url.indexOf('@');
int protoIdx = url.indexOf("://");
if (atIdx > 0 && protoIdx > 0) {
@@ -865,7 +687,6 @@ public class SettingsWindow {
showAlert(AlertType.WARNING, "请输入新的父目录。");
return;
}
- // 检测当前父目录
String firstPath = findFirstProjectPath();
if (firstPath == null) {
showAlert(AlertType.WARNING, "项目路径为空。");
@@ -879,7 +700,6 @@ public class SettingsWindow {
int nextSlash = firstPath.indexOf('/', slashIdx + 1);
String oldParent = (nextSlash > 0) ? firstPath.substring(0, nextSlash) : firstPath;
- // 规范化:去掉末尾 /
if (newParent.endsWith("/") || newParent.endsWith("\\")) {
newParent = newParent.substring(0, newParent.length() - 1);
}
@@ -887,7 +707,7 @@ public class SettingsWindow {
oldParent = oldParent.substring(0, oldParent.length() - 1);
}
- int count = replaceInTextArea(oldParent, newParent);
+ int count = replaceInPreviewTextArea(oldParent, newParent);
showAlert(AlertType.INFORMATION,
"已将 " + count + " 条路径中的父目录从\n" + oldParent + "\n替换为\n" + newParent);
}
@@ -899,7 +719,6 @@ public class SettingsWindow {
showAlert(AlertType.WARNING, "请输入新的账号。");
return;
}
- // 检测当前账号
String firstLine = findFirstLineWithUrl();
if (firstLine == null) {
showAlert(AlertType.WARNING, "未找到带远程 URL 的项目路径。");
@@ -916,14 +735,14 @@ public class SettingsWindow {
}
String oldAccount = url.substring(protoIdx + 3, atIdx);
- int count = replaceInTextArea(oldAccount, newAccount);
+ int count = replaceInPreviewTextArea(oldAccount, newAccount);
showAlert(AlertType.INFORMATION,
"已将 " + count + " 条URL中的账号从\n" + oldAccount + "\n替换为\n" + newAccount);
}
- /** 在 TextArea 中替换文本,返回替换行数 */
- private int replaceInTextArea(String oldStr, String newStr) {
- String text = taProjectPaths.getText();
+ /** 在预览 TextArea 中替换文本,返回替换行数 */
+ private int replaceInPreviewTextArea(String oldStr, String newStr) {
+ String text = taProjectPathsPreview.getText();
int count = 0;
String[] lines = text.split("\n");
StringBuilder sb = new StringBuilder();
@@ -935,13 +754,13 @@ public class SettingsWindow {
sb.append(line).append("\n");
}
}
- taProjectPaths.setText(sb.toString());
+ taProjectPathsPreview.setText(sb.toString());
return count;
}
/** 找到第一条有效项目路径(只取路径部分,不含 URL) */
private String findFirstProjectPath() {
- String text = taProjectPaths.getText();
+ String text = taProjectPathsPreview.getText();
if (text == null || text.trim().isEmpty()) return null;
for (String line : text.split("\n")) {
line = line.trim();
@@ -957,7 +776,7 @@ public class SettingsWindow {
/** 找到第一条同时包含路径和 URL 的行 */
private String findFirstLineWithUrl() {
- String text = taProjectPaths.getText();
+ String text = taProjectPathsPreview.getText();
if (text == null || text.trim().isEmpty()) return null;
for (String line : text.split("\n")) {
line = line.trim();
@@ -971,22 +790,110 @@ public class SettingsWindow {
return null;
}
- private void openInNotepad(Path file) {
+ // ==================== 打开文件编辑器 ====================
+
+ /**
+ * 用系统默认编辑器打开文件
+ * Windows 优先使用 Desktop.edit(),fallback 到 notepad.exe
+ */
+ private void openFileInEditor(Path file) {
try {
if (!Files.exists(file)) {
Files.createDirectories(file.getParent());
Files.createFile(file);
+ log.info("自动创建文件: {}", file.toAbsolutePath());
+ }
+ if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.EDIT)) {
+ Desktop.getDesktop().edit(file.toFile());
+ } else {
+ // Windows fallback
+ Runtime.getRuntime().exec(new String[]{"notepad.exe", file.toAbsolutePath().toString()});
}
- Runtime.getRuntime().exec(new String[]{"notepad.exe", file.toAbsolutePath().toString()});
} catch (IOException e) {
- showAlert(AlertType.ERROR, "无法打开记事本: " + e.getMessage());
+ log.error("打开文件失败: {}", file.toAbsolutePath(), e);
+ showAlert(AlertType.ERROR, "打开文件失败: " + e.getMessage());
}
}
- private void putIfNotEmpty(Properties props, String key, String value) {
- if (value != null && !value.trim().isEmpty()) {
- props.setProperty(key, value.trim());
+ // ==================== 字体相关 ====================
+
+ private HBox createFontRow(String description, int initialValue,
+ java.util.function.IntConsumer onChanged) {
+ HBox row = new HBox(12);
+ row.setAlignment(Pos.CENTER_LEFT);
+ row.setPadding(new Insets(6, 0, 6, 0));
+
+ Label nameLabel = new Label(description);
+ nameLabel.setPrefWidth(320);
+ nameLabel.setFont(font(14));
+
+ Slider slider = new Slider(16, 32, initialValue);
+ slider.setPrefWidth(180);
+ slider.setMajorTickUnit(2);
+ slider.setMinorTickCount(1);
+ slider.setShowTickMarks(true);
+ slider.setShowTickLabels(false);
+ slider.setSnapToTicks(true);
+
+ Label valLabel = new Label(String.valueOf(initialValue) + "px");
+ valLabel.setPrefWidth(40);
+ valLabel.setFont(font(14));
+ valLabel.setStyle("-fx-font-weight: bold; -fx-text-fill: #4a90d9;");
+
+ Label preview = new Label("预览 Abc 123");
+ preview.setFont(font(initialValue));
+ if (description.contains("节标题")) {
+ preview.setStyle("-fx-font-weight: bold;");
+ titleLabels.add(preview);
+ } else if (description.contains("提示")) {
+ preview.setStyle("-fx-text-fill: #666;");
+ hintLabels.add(preview);
+ } else if (description.contains("等宽")) {
+ preview.setFont(Font.font("Consolas", initialValue));
}
+
+ slider.valueProperty().addListener((obs, oldVal, newVal) -> {
+ int v = (int) Math.round(newVal.doubleValue());
+ if (slider.isValueChanging() || Math.abs(newVal.doubleValue() - oldVal.doubleValue()) > 0.1) {
+ valLabel.setText(v + "px");
+ preview.setFont(description.contains("等宽")
+ ? Font.font("Consolas", v)
+ : font(v));
+ onChanged.accept(v);
+ }
+ });
+
+ row.getChildren().addAll(nameLabel, slider, valLabel, preview);
+ return row;
+ }
+
+ /** 将所有字体大小重新应用到已创建的 UI 组件上 */
+ private void applyAllFontSizes() {
+ for (Label lbl : titleLabels) {
+ lbl.setFont(font(fontSectionTitle));
+ }
+ for (Label lbl : fieldLabels) {
+ lbl.setFont(font(fontFieldLabel));
+ }
+ for (ButtonBase btn : allButtons) {
+ btn.setFont(font(fontButton));
+ }
+ for (Label lbl : hintLabels) {
+ lbl.setFont(font(fontBody));
+ }
+ }
+
+ // ==================== 工具方法 ====================
+
+ private Font font(int size) {
+ return Font.font(size);
+ }
+
+ private Button addSmallButton(String text) {
+ Button btn = new Button(text);
+ btn.setFont(font(fontButton));
+ allButtons.add(btn);
+ return btn;
}
private void showAlert(AlertType type, String msg) {
@@ -996,8 +903,4 @@ public class SettingsWindow {
alert.setContentText(msg);
alert.showAndWait();
}
-
- private static String nvl(String s) {
- return s == null ? "" : s;
- }
}
diff --git a/src/main/java/com/example/workutils/system/config/WorkUtilsProperties.java b/src/main/java/com/example/workutils/system/config/WorkUtilsProperties.java
index 7ec9123..1ae9699 100644
--- a/src/main/java/com/example/workutils/system/config/WorkUtilsProperties.java
+++ b/src/main/java/com/example/workutils/system/config/WorkUtilsProperties.java
@@ -69,11 +69,11 @@ public class WorkUtilsProperties {
private final Map mybatisOverrides = new LinkedHashMap<>();
// ---- 字体大小(显示设置) ----
- private int fontSectionTitle = 20; // 节标题
- private int fontFieldLabel = 18; // 字段标签
- private int fontButton = 18; // 按钮
- private int fontBody = 18; // 正文/提示
- private int fontMono = 18; // 等宽/代码
+ private int fontSectionTitle = 24; // 节标题
+ private int fontFieldLabel = 20; // 字段标签
+ private int fontButton = 20; // 按钮
+ private int fontBody = 20; // 正文/提示
+ private int fontMono = 20; // 等宽/代码
// ==================== 外部配置加载 ====================
@@ -167,11 +167,11 @@ public class WorkUtilsProperties {
projectPathsFile = defaults.get(ExternalConfigManager.KEY_PROJECT_PATHS);
damagedReposFile = defaults.get(ExternalConfigManager.KEY_DAMAGED_REPOS);
mybatisOverrides.clear();
- fontSectionTitle = 20;
- fontFieldLabel = 18;
- fontButton = 18;
- fontBody = 18;
- fontMono = 18;
+ fontSectionTitle = 24;
+ fontFieldLabel = 20;
+ fontButton = 20;
+ fontBody = 20;
+ fontMono = 20;
log.info("配置已重置为默认值");
}
diff --git a/src/main/java/com/example/workutils/system/plugin/PluginManager.java b/src/main/java/com/example/workutils/system/plugin/PluginManager.java
index e3bc97a..bce8e7a 100644
--- a/src/main/java/com/example/workutils/system/plugin/PluginManager.java
+++ b/src/main/java/com/example/workutils/system/plugin/PluginManager.java
@@ -106,8 +106,8 @@ public class PluginManager {
break;
}
- // 'S' / 's' 字符(readChoiceOrChar 将单字母转成负ASCII码)
- if (choice == -'S') {
+ // '?' 字符(readChoiceOrChar 将单字母转成负ASCII码)
+ if (choice == -'?') {
settingsService.showSettings(scanner, pluginContext::resetConfigChecks);
// JavaFX 后台线程在 Windows 上可能污染 System.in,
// 清理残留字节并重建 Scanner,确保后续输入正常
@@ -126,7 +126,7 @@ public class PluginManager {
PluginDescriptor desc = enabledPlugins.get(i).getDescriptor();
System.out.println((i + 1) + ". " + desc.getName() + " [v" + desc.getVersion() + "]");
}
- System.out.println("S. 系统设置");
+ System.out.println("?. 系统设置");
System.out.println(EXIT_CODE + ". 退出系统");
}
@@ -145,7 +145,7 @@ public class PluginManager {
System.out.println("插件 [" + desc.getName() + "] 配置异常:");
System.out.println(configError);
System.out.println();
- System.out.println("请进入「系统设置(S)」进行配置后再使用。");
+ System.out.println("请进入「系统设置(?)」进行配置后再使用。");
System.out.println("==============================");
return;
}
diff --git a/src/main/resources/templates/datasources.json b/src/main/resources/templates/datasources.json
index 89299b8..4118ec7 100644
--- a/src/main/resources/templates/datasources.json
+++ b/src/main/resources/templates/datasources.json
@@ -1,23 +1,23 @@
[
{
- "name": "开发环境",
+ "name": "本地环境-loacalhost-db",
"driver": "com.mysql.jdbc.Driver",
"url": "jdbc:mysql://localhost:3306/disburse?useUnicode=true&characterEncoding=utf8&useSSL=false",
"username": "root",
"password": "1Qaz@4772"
},
{
- "name": "SIT环境",
- "driver": "com.mysql.jdbc.Driver",
- "url": "jdbc:mysql://sit-server:3306/disburse?useUnicode=true&characterEncoding=utf8&useSSL=false",
- "username": "sit_user",
- "password": ""
+ "name": "ft_loancapital_core_db",
+ "driver": "com.oceanbase.jdbc.Driver",
+ "url": "jdbc:oceanbase://ob4x-xyd.bestpay.dev-db:2883/ft_loancapital_core_db?rewriteBatchedStatements=true&allowMultiQueries=true&useLocalSessionState=true&useUnicode=true&characterEncoding=utf-8&socketTimeout=6000000",
+ "username": "ft_loancapital_core_app@xydcs_gcz_tnt#ob4x_cluster_xyd",
+ "password": "FT_LOANCAPITAL_CORE@123"
},
{
- "name": "生产环境(只读)",
- "driver": "com.mysql.jdbc.Driver",
- "url": "jdbc:mysql://prod-server:3306/disburse?useUnicode=true&characterEncoding=utf8&useSSL=false",
- "username": "readonly",
- "password": ""
+ "name": "cf_loan_core_db",
+ "driver": "com.oceanbase.jdbc.Driver",
+ "url": "jdbc:oceanbase://ob4x-ky.bestpay.46-db:2883/cf_loan_core_db?useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&socketTimeout=30000&connectTimeout=10000",
+ "username": "cf_loan_core_app@obtest_tnt#ob4x_cluster_test",
+ "password": "CF_LOAN_CORE@123"
}
]