统一应用顶部栏与设置入口

This commit is contained in:
张家训
2026-08-01 14:59:08 +08:00
parent 0a24a9b52b
commit 145b6559b8
5 changed files with 148 additions and 24 deletions
@@ -111,9 +111,9 @@ public class WorkUtilsApplication implements CommandLineRunner {
Platform.runLater(Theme::initTheme);
// 设置导航栏刷新回调(插件管理中启用/禁用后刷新侧边栏)
settingsService.setNavItemsRefresher(items -> Platform.runLater(() -> {
settingsService.setNavItemsRefresher(ignoredItems -> Platform.runLater(() -> {
if (mainWindow != null) {
mainWindow.refreshNavItems(items);
mainWindow.refreshNavItems(buildNavItems());
// 重新绑定插件面板
for (Plugin plugin : pluginManager.getEnabledPlugins()) {
PluginDescriptor desc = plugin.getDescriptor();
@@ -131,7 +131,7 @@ public class WorkUtilsApplication implements CommandLineRunner {
CountDownLatch windowClosed = new CountDownLatch(1);
Platform.runLater(() -> {
try {
mainWindow = new MainWindow(navItems, properties);
mainWindow = new MainWindow(navItems, properties, this::openSettingsEmbedded);
mainWindow.showAndWait();
} catch (Exception e) {
log.error("主窗口创建失败", e);
@@ -164,14 +164,6 @@ public class WorkUtilsApplication implements CommandLineRunner {
));
}
// 系统设置 — 内嵌为 Tab
items.add(new MainWindow.NavItem(
"⚙️", "系统设置",
"配置系统路径、数据库连接、项目路径、多数据源、字体大小等",
this::openSettingsEmbedded,
"__settings__"
));
return items;
}
@@ -8,7 +8,10 @@ import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
@@ -25,6 +28,7 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** Main application window and shell-level plugin content host. */
public class MainWindow {
@@ -43,11 +47,17 @@ public class MainWindow {
private FontIcon toolbarIcon;
private Label toolbarTitle;
public MainWindow(List<NavItem> navItems, WorkUtilsProperties properties) {
public MainWindow(List<NavItem> navItems, WorkUtilsProperties properties, Runnable openSettings) {
this.navItems = new ArrayList<>(navItems);
this.properties = properties;
this.stage = new Stage();
buildUI();
buildUI(Objects.requireNonNull(openSettings, "openSettings must not be null"));
}
/** Compatibility constructor for callers that do not expose settings. */
public MainWindow(List<NavItem> navItems, WorkUtilsProperties properties) {
this(navItems, properties, () -> {
});
}
public void refreshNavItems(List<NavItem> newItems) {
@@ -74,7 +84,7 @@ public class MainWindow {
}
}
private void buildUI() {
private void buildUI(Runnable openSettings) {
setAppIcon();
stage.setTitle("WorkUtils — 开发工作集");
stage.setWidth(1280);
@@ -84,7 +94,7 @@ public class MainWindow {
stage.setOnCloseRequest(event -> embeddedCache.clear());
BorderPane root = new BorderPane();
root.setTop(buildUnifiedHeader());
root.setTop(buildUnifiedHeader(openSettings));
navigationRail = new NavigationRail(navItems, this::navigateTo);
contentArea = new StackPane();
@@ -100,8 +110,9 @@ public class MainWindow {
showWelcomePage();
}
private HBox buildUnifiedHeader() {
private HBox buildUnifiedHeader(Runnable openSettings) {
HBox bar = new HBox(Theme.S1);
bar.setId("app-header");
bar.setAlignment(Pos.CENTER_LEFT);
bar.setPadding(new Insets(0, Theme.S5, 0, Theme.S4));
bar.setMinHeight(DesignTokens.HEADER_HEIGHT);
@@ -109,26 +120,38 @@ public class MainWindow {
bar.setMaxHeight(DesignTokens.HEADER_HEIGHT);
bar.getStyleClass().add(Theme.CSS_HEADER_BAR);
FontIcon brand = IconFactory.create(AppIcon.APP, 20);
bar.getChildren().add(brand);
Button menuButton = new Button(null, IconFactory.createButtonIcon(AppIcon.MENU, "切换导航栏"));
menuButton.setId("menu-button");
menuButton.setTooltip(new Tooltip("切换导航栏"));
menuButton.setOnAction(event -> navigationRail.toggle());
Region spacerLeft = new Region();
bar.getChildren().add(spacerLeft);
Label brand = new Label("WorkUtils", IconFactory.create(AppIcon.APP, 20));
brand.setContentDisplay(ContentDisplay.LEFT);
brand.setFont(Theme.sectionTitle());
pluginToolbar = new HBox(Theme.S2);
pluginToolbar.setAlignment(Pos.CENTER_LEFT);
pluginToolbar.setVisible(false);
pluginToolbar.setManaged(false);
FontIcon chevron = IconFactory.create(AppIcon.CHEVRON_RIGHT, 14);
toolbarIcon = IconFactory.create(AppIcon.APP, 18);
toolbarTitle = new Label();
toolbarTitle.setFont(Theme.body());
pluginToolbar.getChildren().addAll(toolbarIcon, toolbarTitle);
bar.getChildren().add(pluginToolbar);
toolbarTitle.setFont(Theme.pageTitle());
toolbarTitle.getStyleClass().add(Theme.CSS_PAGE_TITLE);
pluginToolbar.getChildren().addAll(chevron, toolbarIcon, toolbarTitle);
Region spacerRight = new Region();
HBox.setHgrow(spacerRight, Priority.ALWAYS);
bar.getChildren().add(spacerRight);
Button settingsButton = new Button(
null, IconFactory.createButtonIcon(AppIcon.SETTINGS, "系统设置"));
settingsButton.setId("settings-button");
settingsButton.setTooltip(new Tooltip("系统设置"));
settingsButton.setOnAction(event -> openSettings.run());
bar.getChildren().addAll(
menuButton, brand, pluginToolbar, spacerRight, settingsButton);
return bar;
}
@@ -0,0 +1,31 @@
package com.example.workutils.system.ui.shell;
import com.example.workutils.system.ui.Theme;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import java.util.Objects;
/** Semantic title block for plugin content pages. */
public final class PageHeader extends VBox {
public PageHeader(String title) {
this(title, null);
}
public PageHeader(String title, String description) {
super(Theme.S1);
Label titleLabel = new Label(Objects.requireNonNull(title, "title must not be null"));
titleLabel.setFont(Theme.pageTitle());
titleLabel.getStyleClass().add(Theme.CSS_PAGE_TITLE);
getChildren().add(titleLabel);
if (description != null && !description.isBlank()) {
Label descriptionLabel = new Label(description);
descriptionLabel.setFont(Theme.secondary());
descriptionLabel.getStyleClass().add(Theme.CSS_SECONDARY_TEXT);
getChildren().add(descriptionLabel);
}
}
}