用JavaFX写了一个简易的管理系统

文章目录

  • 前言
  • 正文
    • 一、最终效果
      • 1.1 主页面
      • 1.2 动物管理页面-初始化
      • 1.3 动物管理页面-修改&新增
      • 1.4 动物管理页面-删除&批量删除
    • 二、核心代码展示
      • 2.1 启动类
      • 2.2 数据库配置-db.setting
      • 2.3 日志文本域组件
      • 2.4 自定义表格视图组件
      • 2.5 自定义分页组件
      • 2.6 动物管理页面
      • 2.7 页面工厂
  • 附录
    • 附1:sql脚本

前言

自学一下Java FX ,用Java代码写一个简易的客户端。

本文项目是Maven项目,使用了Java17,以及mysql。
代码仓库:https://gitee.com/fengsoshuai/java-fx-management-system-demo/tree/dev-with-db/

正文

一、最终效果

1.1 主页面

启动项目后,自动展示主页面。
用JavaFX写了一个简易的管理系统_第1张图片

1.2 动物管理页面-初始化

支持分页查询,搜索,新增,修改,删除,批量删除等功能。
整体分为以下几层:

  • 操作按钮,包含新增,修改,删除,刷新列表;
  • 搜索框,支持搜索动物的几个常用信息;
  • 列表,展示动物数据(分页);
  • 分页功能相关按钮,支持首页,下一页,最后一页,跳转到指定页,切换每页大小;
  • 操作记录,记录操作日志,内存存储(有兴趣的可以改为数据库存储),支持清空记录;

用JavaFX写了一个简易的管理系统_第2张图片

1.3 动物管理页面-修改&新增

用JavaFX写了一个简易的管理系统_第3张图片

1.4 动物管理页面-删除&批量删除

使用ctrl+鼠标左键选择要删除的数据。然后点击删除按钮。
出现弹窗后,点击确定,即可删除。
用JavaFX写了一个简易的管理系统_第4张图片

二、核心代码展示

2.1 启动类

渲染主页,左侧菜单栏,以及基本的布局。

package org.feng.demofx;

import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import org.feng.demofx.pages.AnimalManagePage;
import org.feng.demofx.pages.MainPage;
import org.feng.demofx.sys.*;
import org.feng.demofx.util.ArrayUtil;
import org.feng.demofx.util.StyleUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 启动类
 *
 * @author feng
 */
public class StartApplication extends Application {

    /**
     * 屏幕宽度
     */
    public static final Double SCREEN_WIDTH;

    /**
     * 屏幕高度
     */
    public static final Double SCREEN_HEIGHT;

    /**
     * 左侧菜单宽度
     */
    public static final Double LEFT_MENU_WIDTH;

    /**
     * root容器,使用HBox类型
     */
    private HBox root;

    /**
     * 左侧菜单栏
     */
    private VBox leftMenu;

    /**
     * 当前页的节点
     */
    private Node currentPageNode;

    /**
     * 左侧菜单的当前索引
     */
    private Integer currentMenuIndex;
    /**
     * 左侧菜单的临时索引,选中时的菜单项索引
     */
    private Integer tempIndex;

    /**
     * 左侧菜单面板背景颜色
     */
    private static final Color LEFT_MENU_BACKGROUND_COLOR = Color.web("#FFF8DC");
    /**
     * 左侧菜单按钮背景颜色
     */
    private static final Color LEFT_MENU_BUTTON_BACKGROUND_COLOR = Color.web("#DEB887");
    /**
     * 左侧菜单按钮hover时(鼠标移动到按钮上)的颜色
     */
    private static final Color LEFT_MENU_BUTTON_HOVER_BACKGROUND_COLOR = Color.web("D2691E");
    /**
     * 左侧菜单按钮点击时的字体颜色
     */
    private static final Color LEFT_MENU_BUTTON_CLICK_BACKGROUND_COLOR = Color.web("#6495ed");

    static {
        SCREEN_WIDTH = Screen.getPrimary().getBounds().getWidth();
        SCREEN_HEIGHT = Screen.getPrimary().getBounds().getHeight();
        LEFT_MENU_WIDTH = 240.0;
    }


    @Override
    public void start(Stage stage) throws Exception {
        // 注册页面渲染器
        registerPageNodeRender();

        root = new HBox();
        // 渲染左侧菜单栏
        leftMenu = renderLeftMenu(root);
        root.getChildren().add(leftMenu);

        // 主面板
        currentPageNode = routePage(root, MainPage.PAGE_KEY);
        HBox.setHgrow(currentPageNode, Priority.ALWAYS);
        root.getChildren().add(currentPageNode);
        StyleUtil.setPaneBackground(root, Color.WHITE);

        Scene scene = new Scene(root, SCREEN_WIDTH * 0.8, SCREEN_HEIGHT * 0.8);

        // 窗口的标题
        stage.setTitle("管理系统");
        stage.setScene(scene);
        // 禁止拖拽窗口大小
        stage.setResizable(false);
        stage.show();
    }


    private VBox renderLeftMenu(HBox root) {
        VBox vbox = new VBox();
        vbox.setMinHeight(root.getPrefHeight());
        vbox.setMinWidth(LEFT_MENU_WIDTH);
        StyleUtil.setPaneBackground(vbox, LEFT_MENU_BACKGROUND_COLOR);
        // 增加菜单中的项目
        vbox.getChildren().addAll(getLeftMenuItemList());
        return vbox;
    }

    /**
     * 生成左侧菜单按钮
     */
    private List<Button> getLeftMenuItemList() {
        double buttonHeight = 30;
        List<Button> buttonList = new ArrayList<>(3);
        Map<String, String> pageNameMap = PageNodeRenderFactory.getPageNameMap();
        String[] itemNames = pageNameMap.values().toArray(new String[0]);

        for (String name : itemNames) {
            Button button = new Button(name);
            button.setMinWidth(LEFT_MENU_WIDTH);
            button.setMinHeight(buttonHeight);
            StyleUtil.setButtonBackground(button, LEFT_MENU_BUTTON_BACKGROUND_COLOR, Color.WHITE);
            // 增加鼠标移动到菜单上到hover效果
            button.setOnMouseMoved(event -> {
                StyleUtil.setButtonBackground(button, LEFT_MENU_BUTTON_HOVER_BACKGROUND_COLOR, Color.WHITE);
                StyleUtil.setFont(button, Color.web("#E6A23C"), -1);
            });
            button.setOnMouseExited(event -> {
                if (currentMenuIndex == null || !button.getText().equals(itemNames[currentMenuIndex])) {
                    StyleUtil.setButtonBackground(button, LEFT_MENU_BUTTON_BACKGROUND_COLOR, Color.WHITE);
                } else {
                    StyleUtil.setButtonBackground(button, LEFT_MENU_BUTTON_BACKGROUND_COLOR, Color.WHITE);
                }
            });
            button.setOnMouseClicked(event -> {
                currentMenuIndex = ArrayUtil.getIndexForArray(itemNames, button.getText());
                currentPageNode = routePage(root, PageNodeRenderFactory.getPageKeyByName(name));
                root.getChildren().remove(1);    //清除右侧页面路由组件节点
                HBox.setHgrow(currentPageNode, Priority.ALWAYS);
                root.getChildren().add(currentPageNode);
                StyleUtil.setFont(button, Color.WHITE, -1);
                // 选中状态逻辑
                if (tempIndex != null) {
                    Button node = (Button) leftMenu.getChildren().get(tempIndex);
                    // 清空选中状态样式
                    StyleUtil.setFont(node, Color.WHITE, -1);
                    StyleUtil.setButtonBackground(node, LEFT_MENU_BUTTON_BACKGROUND_COLOR, Color.WHITE);
                }
                // 设置选中样式
                StyleUtil.setFont(button, LEFT_MENU_BUTTON_CLICK_BACKGROUND_COLOR, -1);
                tempIndex = currentMenuIndex;
            });
            buttonList.add(button);
        }
        return buttonList;
    }

    /**
     * 右侧页面路由
     */
    private Node routePage(Pane root, String pageKey) {
        return PageNodeRenderFactory.newInstance(pageKey).render(root);
    }

    private void registerPageNodeRender() {
        PageNodeRenderFactory.registerNodeRender(MainPage.class);
        PageNodeRenderFactory.registerNodeRender(AnimalManagePage.class);

        PageNodeRenderFactory.fillPageNameMap();
    }

    public static void main(String[] args) {
        launch();
    }
}

2.2 数据库配置-db.setting

用JavaFX写了一个简易的管理系统_第5张图片

#------------------------------------------------------------------------------------------
## 基本配置信息
# JDBC URL,根据不同的数据库,使用相应的JDBC连接字符串
url = jdbc:mysql://localhost:3306/fx_demo?useUnicode=true&serverTimezone=UTC
# 用户名,此处也可以使用 user 代替
username = root
# 密码,此处也可以使用 pass 代替
password = root
# JDBC驱动名,可选(Hutool会自动识别)
# driver = com.mysql.jdbc.Driver

## 可选配置
# 是否在日志中显示执行的SQL
showSql = true
# 是否格式化显示的SQL
formatSql = false
# 是否显示SQL参数
showParams = true
# 打印SQL的日志等级,默认debug
sqlLevel = debug
#------------------------------------------------------------------------------------------

2.3 日志文本域组件

对应于效果中的“操作记录”文本域。

package org.feng.demofx.pages;

import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import lombok.Getter;
import org.feng.demofx.util.ControlUtil;
import org.feng.demofx.util.TimeUtil;

import java.time.LocalDateTime;

/**
 * 日志文本域组件
 *
 * @author feng
 */
@Getter
public class LogTextAreaComponent {

    private final TextArea textArea;
    private final VBox logBox;

    public LogTextAreaComponent(double height, double width) {
        // 控制台输入操作记录
        textArea = new TextArea();
        textArea.setScrollTop(Double.MAX_VALUE);
        textArea.setMaxHeight(height);
        textArea.setMaxWidth(width);
        textArea.setEditable(false);
        textArea.setStyle("-fx-text-fill: red");

        // 日志盒子
        logBox = new VBox();
        logBox.setMaxWidth(width);

        logBox.getChildren().add(new Label(""));

        Label operLogLabel = new Label("操作记录:");
        operLogLabel.setFont(Font.font(null, FontWeight.BOLD, 15));
        operLogLabel.setPadding(new Insets(5, 0, 5, 0));

        logBox.getChildren().add(operLogLabel);
        logBox.getChildren().add(textArea);

        // 清空日志记录按钮
        logBox.getChildren().add(new Label(""));
        Button clearLog = ControlUtil.genClearLogButton();
        logBox.getChildren().add(clearLog);
        clearLog.setOnMouseClicked(event -> {
            textArea.setText("");
        });
    }

    /**
     * 记录日志
     *
     * @param text 日志内容
     */
    public void log(String text) {
        textArea.appendText("【" + TimeUtil.parse(LocalDateTime.now()) + "】" + text + "\r\n");
    }
}

2.4 自定义表格视图组件

package org.feng.demofx.pages;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableView;
import lombok.Data;
import org.feng.demofx.StartApplication;
import org.feng.demofx.util.TableColumnUtil;
import org.feng.demofx.vo.AnimalVo;

import java.util.List;

/**
 * 自定义表格视图组件
 *
 * @author 01434188
 */
@Data
public class CustomTableViewComponent<T> {

    /**
     * 表格视图
     */
    private TableView<T> tableView;

    private ObservableList<T> data;

    public CustomTableViewComponent(Class<T> beanClass, List<T> dataList) {
        tableView = new TableView<>();

        // 设置可编辑
        tableView.setEditable(true);
        // 表格宽度
        tableView.setMaxWidth(((StartApplication.SCREEN_WIDTH * 0.8) - StartApplication.LEFT_MENU_WIDTH) * 0.8);
        // 设置可选择多行数据
        tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        // 解析表头和列信息
        TableColumnUtil.parseColumnsByClass(tableView.getColumns(), beanClass);
        // 添加表格数据
        data = FXCollections.observableArrayList();
        data.addAll(dataList);
        tableView.setItems(data);
    }


    public void resetData(List<T> newDataList) {
        ObservableList<T> list = FXCollections.observableArrayList();
        list.addAll(newDataList);
        data = list;
        tableView.setItems(list);
    }
}

2.5 自定义分页组件

package org.feng.demofx.pages;

import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import lombok.Data;
import org.feng.demofx.util.ControlUtil;

/**
 * 自定义分页组件
 *
 * @author feng
 */
@Data
public class CustomPagingComponent {

    /**
     * 每页条数
     */
    private ComboBox<String> pageSize;

    /**
     * 当前页
     */
    private TextField currentPage;

    /**
     * 上一页
     */
    private Button lastPageButton;
    /**
     * 下一页
     */
    private Button nextPageButton;

    /**
     * 跳转
     */
    private Button skipPageButton;

    /**
     * 首页
     */
    private Button firstPageButton;
    /**
     * 最后一页
     */
    private Button finallyPageButton;

    /**
     * 分页面板
     */
    private GridPane pagingGridPane;

    /**
     * 总页数
     */
    private Label pageCount;

    public CustomPagingComponent(double width, double height) {
        this(width, height, null, null);
    }

    public CustomPagingComponent(double width, double height, Integer pageCount, Integer pageSize) {
        pagingGridPane = new GridPane();
        pagingGridPane.setMaxWidth(width);
        pagingGridPane.setMinWidth(width);
        pagingGridPane.setMaxHeight(height);
        pagingGridPane.setMinHeight(height);
        pagingGridPane.setPadding(new Insets(8, 0, 8, 0));

        // 设置下拉框,选择每页条数;默认15条
        this.pageSize = new ComboBox<>();
        this.pageSize.getItems().addAll("10", "15", "30", "50", "100");
        this.pageSize.setValue(pageSize == null ? "15" : String.valueOf(pageSize));

        // 当前页默认设置为1
        currentPage = new TextField();
        currentPage.setText("1");
        currentPage.setMaxWidth(35);
        // 上一页,下一页
        lastPageButton = ControlUtil.pagingButton("上一页");
        nextPageButton = ControlUtil.pagingButton("下一页");
        skipPageButton = ControlUtil.pagingButton("跳转");
        firstPageButton = ControlUtil.pagingButton("首页");
        finallyPageButton = ControlUtil.pagingButton("最后一页");

        // 总页数
        this.pageCount = new Label(String.format(" %s ", pageCount == null ? "0" : String.valueOf(pageCount)));

        // 设置垂直间距,水平间距
        pagingGridPane.setVgap(5);
        pagingGridPane.setHgap(3);

        pagingGridPane.add(new Label("总页数:"), 0, 0);
        pagingGridPane.add(this.pageCount, 1, 0);
        Node spacer1 = new Region();
        HBox.setHgrow(spacer1, Priority.ALWAYS);
        pagingGridPane.add(spacer1, 2, 0);

        pagingGridPane.add(firstPageButton, 3, 0);
        pagingGridPane.add(lastPageButton, 4, 0);
        pagingGridPane.add(currentPage, 5, 0);
        pagingGridPane.add(nextPageButton, 6, 0);
        pagingGridPane.add(finallyPageButton, 7, 0);
        pagingGridPane.add(skipPageButton, 8, 0);

        Node spacer2 = new Region();
        HBox.setHgrow(spacer2, Priority.ALWAYS);
        pagingGridPane.add(spacer2, 9, 0);
        pagingGridPane.add(this.pageSize, 10, 0);
    }

    public int getCurrentPageValue() {
        return Integer.parseInt(this.getCurrentPage().getText());
    }

    public void setCurrentPageValue(int currentPageValue) {
        this.getCurrentPage().setText(String.valueOf(currentPageValue));
    }

    public int getPageSizeValue() {
        return Integer.parseInt(this.getPageSize().getValue());
    }

    public void setPageSizeValue(int pageSizeValue) {
        this.getPageSize().setValue(String.valueOf(pageSizeValue));
    }

    public void setPageCountValue(int pageCountValue) {
        this.getPageCount().setText(String.valueOf(pageCountValue));
    }
}

2.6 动物管理页面

package org.feng.demofx.pages;

import cn.hutool.core.bean.BeanUtil;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import lombok.Data;
import org.feng.demofx.StartApplication;
import org.feng.demofx.dao.impl.AnimalDao;
import org.feng.demofx.entity.bo.AnimalBO;
import org.feng.demofx.enums.AnimalSexEnum;
import org.feng.demofx.enums.AnimalTypeEnum;
import org.feng.demofx.service.AnimalService;
import org.feng.demofx.service.impl.AnimalServiceImpl;
import org.feng.demofx.sys.PageNodeKey;
import org.feng.demofx.sys.PageNodeRender;
import org.feng.demofx.util.*;
import org.feng.demofx.vo.AnimalVo;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 动物管理界面
 *
 * @author feng
 */
@PageNodeKey(value = AnimalManagePage.PAGE_KEY, pageName = "动物管理")
public class AnimalManagePage implements PageNodeRender {
    public static final String PAGE_KEY = "animalManage";

    private final AnimalService animalService;
    public AnimalManagePage() {
        animalService = new AnimalServiceImpl(new AnimalDao());
    }

    @Override
    public Node render(Pane pane) {
        VBox vbox = new VBox();
        // 当前容器内,布局居中,局上
        vbox.setAlignment(Pos.TOP_CENTER);
        // 当前面板背景颜色
        StyleUtil.setPaneBackground(vbox, Color.web("#fffff0"));
        // 页面标题
        vbox.getChildren().add(genPageTitle("动物管理页面"));
        final double width = ((StartApplication.SCREEN_WIDTH * 0.8) - StartApplication.LEFT_MENU_WIDTH) * 0.8;

        // 操作按钮,
        VBox oper = new VBox();
        HBox hBox = new HBox();
        hBox.setMaxWidth(250);
        oper.getChildren().add(hBox);
        Button addButton = ControlUtil.genAddButton();
        Button updateButton = ControlUtil.genUpdateButton();
        Button deleteButton = ControlUtil.genDeleteButton();
        Button refreshButton = ControlUtil.genRefreshButton();
        // 设置外边距,上、右,下,左
        oper.setPadding(new Insets(10, 0, 12, 0));
        oper.setAlignment(Pos.TOP_LEFT);
        oper.setMaxWidth(width);
        oper.setMaxHeight(200);

        Node spacer1 = new Region();
        HBox.setHgrow(spacer1, Priority.ALWAYS);
        Node spacer2 = new Region();
        HBox.setHgrow(spacer2, Priority.ALWAYS);
        Node spacer3 = new Region();
        HBox.setHgrow(spacer3, Priority.ALWAYS);

        hBox.getChildren().add(addButton);
        hBox.getChildren().add(spacer1);
        hBox.getChildren().add(updateButton);
        hBox.getChildren().add(spacer2);
        hBox.getChildren().add(deleteButton);
        hBox.getChildren().add(spacer3);
        hBox.getChildren().add(refreshButton);

        vbox.getChildren().add(oper);

        //  搜索面板
        SearchAnimalPane searchAnimalPane = new SearchAnimalPane(oper.getMaxWidth());
        vbox.getChildren().add(searchAnimalPane.getSearchPane());
        // 分页组件
        CustomPagingComponent customPagingComponent = new CustomPagingComponent(width, 60);
        // 控制台输入操作记录
        LogTextAreaComponent logger = new LogTextAreaComponent(400, width);
        // 表格视图
        PageBean<AnimalVo> pageBean = new PageBean<>(customPagingComponent.getPageSizeValue(),
                customPagingComponent.getCurrentPageValue(), 0);
        animalService.listPage(searchAnimalPane.genAnimalBo(), pageBean);
        customPagingComponent.setPageCountValue(pageBean.getSumPageCount());
        CustomTableViewComponent<AnimalVo> tableViewComponent = new CustomTableViewComponent<>(AnimalVo.class, pageBean.getData());

        TableView<AnimalVo> animalTableView = tableViewComponent.getTableView();
        // 表格展示数据+分页
        vbox.getChildren().add(animalTableView);
        vbox.getChildren().add(customPagingComponent.getPagingGridPane());
        // 分隔线
        vbox.getChildren().add(ControlUtil.separator(animalTableView.getMaxWidth()));
        // 操作记录
        vbox.getChildren().add(logger.getLogBox());

        // 注册刷新
        refreshButton.setOnAction(event -> {
            ControlUtil.refreshTableView(animalTableView, logger);
        });

        // 更改每页大小-事件处理
        customPagingComponent.getPageSize().setOnAction(event -> {
            int pageSize = customPagingComponent.getPageSizeValue();
            logger.log(String.format("修改每页大小为:%s,重新刷新列表!", pageSize));
            // 设置每页大小
            pageBean.setPageSize(pageSize);
            // 重新分页查询第一页数据
            refreshToFirstPage(pageBean, searchAnimalPane, customPagingComponent);
            // 刷新列表
            tableViewComponent.resetData(pageBean.getData());
            ControlUtil.refreshTableView(animalTableView, logger);
        });

        // 上一页
        customPagingComponent.getLastPageButton().setOnAction(event -> {
            // 当前页
            int currentPage = customPagingComponent.getCurrentPageValue();
            String canQuery = pageBean.setCurrentPageNo(currentPage - 1);
            if (StringUtil.isNotEmpty(canQuery)) {
                logger.log(canQuery);
                return;
            }
            animalService.listPage(searchAnimalPane.genAnimalBo(), pageBean);
            customPagingComponent.setPageCountValue(pageBean.getSumPageCount());
            customPagingComponent.setCurrentPageValue(currentPage - 1);
            // 刷新列表
            tableViewComponent.resetData(pageBean.getData());
            ControlUtil.refreshTableView(animalTableView, logger);
        });

        // 下一页
        customPagingComponent.getNextPageButton().setOnAction(event -> {
            // 当前页
            int currentPage = customPagingComponent.getCurrentPageValue();
            String canQuery = pageBean.setCurrentPageNo(currentPage + 1);
            if (StringUtil.isNotEmpty(canQuery)) {
                logger.log(canQuery);
                return;
            }
            animalService.listPage(searchAnimalPane.genAnimalBo(), pageBean);
            customPagingComponent.setCurrentPageValue(currentPage + 1);
            customPagingComponent.setPageCountValue(pageBean.getSumPageCount());
            // 刷新列表
            tableViewComponent.resetData(pageBean.getData());
            ControlUtil.refreshTableView(animalTableView, logger);
        });

        // 首页事件
        customPagingComponent.getFirstPageButton().setOnAction(event -> {
            refreshToFirstPage(pageBean, searchAnimalPane, customPagingComponent);
            logger.log("跳转首页成功!");
            // 刷新列表
            tableViewComponent.resetData(pageBean.getData());
            ControlUtil.refreshTableView(animalTableView, logger);
        });

        // 最后一页
        customPagingComponent.getFinallyPageButton().setOnAction(event -> {
            pageBean.setCurrentPageNo(pageBean.getSumPageCount());
            animalService.listPage(searchAnimalPane.genAnimalBo(), pageBean);
            customPagingComponent.setPageCountValue(pageBean.getSumPageCount());
            customPagingComponent.setCurrentPageValue(pageBean.getSumPageCount());
            // 刷新列表
            tableViewComponent.resetData(pageBean.getData());
            ControlUtil.refreshTableView(animalTableView, logger);
        });

        // 跳转事件
        customPagingComponent.getSkipPageButton().setOnAction(event -> {
            int currentPage = customPagingComponent.getCurrentPageValue();
            if(currentPage < 1) {
                currentPage = 1;
            }
            pageBean.setCurrentPageNo(currentPage);
            animalService.listPage(searchAnimalPane.genAnimalBo(), pageBean);
            // 如果输入的当前页大于总页数,修复跳转至最后一页
            if(currentPage > pageBean.getSumPageCount()) {
                currentPage = pageBean.getSumPageCount();
                pageBean.setCurrentPageNo(currentPage);
                animalService.listPage(searchAnimalPane.genAnimalBo(), pageBean);
            }

            customPagingComponent.setPageCountValue(pageBean.getSumPageCount());
            customPagingComponent.setCurrentPageValue(currentPage);
            logger.log(String.format("跳转第【%s】页成功!", currentPage));
            // 刷新列表
            tableViewComponent.resetData(pageBean.getData());
            ControlUtil.refreshTableView(animalTableView, logger);
        });

        // 当选择发生变化时调用该函数-选择某一行时
        animalTableView.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
            if (newVal != null && !animalTableView.getSelectionModel().isSelected(newVal.getId())) {
                logger.log("已选择 " + newVal.getName());
            }
        });

        // 注册搜索
        searchAnimalPane.getSearchButton().setOnAction(event -> {
            refreshToFirstPage(pageBean, searchAnimalPane, customPagingComponent);
            logger.log("搜索成功!");
            // 刷新列表
            tableViewComponent.resetData(pageBean.getData());
            ControlUtil.refreshTableView(animalTableView, logger);
        });

        // 注册删除
        deleteButton.setOnAction(ControlUtil.alertConfirm("确认删除吗?", "", buttonType -> {
            ButtonBar.ButtonData buttonData = buttonType.getButtonData();
            if (buttonData.isDefaultButton()) {
                // 确认删除
                ObservableList<AnimalVo> items = animalTableView.getSelectionModel().getSelectedItems();
                List<Long> ids = items.stream().mapToLong(vo -> Long.valueOf(vo.getId())).boxed().toList();
                logger.log("正在删除 " + items.stream().map(AnimalVo::getName).collect(Collectors.joining(",")));
                animalService.delete(ids);

                // 刷新列表
                refreshToFirstPage(pageBean, searchAnimalPane, customPagingComponent);
                tableViewComponent.resetData(pageBean.getData());
                ControlUtil.refreshTableView(animalTableView, logger);
                logger.log("删除 " + items.stream().map(AnimalVo::getName).collect(Collectors.joining(",")) + "成功!");
            }
        }));

        // 注册新增
        addButton.setOnAction(event -> {
            AddAndUpdateAnimalDialog addDialog = new AddAndUpdateAnimalDialog(null);
            addDialog.getDialog().showAndWait().ifPresent(buttonType -> {
                if (buttonType.getButtonData().isDefaultButton()) {
                    logger.log("开始新增数据");
                    // 获取文本框中数据
                    AnimalBO animalBO = addDialog.genAnimalBo();
                    logger.log("正在新增 " + animalBO.getName());
                    // 添加动物
                    animalService.add(animalBO);

                    // 刷新列表
                    refreshToFirstPage(pageBean, searchAnimalPane, customPagingComponent);
                    tableViewComponent.resetData(pageBean.getData());
                    ControlUtil.refreshTableView(animalTableView, logger);
                    logger.log("新增 " + animalBO.getName() + "成功!");
                }
            });
            addDialog.getDialog().close();
        });

        // 注册更新
        updateButton.setOnAction(event -> {
            ObservableList<AnimalVo> selectedItems = animalTableView.getSelectionModel().getSelectedItems();
            if (selectedItems == null || selectedItems.size() != 1) {
                logger.log("请选择一条数据进行修改");
                return;
            }
            // 获取选中的数据原值
            AnimalVo selectedAnimal = selectedItems.get(0);
            AddAndUpdateAnimalDialog updateDialog = new AddAndUpdateAnimalDialog(selectedAnimal);
            // 注册点击事件
            updateDialog.getDialog().showAndWait().ifPresent(buttonType -> {
                if (buttonType.getButtonData().isDefaultButton()) {
                    logger.log("开始修改数据");
                    // 获取文本框中数据
                    AnimalBO animalBO = updateDialog.genAnimalBo();
                    animalBO.setId(selectedAnimal.getId().longValue());

                    logger.log(String.format("正在修改ID为【%s】,名字为【%s】 ", selectedAnimal.getId(), updateDialog.getOldAnimal().getName()));

                    // 更新动物
                    animalService.update(animalBO);
                    // 刷新列表
                    refreshToFirstPage(pageBean, searchAnimalPane, customPagingComponent);
                    tableViewComponent.resetData(pageBean.getData());
                    ControlUtil.refreshTableView(animalTableView, logger);
                    logger.log(String.format("修改完成! ID为【%s】,修改名字【%s】为【%s】,动物类型【%s】为【%s】,年龄【%s】为【%s】", selectedAnimal.getId(), updateDialog.getOldAnimal().getName(), animalBO.getName(),
                            updateDialog.getOldAnimal().getAnimalType(), animalBO.getAnimalType().getDesc(), updateDialog.getOldAnimal().getAge(), animalBO.getAge()));
                }
            });
            updateDialog.getDialog().close();
        });

        // 动物管理页面初始化完成,刷新一次
        ControlUtil.refreshTableView(animalTableView, logger);
        return vbox;
    }

    /**
     * 刷新列表数据至第一页
     */
    private void refreshToFirstPage(PageBean<AnimalVo> pageBean, SearchAnimalPane searchAnimalPane, CustomPagingComponent customPagingComponent) {
        pageBean.setCurrentPageNo(1);
        animalService.listPage(searchAnimalPane.genAnimalBo(), pageBean);
        customPagingComponent.setPageCountValue(pageBean.getSumPageCount());
        customPagingComponent.setCurrentPageValue(1);
    }


    @Data
    private static class SearchAnimalPane {
        // 折叠面板
        private TitledPane searchPane;

        private TextField searchNameTextField;
        private TextField searchAgeTextField;
        private ComboBox<String> searchTypeComboBox;
        private ComboBox<String> searchSexComboBox;

        private DatePicker createDateStart;
        private DatePicker createDateEnd;
        private DatePicker updateDateStart;
        private DatePicker updateDateEnd;

        private Button searchButton;

        public SearchAnimalPane(double width) {
            // 定义搜索网格面板
            GridPane searchGridPane = new GridPane();
            // 设置垂直间距,水平间距
            searchGridPane.setVgap(10);
            searchGridPane.setHgap(5);
            searchNameTextField = new TextField();
            searchAgeTextField = new TextField();
            searchTypeComboBox = new ComboBox<>();
            searchTypeComboBox.getItems().addAll(Arrays.stream(AnimalTypeEnum.values()).map(AnimalTypeEnum::getDesc).toList());
            searchSexComboBox = new ComboBox<>();
            searchSexComboBox.getItems().addAll(Arrays.stream(AnimalSexEnum.values()).map(AnimalSexEnum::getDesc).toList());

            searchButton = ControlUtil.genSearchButton();
            Button clearButton = ControlUtil.genClearButton();
            createDateStart = new DatePicker();
            createDateEnd = new DatePicker();
            updateDateStart = new DatePicker();
            updateDateEnd = new DatePicker();
            createDateStart.setEditable(true);
            createDateEnd.setEditable(true);
            updateDateStart.setEditable(true);
            updateDateEnd.setEditable(true);
            searchGridPane.add(new Label("名字 "), 0, 0);
            searchGridPane.add(searchNameTextField, 1, 0);
            searchGridPane.add(new Label("年龄 "), 0, 1);
            searchGridPane.add(searchAgeTextField, 1, 1);
            searchGridPane.add(new Label("类型 "), 0, 2);
            searchGridPane.add(searchTypeComboBox, 1, 2);
            searchGridPane.add(new Label("性别 "), 0, 3);
            searchGridPane.add(searchSexComboBox, 1, 3);
            searchGridPane.add(new Label("创建日期起止 "), 0, 4);
            searchGridPane.add(new Label("更新日期起止 "), 0, 5);
            searchGridPane.add(createDateStart, 1, 4);
            searchGridPane.add(createDateEnd, 2, 4);
            searchGridPane.add(updateDateStart, 1, 5);
            searchGridPane.add(updateDateEnd, 2, 5);
            searchGridPane.add(clearButton, 0, 7);
            searchGridPane.add(searchButton, 1, 7);

            // 折叠面板-搜索
            searchPane = new TitledPane("高级搜索", searchGridPane);
            // 设置外边距,上、右,下,左
            searchPane.setPadding(new Insets(0, 0, 8, 0));
            searchPane.setMaxWidth(width);
            // 设置可折叠
            searchPane.setCollapsible(true);
            // 设置折叠时的动画
            searchPane.setAnimated(true);

            // 注册清空参数事件
            clearButton.setOnAction(event -> {
                clearSearchParam();
            });
        }

        public void clearSearchParam() {
            searchNameTextField.setText("");
            searchAgeTextField.setText("");
            searchTypeComboBox.setValue("");
            searchSexComboBox.setValue("");
            createDateStart.setValue(null);
            createDateEnd.setValue(null);
            updateDateStart.setValue(null);
            updateDateEnd.setValue(null);
        }

        public AnimalBO genAnimalBo() {
            String searchNameText = this.getSearchNameTextField().getText();
            String searchAgeText = this.getSearchAgeTextField().getText();
            String searchTypeValue = this.getSearchTypeComboBox().getValue();
            String searchSexValue = this.getSearchSexComboBox().getValue();
            LocalDate createDateStartValue = this.getCreateDateStart().getValue();
            LocalDate createDateEndValue = this.getCreateDateEnd().getValue();
            LocalDate updateDateStartValue = this.getUpdateDateStart().getValue();
            LocalDate updateDateEndValue = this.getUpdateDateEnd().getValue();

            AnimalVo animalVo = new AnimalVo();
            if (StringUtil.isNotEmpty(searchNameText)) {
                animalVo.setName(searchNameText);
            }
            if (StringUtil.isNotEmpty(searchAgeText)) {
                animalVo.setAge(Integer.parseInt(searchAgeText));
            }
            if (StringUtil.isNotEmpty(searchTypeValue)) {
                animalVo.setAnimalType(searchTypeValue);
            }
            if (StringUtil.isNotEmpty(searchSexValue)) {
                animalVo.setAnimalSex(searchSexValue);
            }

            // 创建时间起、止选择后生效
            if (Objects.nonNull(createDateStartValue) || Objects.nonNull(createDateEndValue)) {
                animalVo.setCreateDateRange(new DateRange(createDateStartValue, createDateEndValue));
            }
            // 更新时间起、止选择后生效
            if (Objects.nonNull(updateDateStartValue) || Objects.nonNull(updateDateEndValue)) {
                animalVo.setUpdateDateRange(new DateRange(updateDateStartValue, updateDateEndValue));
            }

            return animalVo.toAnimalBo();
        }
    }

    @Data
    private static class AddAndUpdateAnimalDialog {

        private Dialog<ButtonType> dialog;

        private TextField nameTextField;
        private TextField ageTextField;
        private ComboBox<String> typeComboBox;
        private ComboBox<String> sexComboBox;
        private TextArea remarkTextArea;

        private AnimalVo oldAnimal;

        public AddAndUpdateAnimalDialog(AnimalVo animalVo) {
            Label nameLabel = new Label("名字 ");
            nameTextField = new TextField();
            Label ageLabel = new Label("年龄 ");
            ageTextField = new TextField();
            Label typeLabel = new Label("类型 ");
            typeComboBox = new ComboBox<>();
            typeComboBox.getItems().addAll(Arrays.stream(AnimalTypeEnum.values()).map(AnimalTypeEnum::getDesc).toList());
            Label sexLabel = new Label("性别 ");
            sexComboBox = new ComboBox<>();
            sexComboBox.getItems().addAll(Arrays.stream(AnimalSexEnum.values()).map(AnimalSexEnum::getDesc).toList());
            Label remarkLabel = new Label("备注 ");
            remarkTextArea = new TextArea();
            remarkTextArea.setScrollTop(Double.MAX_VALUE);
            remarkTextArea.setEditable(true);
            remarkTextArea.setMaxHeight(250);
            remarkTextArea.setMaxWidth(300);

            if (animalVo == null) {
                typeComboBox.setValue(AnimalTypeEnum.UN_KNOW.getDesc());
                sexComboBox.setValue(AnimalSexEnum.UN_KNOW.getDesc());
            } else {
                oldAnimal = new AnimalVo();
                BeanUtil.copyProperties(animalVo, oldAnimal);

                // 数据回显
                nameTextField.setText(oldAnimal.getName());
                ageTextField.setText(String.valueOf(oldAnimal.getAge()));
                typeComboBox.setValue(oldAnimal.getAnimalType());
                sexComboBox.setValue(oldAnimal.getAnimalSex());
                remarkTextArea.setText(oldAnimal.getRemark());
            }

            // 定义网格面板
            GridPane gridPane = new GridPane();
            // 设置垂直间距,水平间距
            gridPane.setVgap(10);
            gridPane.setHgap(5);
            gridPane.add(nameLabel, 0, 0);
            gridPane.add(nameTextField, 1, 0);
            gridPane.add(ageLabel, 0, 1);
            gridPane.add(ageTextField, 1, 1);
            gridPane.add(typeLabel, 0, 2);
            gridPane.add(typeComboBox, 1, 2);
            gridPane.add(sexLabel, 0, 3);
            gridPane.add(sexComboBox, 1, 3);
            gridPane.add(remarkLabel, 0, 4);
            gridPane.add(remarkTextArea, 1, 4);

            DialogPane dialogPane = new DialogPane();
            dialogPane.setPrefHeight(300);
            dialogPane.setPrefWidth(500);
            dialogPane.setMaxHeight(400);
            dialogPane.setMaxWidth(550);

            dialogPane.setContent(gridPane);
            dialogPane.getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK);
            dialog = new Dialog<>();
            dialog.setDialogPane(dialogPane);
        }

        public AnimalBO genAnimalBo() {
            AnimalBO animalBO = new AnimalBO();
            // 年龄
            animalBO.setAge(Integer.parseInt(this.getAgeTextField().getText()));
            // 姓名
            animalBO.setName(this.getNameTextField().getText());
            // 获取下拉框的数据:动物类型
            String type = this.getTypeComboBox().getValue();
            animalBO.setAnimalType(AnimalTypeEnum.getByDesc(type));
            // 获取下拉框的数据:性别
            String sex = this.getSexComboBox().getValue();
            animalBO.setAnimalSex(AnimalSexEnum.getByDesc(sex));
            // 获取文本域内容:备注
            animalBO.setRemark(this.getRemarkTextArea().getText());
            return animalBO;
        }
    }
}

2.7 页面工厂

package org.feng.demofx.sys;

import org.feng.demofx.util.StringUtil;

import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
 * PageNodeRender工厂
 *
 * @author feng
 */
public class PageNodeRenderFactory {

    private static final Map<String, PageNodeRender> PAGE_NODE_RENDER_MAP = new HashMap<>(8);

    private static final Set<PageNodeDefine> PAGE_NODE_DEFINES = new TreeSet<>(Comparator.comparingInt(PageNodeDefine::getOrder)
            .thenComparing(PageNodeDefine::getPageKey));

    private static final Map<String, String> PAGE_NAME_MAP = new LinkedHashMap<>();
    private static final Map<String, String> RESERVE_PAGE_NAME_MAP = new LinkedHashMap<>();

    public static PageNodeRender newInstance(String pageKey) {
        return PAGE_NODE_RENDER_MAP.get(pageKey);
    }

    public static Map<String, String> getPageNameMap() {
        return PAGE_NAME_MAP;
    }

    public static String getPageKeyByName(String pageName) {
        return RESERVE_PAGE_NAME_MAP.get(pageName);
    }


    public static void registerNodeRender(Class<? extends PageNodeRender> renderClass) {
        try {
            // 处理PageNodeRender#getPageKey()
            PageNodeRender pageNodeRender = renderClass.getDeclaredConstructor().newInstance();
            String pageKey = pageNodeRender.getPageKey();
            if (StringUtil.isNotEmpty(pageKey)) {
                PAGE_NODE_RENDER_MAP.put(pageKey, pageNodeRender);

                PageNodeDefine pageNodeDefine = new PageNodeDefine();
                pageNodeDefine.setPageKey(pageKey);
                pageNodeDefine.setPageName(pageNodeRender.getPageName());
                pageNodeDefine.setOrder(pageNodeRender.order());
                PAGE_NODE_DEFINES.add(pageNodeDefine);
                return;
            }

            // 处理PageNodeKey注解
            if (renderClass.isAnnotationPresent(PageNodeKey.class)) {
                PageNodeKey pageNodeKey = renderClass.getAnnotation(PageNodeKey.class);
                pageKey = pageNodeKey.value();
                if (StringUtil.isNotEmpty(pageKey)) {
                    PAGE_NODE_RENDER_MAP.put(pageKey, pageNodeRender);

                    PageNodeDefine pageNodeDefine = new PageNodeDefine();
                    pageNodeDefine.setPageKey(pageKey);
                    pageNodeDefine.setPageName(pageNodeKey.pageName());
                    pageNodeDefine.setOrder(pageNodeKey.order());
                    PAGE_NODE_DEFINES.add(pageNodeDefine);
                    return;
                }
            }

            throw new IllegalArgumentException(renderClass.getName() + "定义错误,请检查");
        } catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
                 IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }


    public static void fillPageNameMap() {
        PAGE_NODE_DEFINES.forEach(pageNodeDefine -> {
            PAGE_NAME_MAP.put(pageNodeDefine.getPageKey(), pageNodeDefine.getPageName());
            RESERVE_PAGE_NAME_MAP.put(pageNodeDefine.getPageName(), pageNodeDefine.getPageKey());
        });
    }
}

附录

附1:sql脚本

CREATE TABLE `fx_animal`
(
    `id`          BIGINT       NOT NULL AUTO_INCREMENT,
    `name`        VARCHAR(255) NOT NULL COMMENT '名字',
    `animal_type` INT          NOT NULL DEFAULT - 1 COMMENT '动物类型,-1 未知',
    `animal_sex`  INT          NOT NULL DEFAULT - 1 COMMENT '动物性别,-1 未知 0 雄性 1雌性',
    `age`         INT          NOT NULL DEFAULT 0 COMMENT '年龄',
    `update_time` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `create_time` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `remark`      VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注',
    PRIMARY KEY (`id`)
);

INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (1, '小名', 0, 1, 2, '2024-01-19 23:10:36', '2024-01-19 22:31:53', '呜呜呜汪汪汪');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (2, '小红', 1, 0, 0, '2024-01-19 23:50:17', '2024-01-19 22:31:59', '00000');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (3, '啊华', 1, 1, 2, '2024-01-19 23:13:46', '2024-01-19 23:13:46', '喵喵喵');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (4, '小吗', 1, 1, 2, '2024-01-22 23:34:09', '2024-01-22 23:34:09', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (5, '待机', -1, -1, 2, '2024-01-22 23:34:56', '2024-01-22 23:34:56', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (6, '松紧带哦哦', -1, -1, 21, '2024-01-22 23:35:05', '2024-01-22 23:35:05', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (7, '额温枪', -1, -1, 21, '2024-01-22 23:35:10', '2024-01-22 23:35:10', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (8, '大萨达', -1, -1, 2, '2024-01-22 23:35:16', '2024-01-22 23:35:16', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (9, '哇打算', -1, -1, 21, '2024-01-22 23:35:21', '2024-01-22 23:35:21', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (10, '大东方闪电', -1, -1, 22, '2024-01-22 23:35:37', '2024-01-22 23:35:37', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (11, '2多发点', -1, -1, 4322, '2024-01-22 23:35:50', '2024-01-22 23:35:50', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (12, '绕弯儿', -1, -1, 32, '2024-01-22 23:37:23', '2024-01-22 23:37:23', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (13, '法国掉头发', -1, -1, 32, '2024-01-22 23:37:30', '2024-01-22 23:37:30', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (14, '43给对方刚刚', -1, -1, 32, '2024-01-22 23:37:36', '2024-01-22 23:37:36', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (15, '手动挡', -1, -1, 2, '2024-01-22 23:37:45', '2024-01-22 23:37:45', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (16, '发得分', -1, -1, 32, '2024-01-22 23:37:53', '2024-01-22 23:37:53', '');
INSERT INTO `fx_demo`.`fx_animal`(`id`, `name`, `animal_type`, `animal_sex`, `age`, `update_time`, `create_time`, `remark`) VALUES (17, '明明', 1, 1, 13, '2024-01-24 21:03:58', '2024-01-24 21:03:21', '修改了一次年龄');


你可能感兴趣的:(javafx,java练习,java,javafx,动物管理系统,mysql)