文章目录
- 前言
- 正文
- 一、最终效果
- 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 主页面
启动项目后,自动展示主页面。
1.2 动物管理页面-初始化
支持分页查询,搜索,新增,修改,删除,批量删除等功能。
整体分为以下几层:
- 操作按钮,包含新增,修改,删除,刷新列表;
- 搜索框,支持搜索动物的几个常用信息;
- 列表,展示动物数据(分页);
- 分页功能相关按钮,支持首页,下一页,最后一页,跳转到指定页,切换每页大小;
- 操作记录,记录操作日志,内存存储(有兴趣的可以改为数据库存储),支持清空记录;
1.3 动物管理页面-修改&新增
1.4 动物管理页面-删除&批量删除
使用ctrl+鼠标左键选择要删除的数据。然后点击删除按钮。
出现弹窗后,点击确定,即可删除。
二、核心代码展示
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;}@Overridepublic 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
#------------------------------------------------------------------------------------------
## 基本配置信息
# 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));// 当前页默认设置为1currentPage = 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());}@Overridepublic 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);}@Dataprivate 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();}}@Dataprivate 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', '修改了一次年龄');