现在插件管理
This commit is contained in:
@@ -113,6 +113,22 @@ public class WorkUtilsApplication implements CommandLineRunner {
|
|||||||
Theme.Mode mode = Theme.Mode.LIGHT;
|
Theme.Mode mode = Theme.Mode.LIGHT;
|
||||||
Platform.runLater(() -> Theme.applyTheme(mode));
|
Platform.runLater(() -> Theme.applyTheme(mode));
|
||||||
|
|
||||||
|
// 设置导航栏刷新回调(插件管理中启用/禁用后刷新侧边栏)
|
||||||
|
settingsService.setNavItemsRefresher(items -> Platform.runLater(() -> {
|
||||||
|
if (mainWindow != null) {
|
||||||
|
mainWindow.refreshNavItems(items);
|
||||||
|
// 重新绑定插件面板
|
||||||
|
for (Plugin plugin : pluginManager.getEnabledPlugins()) {
|
||||||
|
PluginDescriptor desc = plugin.getDescriptor();
|
||||||
|
VisualPluginFactory factory = plugin.getVisualFactory();
|
||||||
|
Parent pane = factory != null ? factory.getEmbeddedPane() : null;
|
||||||
|
if (pane != null) {
|
||||||
|
mainWindow.embedPlugin(desc.getId(), desc.getIcon(), desc.getName(), pane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
List<MainWindow.NavItem> navItems = buildNavItems();
|
List<MainWindow.NavItem> navItems = buildNavItems();
|
||||||
|
|
||||||
CountDownLatch windowClosed = new CountDownLatch(1);
|
CountDownLatch windowClosed = new CountDownLatch(1);
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package com.example.workutils.system.config;
|
package com.example.workutils.system.config;
|
||||||
|
|
||||||
|
import com.example.workutils.system.plugin.PluginManager;
|
||||||
|
import com.example.workutils.system.ui.MainWindow;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统设置服务 —— 通过可视化 GUI 或控制台修改配置
|
* 系统设置服务 —— 通过可视化 GUI 或控制台修改配置
|
||||||
@@ -23,12 +27,22 @@ public class SettingsService {
|
|||||||
private final WorkUtilsProperties properties;
|
private final WorkUtilsProperties properties;
|
||||||
private final ExternalConfigManager configManager;
|
private final ExternalConfigManager configManager;
|
||||||
private final DatasourceConfigManager datasourceConfigManager;
|
private final DatasourceConfigManager datasourceConfigManager;
|
||||||
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
|
/** 导航栏刷新回调(插件启用/禁用时刷新侧边栏) */
|
||||||
|
private Consumer<List<MainWindow.NavItem>> navItemsRefresher;
|
||||||
|
|
||||||
|
/** 设置导航栏刷新回调 */
|
||||||
|
public void setNavItemsRefresher(Consumer<List<MainWindow.NavItem>> refresher) {
|
||||||
|
this.navItemsRefresher = refresher;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建设置窗口(不自动 showAndWait),供内嵌模式使用
|
* 创建设置窗口(不自动 showAndWait),供内嵌模式使用
|
||||||
*/
|
*/
|
||||||
public SettingsWindow createSettingsWindow(Runnable onConfigChanged) {
|
public SettingsWindow createSettingsWindow(Runnable onConfigChanged) {
|
||||||
return new SettingsWindow(properties, configManager, datasourceConfigManager, onConfigChanged);
|
return new SettingsWindow(properties, configManager, datasourceConfigManager,
|
||||||
|
pluginManager, onConfigChanged, navItemsRefresher);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +52,8 @@ public class SettingsService {
|
|||||||
*/
|
*/
|
||||||
public void showSettings(Scanner scanner, Runnable onConfigChanged) {
|
public void showSettings(Scanner scanner, Runnable onConfigChanged) {
|
||||||
try {
|
try {
|
||||||
SettingsWindow window = new SettingsWindow(properties, configManager, datasourceConfigManager, onConfigChanged);
|
SettingsWindow window = new SettingsWindow(properties, configManager, datasourceConfigManager,
|
||||||
|
pluginManager, onConfigChanged, null);
|
||||||
window.showAndWait();
|
window.showAndWait();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("打开设置窗口失败,回退到控制台模式", e);
|
log.error("打开设置窗口失败,回退到控制台模式", e);
|
||||||
@@ -48,4 +63,13 @@ public class SettingsService {
|
|||||||
System.out.println("编辑后重启程序即可生效。");
|
System.out.println("编辑后重启程序即可生效。");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知导航栏刷新(供外部调用)
|
||||||
|
*/
|
||||||
|
public void refreshNavItems(List<MainWindow.NavItem> items) {
|
||||||
|
if (navItemsRefresher != null) {
|
||||||
|
navItemsRefresher.accept(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package com.example.workutils.system.config;
|
package com.example.workutils.system.config;
|
||||||
|
|
||||||
|
import com.example.workutils.system.plugin.Plugin;
|
||||||
|
import com.example.workutils.system.plugin.PluginDescriptor;
|
||||||
|
import com.example.workutils.system.plugin.PluginManager;
|
||||||
|
import com.example.workutils.system.plugin.PluginState;
|
||||||
import com.example.workutils.system.support.JfxBootstrap;
|
import com.example.workutils.system.support.JfxBootstrap;
|
||||||
import com.example.workutils.system.support.SystemOpenHelper;
|
import com.example.workutils.system.support.SystemOpenHelper;
|
||||||
|
import com.example.workutils.system.ui.MainWindow;
|
||||||
import com.example.workutils.system.ui.Theme;
|
import com.example.workutils.system.ui.Theme;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
@@ -12,6 +17,8 @@ import javafx.scene.Scene;
|
|||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.Alert.AlertType;
|
import javafx.scene.control.Alert.AlertType;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import javafx.stage.DirectoryChooser;
|
import javafx.stage.DirectoryChooser;
|
||||||
@@ -23,7 +30,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Properties;
|
import java.util.*;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,8 +44,12 @@ public class SettingsWindow {
|
|||||||
private final WorkUtilsProperties properties;
|
private final WorkUtilsProperties properties;
|
||||||
private final ExternalConfigManager configManager;
|
private final ExternalConfigManager configManager;
|
||||||
private final DatasourceConfigManager datasourceConfigManager;
|
private final DatasourceConfigManager datasourceConfigManager;
|
||||||
|
private final PluginManager pluginManager;
|
||||||
private final Runnable onConfigChanged;
|
private final Runnable onConfigChanged;
|
||||||
|
|
||||||
|
/** 导航栏刷新回调 */
|
||||||
|
private final java.util.function.Consumer<List<MainWindow.NavItem>> navItemsRefresher;
|
||||||
|
|
||||||
private Stage stage;
|
private Stage stage;
|
||||||
private Scene scene;
|
private Scene scene;
|
||||||
|
|
||||||
@@ -65,11 +76,15 @@ public class SettingsWindow {
|
|||||||
public SettingsWindow(WorkUtilsProperties properties,
|
public SettingsWindow(WorkUtilsProperties properties,
|
||||||
ExternalConfigManager configManager,
|
ExternalConfigManager configManager,
|
||||||
DatasourceConfigManager datasourceConfigManager,
|
DatasourceConfigManager datasourceConfigManager,
|
||||||
Runnable onConfigChanged) {
|
PluginManager pluginManager,
|
||||||
|
Runnable onConfigChanged,
|
||||||
|
java.util.function.Consumer<List<MainWindow.NavItem>> navItemsRefresher) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
this.configManager = configManager;
|
this.configManager = configManager;
|
||||||
this.datasourceConfigManager = datasourceConfigManager;
|
this.datasourceConfigManager = datasourceConfigManager;
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
this.onConfigChanged = onConfigChanged;
|
this.onConfigChanged = onConfigChanged;
|
||||||
|
this.navItemsRefresher = navItemsRefresher;
|
||||||
this.fontH1 = properties.getFontH1();
|
this.fontH1 = properties.getFontH1();
|
||||||
this.fontH2 = properties.getFontH2();
|
this.fontH2 = properties.getFontH2();
|
||||||
this.fontH3 = properties.getFontH3();
|
this.fontH3 = properties.getFontH3();
|
||||||
@@ -138,6 +153,7 @@ public class SettingsWindow {
|
|||||||
createProjectTab(),
|
createProjectTab(),
|
||||||
createDatasourceTab(),
|
createDatasourceTab(),
|
||||||
createDamagedCacheTab(),
|
createDamagedCacheTab(),
|
||||||
|
createPluginTab(),
|
||||||
createDisplayTab()
|
createDisplayTab()
|
||||||
);
|
);
|
||||||
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
|
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
|
||||||
@@ -596,6 +612,172 @@ public class SettingsWindow {
|
|||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== Tab 6: 插件管理 ====================
|
||||||
|
|
||||||
|
private Tab createPluginTab() {
|
||||||
|
VBox root = new VBox(10);
|
||||||
|
root.setPadding(new Insets(15));
|
||||||
|
|
||||||
|
Label title = new Label("插件管理");
|
||||||
|
title.setFont(Theme.bold(fontH2));
|
||||||
|
title.getStyleClass().add(Theme.CSS_SECTION_TITLE);
|
||||||
|
titleLabels.add(title);
|
||||||
|
root.getChildren().add(title);
|
||||||
|
|
||||||
|
Label hint = new Label("启用或禁用功能模块。禁用后对应菜单项将从左侧导航栏隐藏,重启后生效。");
|
||||||
|
hint.setFont(Theme.base(fontBody));
|
||||||
|
hint.getStyleClass().add(Theme.CSS_HINT_TEXT);
|
||||||
|
hintLabels.add(hint);
|
||||||
|
root.getChildren().add(hint);
|
||||||
|
|
||||||
|
// 插件列表
|
||||||
|
VBox pluginList = new VBox(4);
|
||||||
|
|
||||||
|
List<PluginDescriptor> allDescriptors = pluginManager.listAllPluginDescriptors();
|
||||||
|
Set<String> enabledIds = new HashSet<>();
|
||||||
|
for (Plugin p : pluginManager.getEnabledPlugins()) {
|
||||||
|
enabledIds.add(p.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PluginDescriptor desc : allDescriptors) {
|
||||||
|
String id = desc.getId();
|
||||||
|
boolean isEnabled = enabledIds.contains(id) || pluginManager.getPluginState(id) == PluginState.ACTIVE;
|
||||||
|
|
||||||
|
HBox row = new HBox(10);
|
||||||
|
row.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
row.setPadding(new Insets(6, 0, 6, 0));
|
||||||
|
row.setStyle("-fx-background-color: " + Theme.toWeb(Theme.BG_CARD)
|
||||||
|
+ "; -fx-background-radius: " + Theme.RADIUS_SM + "; -fx-padding: 8 12;");
|
||||||
|
|
||||||
|
// 图标 + 名称
|
||||||
|
Label iconLabel = new Label(desc.getIcon());
|
||||||
|
iconLabel.setFont(Theme.base(16));
|
||||||
|
Label nameLabel = new Label(desc.getName());
|
||||||
|
nameLabel.setFont(Theme.bold(fontBody));
|
||||||
|
nameLabel.setPrefWidth(160);
|
||||||
|
Label descLabel = new Label(desc.getDescription());
|
||||||
|
descLabel.setFont(Theme.base(fontSmall));
|
||||||
|
descLabel.getStyleClass().add(Theme.CSS_HINT_TEXT);
|
||||||
|
descLabel.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
|
||||||
|
Region spacer = new Region();
|
||||||
|
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||||
|
|
||||||
|
// 启用/禁用开关
|
||||||
|
ToggleButton toggleBtn = new ToggleButton(isEnabled ? "已启用" : "已禁用");
|
||||||
|
toggleBtn.setSelected(isEnabled);
|
||||||
|
toggleBtn.setFont(font(fontBtnSm));
|
||||||
|
toggleBtn.getStyleClass().add(Theme.CSS_TOGGLE_BTN);
|
||||||
|
allButtons.add(toggleBtn);
|
||||||
|
|
||||||
|
final String pluginId = id;
|
||||||
|
toggleBtn.setOnAction(e -> {
|
||||||
|
if (toggleBtn.isSelected()) {
|
||||||
|
pluginManager.enablePlugin(pluginId);
|
||||||
|
toggleBtn.setText("已启用");
|
||||||
|
} else {
|
||||||
|
pluginManager.disablePlugin(pluginId);
|
||||||
|
toggleBtn.setText("已禁用");
|
||||||
|
}
|
||||||
|
// 持久化到配置文件
|
||||||
|
savePluginToggleState();
|
||||||
|
// 刷新导航栏
|
||||||
|
refreshMainWindowNavItems();
|
||||||
|
});
|
||||||
|
|
||||||
|
row.getChildren().addAll(iconLabel, nameLabel, descLabel, spacer, toggleBtn);
|
||||||
|
pluginList.getChildren().add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollPane scrollPane = new ScrollPane(pluginList);
|
||||||
|
scrollPane.setFitToWidth(true);
|
||||||
|
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
|
||||||
|
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||||
|
|
||||||
|
root.getChildren().add(scrollPane);
|
||||||
|
|
||||||
|
// 操作按钮行
|
||||||
|
HBox btnRow = new HBox(10);
|
||||||
|
btnRow.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
btnRow.getChildren().add(new Separator());
|
||||||
|
|
||||||
|
Button btnSave = new Button("保存配置并刷新菜单");
|
||||||
|
btnSave.setFont(font(fontBtn));
|
||||||
|
btnSave.getStyleClass().add(Theme.CSS_BTN_SUCCESS);
|
||||||
|
allButtons.add(btnSave);
|
||||||
|
btnSave.setOnAction(e -> {
|
||||||
|
savePluginToggleState();
|
||||||
|
refreshMainWindowNavItems();
|
||||||
|
showAlert(AlertType.INFORMATION, "插件配置已保存,左侧导航栏已刷新。");
|
||||||
|
});
|
||||||
|
btnRow.getChildren().add(btnSave);
|
||||||
|
|
||||||
|
root.getChildren().add(btnRow);
|
||||||
|
|
||||||
|
Tab tab = new Tab("插件管理");
|
||||||
|
tab.setContent(root);
|
||||||
|
tab.setClosable(false);
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存插件启用/禁用状态到 workutils.config */
|
||||||
|
private void savePluginToggleState() {
|
||||||
|
try {
|
||||||
|
Properties props = configManager.load();
|
||||||
|
|
||||||
|
List<String> disabledList = new ArrayList<>();
|
||||||
|
for (PluginDescriptor desc : pluginManager.listAllPluginDescriptors()) {
|
||||||
|
PluginState state = pluginManager.getPluginState(desc.getId());
|
||||||
|
if (state == PluginState.DISABLED) {
|
||||||
|
disabledList.add(desc.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disabledList.isEmpty()) {
|
||||||
|
props.remove("workutils.tools.disabled");
|
||||||
|
props.remove("workutils.tools.enabled");
|
||||||
|
} else {
|
||||||
|
props.setProperty("workutils.tools.disabled", String.join(",", disabledList));
|
||||||
|
}
|
||||||
|
|
||||||
|
configManager.saveSectioned(props);
|
||||||
|
properties.mergeExternal(props);
|
||||||
|
log.info("插件状态已保存: 禁用={}", disabledList);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
showAlert(AlertType.ERROR, "保存插件状态失败: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 刷新主窗口导航栏 */
|
||||||
|
private void refreshMainWindowNavItems() {
|
||||||
|
if (onConfigChanged != null) onConfigChanged.run();
|
||||||
|
if (navItemsRefresher != null) {
|
||||||
|
List<MainWindow.NavItem> items = buildNavItemsFromPlugins();
|
||||||
|
navItemsRefresher.accept(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从当前启用的插件构建导航项列表 */
|
||||||
|
private List<MainWindow.NavItem> buildNavItemsFromPlugins() {
|
||||||
|
List<MainWindow.NavItem> items = new ArrayList<>();
|
||||||
|
for (Plugin plugin : pluginManager.getEnabledPlugins()) {
|
||||||
|
PluginDescriptor desc = plugin.getDescriptor();
|
||||||
|
String pluginId = desc.getId();
|
||||||
|
items.add(new MainWindow.NavItem(
|
||||||
|
desc.getIcon(), desc.getName(), desc.getDescription(),
|
||||||
|
null, // action 不在此处设置,由 WorkUtilsApplication 管理
|
||||||
|
pluginId
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// 系统设置固定项
|
||||||
|
items.add(new MainWindow.NavItem(
|
||||||
|
"\u2699\ufe0f", "系统设置",
|
||||||
|
"配置系统路径、数据库连接、项目路径、多数据源、字体大小等",
|
||||||
|
null, "__settings__"
|
||||||
|
));
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Tab 6: 显示设置 ====================
|
// ==================== Tab 6: 显示设置 ====================
|
||||||
|
|
||||||
private Tab createDisplayTab() {
|
private Tab createDisplayTab() {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.example.workutils.system.config.SettingsService;
|
|||||||
import com.example.workutils.system.config.WorkUtilsProperties;
|
import com.example.workutils.system.config.WorkUtilsProperties;
|
||||||
import com.example.workutils.system.support.ConsoleHelper;
|
import com.example.workutils.system.support.ConsoleHelper;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
@@ -39,7 +40,7 @@ public class PluginManager {
|
|||||||
private Map<String, PluginState> pluginStates;
|
private Map<String, PluginState> pluginStates;
|
||||||
|
|
||||||
public PluginManager(List<Plugin> plugins, WorkUtilsProperties properties,
|
public PluginManager(List<Plugin> plugins, WorkUtilsProperties properties,
|
||||||
SettingsService settingsService) {
|
@Lazy SettingsService settingsService) {
|
||||||
this.allPlugins = plugins;
|
this.allPlugins = plugins;
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
this.settingsService = settingsService;
|
this.settingsService = settingsService;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import java.util.*;
|
|||||||
public class MainWindow {
|
public class MainWindow {
|
||||||
|
|
||||||
private final Stage stage;
|
private final Stage stage;
|
||||||
private final List<NavItem> navItems;
|
private List<NavItem> navItems;
|
||||||
private final WorkUtilsProperties properties;
|
private final WorkUtilsProperties properties;
|
||||||
|
|
||||||
private VBox navBox;
|
private VBox navBox;
|
||||||
@@ -100,18 +100,43 @@ public class MainWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private double computeNavMinBaseWidth() {
|
private double computeNavMinBaseWidth() {
|
||||||
double maxTextWidth = 0;
|
// 固定图标区 24px + 间距 8px + 最长文字约 6字符 * 13px ≈ 78px + padding 32px
|
||||||
for (NavItem item : navItems) {
|
return 24 + 8 + 130 + 16;
|
||||||
String text = " " + item.icon + " " + item.name;
|
}
|
||||||
double w = 0;
|
|
||||||
for (int i = 0; i < text.length(); i++) {
|
/**
|
||||||
char c = text.charAt(i);
|
* 刷新导航栏(插件启用/禁用后调用)
|
||||||
if (c > 127) w += 1.05;
|
*/
|
||||||
else w += 0.58;
|
public void refreshNavItems(List<NavItem> newItems) {
|
||||||
}
|
navButtons.clear();
|
||||||
maxTextWidth = Math.max(maxTextWidth, w * 13);
|
navBox.getChildren().clear();
|
||||||
|
|
||||||
|
// 品牌区
|
||||||
|
navBox.getChildren().add(appLabel);
|
||||||
|
|
||||||
|
// 分割线
|
||||||
|
Separator sep = new Separator();
|
||||||
|
sep.getStyleClass().add(Theme.CSS_NAV_SEP);
|
||||||
|
sep.setPadding(new Insets(0));
|
||||||
|
navBox.getChildren().add(sep);
|
||||||
|
|
||||||
|
this.navItems.clear();
|
||||||
|
this.navItems.addAll(newItems);
|
||||||
|
|
||||||
|
// 导航按钮
|
||||||
|
for (NavItem item : newItems) {
|
||||||
|
Button btn = createNavButton(item);
|
||||||
|
navButtons.add(btn);
|
||||||
|
navBox.getChildren().add(btn);
|
||||||
}
|
}
|
||||||
return maxTextWidth + 3 + 16 + 16 + 8;
|
|
||||||
|
// 弹性空间
|
||||||
|
Region spacer = new Region();
|
||||||
|
VBox.setVgrow(spacer, Priority.ALWAYS);
|
||||||
|
navBox.getChildren().add(spacer);
|
||||||
|
|
||||||
|
// 回到欢迎页
|
||||||
|
showWelcomePage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int fsTitle() { return properties.getFontSectionTitle(); }
|
private int fsTitle() { return properties.getFontSectionTitle(); }
|
||||||
@@ -268,14 +293,25 @@ public class MainWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Button createNavButton(NavItem item) {
|
private Button createNavButton(NavItem item) {
|
||||||
Button btn = new Button(" " + item.icon + " " + item.name);
|
Button btn = new Button();
|
||||||
btn.setFont(rfont(13));
|
|
||||||
btn.setMaxWidth(Double.MAX_VALUE);
|
btn.setMaxWidth(Double.MAX_VALUE);
|
||||||
btn.setAlignment(Pos.CENTER_LEFT);
|
btn.setAlignment(Pos.CENTER_LEFT);
|
||||||
btn.setPadding(new Insets(12, 16, 12, 16));
|
btn.setPadding(new Insets(12, 16, 12, 16));
|
||||||
btn.setTextAlignment(TextAlignment.LEFT);
|
|
||||||
btn.setCursor(Cursor.HAND);
|
btn.setCursor(Cursor.HAND);
|
||||||
|
|
||||||
|
// 使用 HBox 分离图标和文字,图标区域固定宽度确保对齐
|
||||||
|
HBox content = new HBox(8);
|
||||||
|
content.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
Label iconLabel = new Label(item.icon);
|
||||||
|
iconLabel.setPrefWidth(24); // 固定图标区域宽度
|
||||||
|
iconLabel.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
iconLabel.setFont(rfont(13));
|
||||||
|
Label nameLabel = new Label(item.name);
|
||||||
|
nameLabel.setFont(rfont(13));
|
||||||
|
content.getChildren().addAll(iconLabel, nameLabel);
|
||||||
|
btn.setGraphic(content);
|
||||||
|
btn.setText("");
|
||||||
|
|
||||||
btn.getStyleClass().add(Theme.CSS_NAV_BTN);
|
btn.getStyleClass().add(Theme.CSS_NAV_BTN);
|
||||||
|
|
||||||
btn.setOnMouseEntered(e -> {
|
btn.setOnMouseEntered(e -> {
|
||||||
|
|||||||
@@ -50,6 +50,12 @@
|
|||||||
-fx-max-width: Infinity;
|
-fx-max-width: Infinity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-btn .text {
|
||||||
|
/* 固定图标区域宽度,确保菜单文字左对齐 */
|
||||||
|
-fx-display: inline-flex;
|
||||||
|
-fx-alignment: center-left;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-btn:hover {
|
.nav-btn:hover {
|
||||||
-fx-background-color: -workutils-nav-hover;
|
-fx-background-color: -workutils-nav-hover;
|
||||||
-fx-text-fill: #e6edf3;
|
-fx-text-fill: #e6edf3;
|
||||||
|
|||||||
@@ -51,6 +51,12 @@
|
|||||||
-fx-max-width: Infinity;
|
-fx-max-width: Infinity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-btn .text {
|
||||||
|
/* 固定图标区域宽度,确保菜单文字左对齐 */
|
||||||
|
-fx-display: inline-flex;
|
||||||
|
-fx-alignment: center-left;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-btn:hover {
|
.nav-btn:hover {
|
||||||
-fx-background-color: -workutils-nav-hover;
|
-fx-background-color: -workutils-nav-hover;
|
||||||
-fx-text-fill: #f1f5f9;
|
-fx-text-fill: #f1f5f9;
|
||||||
|
|||||||
Reference in New Issue
Block a user