package com.ecep.contract.controller; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import com.ecep.contract.controller.table.EditableEntityTableTabSkin; import com.ecep.contract.controller.table.TableTabSkin; import com.ecep.contract.model.IdentityEntity; import com.ecep.contract.service.QueryService; import com.ecep.contract.service.ViewModelService; import com.ecep.contract.util.TableViewUtils; import com.ecep.contract.util.UITools; import com.ecep.contract.vm.IdentityViewModel; import javafx.application.Platform; import javafx.beans.property.Property; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.geometry.Bounds; import javafx.scene.Node; import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.util.converter.NumberStringConverter; /** * 实体管理器皮肤 * 提供了实体管理器的基本功能,如查询、新增、删除、修改、分页等 * * @param Entity 的类型 * @param Entity 对应的ViewModel * @param Skin 的类型 * @param */ public abstract class AbstEntityManagerSkin, SKIN extends ManagerSkin, C extends AbstManagerWindowController> implements ManagerSkin, TableTabSkin, EditableEntityTableTabSkin { private static final Logger logger = LoggerFactory.getLogger(AbstEntityManagerSkin.class); /** * */ protected C controller; protected CompletableFuture loadedFuture; protected ObservableList dataSet = FXCollections.observableArrayList(); protected PageRequest currentPageable = PageRequest.ofSize(25); protected final SimpleIntegerProperty currentPageNumber = new SimpleIntegerProperty(); // 是否允许调整表格高度 private boolean allowResize = true; // 记录延时任务信息 private ScheduledFuture loadTableDataSetFuture; public AbstEntityManagerSkin(C controller) { this.controller = controller; } @Override public TT getBean(Class requiredType) throws BeansException { return controller.getCachedBean(requiredType); } @Override public TableView getTableView() { return controller.table; } @Override public void handleException(String message, Throwable ex) { if (controller != null) { controller.handleException(message, ex); return; } if (logger.isErrorEnabled()) { logger.error(message, ex); } UITools.showExceptionAndWait(message, ex); } public Locale getLocale() { return controller.getLocale(); } public void install() { onShown(); // 注册 F5 和 Ctrl+R 刷新快捷键 KeyCodeCombination ctrlRCombination = new KeyCodeCombination(KeyCode.R, KeyCombination.SHORTCUT_DOWN); KeyCodeCombination f5Combination = new KeyCodeCombination(KeyCode.F5); getTableView().addEventHandler(KeyEvent.KEY_PRESSED, event -> { if (ctrlRCombination.match(event) || f5Combination.match(event)) { System.out.println("loadTableDataSetFuture = " + loadTableDataSetFuture); if (loadTableDataSetFuture == null) { loadTableDataSet(false); } event.consume(); } }); } private void onShown() { if (loadedFuture == null) { loadedFuture = runAsync(() -> { Platform.runLater(() -> { getTableView().setItems(dataSet); }); initializeSearchBar(); initializeTable(); initializeFooter(); TableView table = getTableView(); // 视图更新时 table.layoutBoundsProperty().addListener(this::resizeTable); // 启用行编辑功能 if (!table.isEditable()) { TableViewUtils.bindDoubleClicked(table, this::onTableRowDoubleClickedAction); } if (table.contextMenuProperty().get() == null) { ContextMenu contextMenu = new ContextMenu(); createContextMenu(contextMenu); table.setContextMenu(contextMenu); } table.setSortPolicy(v -> { if (loadTableDataSetFuture == null) { loadTableDataSet(false); } return true; }); loadTableDataSet(true); }); } } private void initializeSearchBar() { if (controller.searchKeyField != null) { controller.searchKeyField.setOnKeyReleased(event -> { if (event.getCode() == KeyCode.ENTER) { controller.searchBtn.fire(); } }); } if (controller.searchBtn != null) { controller.searchBtn.setOnAction(this::onSearchAction); } } private void initializeFooter() { currentPageNumber.addListener(this::currentPageNumberListener); // controller.currentPageNumberField.textProperty().bindBidirectional(currentPageNumber, new NumberStringConverter()); controller.previousPageBtn.setOnAction(event -> { try { currentPageable = currentPageable.previous(); loadTableDataSet(true); } catch (Exception e) { logger.warn("previous page error", e); } }); controller.nextPageBtn.setOnAction(event -> { try { currentPageable = currentPageable.next(); loadTableDataSet(true); } catch (Exception e) { logger.warn("next page error", e); } }); } private void currentPageNumberListener(Object obj, Number old, Number newValue) { int page = newValue.intValue(); if (page < 0) { page = 0; } if (currentPageable.getPageNumber() == page) { return; } currentPageable = currentPageable.withPage(page); loadTableDataSet(false); } public void onSearchAction(ActionEvent event) { currentPageable = currentPageable.withPage(0); loadTableDataSet(true); } /** * 根据表格高度重新计算分页的页大小 */ private void resizeTable(Object observable, Bounds old, Bounds newBounds) { if (!allowResize) { return; } double tableHeight = newBounds.getHeight(); if (tableHeight <= 0) { return; } TableView table = getTableView(); Node lookup = table.lookup("TableRow"); if (lookup != null) { double rowHeight = lookup.prefHeight(-1); int rows = (int) Math.round(table.getHeight() / rowHeight) - 1; // 只有当行数变化超过一定阈值时才重新加载数据 int currentRows = currentPageable.getPageSize(); if (Math.abs(rows - currentRows) <= 2) { return; // 避免微小变化导致频繁刷新 } int pageNumber = (int) Math.abs(currentPageable.getOffset() / rows); if (currentPageable.getPageNumber() == pageNumber && currentPageable.getPageSize() == rows) { return; } currentPageable = PageRequest.of(pageNumber, rows); loadTableDataSet(false); } } protected void createContextMenu(ContextMenu contextMenu) { MenuItem item2 = new MenuItem("刷新"); item2.setOnAction(this::onTableRefreshAction); contextMenu.getItems().add(item2); if (this instanceof EditableEntityTableTabSkin) { MenuItem item1 = new MenuItem("新建"); item1.setOnAction(this::onTableCreateNewAction); MenuItem item3 = new MenuItem("删除"); item3.setOnAction(this::onTableDeleteAction); contextMenu.getItems().addAll(item1, item3); } } protected void onTableCreateNewAction(ActionEvent event) { TV viewModel = createNewViewModel(); dataSet.add(viewModel); } protected void onTableRefreshAction(ActionEvent event) { loadTableDataSet(false); } protected void onTableDeleteAction(ActionEvent event) { ObservableList selectedItems = getTableView().getSelectionModel().getSelectedItems(); if (selectedItems.isEmpty()) { return; } if (!UITools.showConfirmDialog("删除行", "确认删除选中的" + selectedItems.size() + "条数据?")) { return; } for (TV selectedItem : new ArrayList<>(selectedItems)) { if (deleteRow(selectedItem)) { dataSet.remove(selectedItem); } } } /** * 处理单元格编辑事件 * * @param * @param event * @param propGetter */ protected void acceptCellEditEvent(TableColumn.CellEditEvent event, Function> propGetter) { TV row = event.getRowValue(); Property property = propGetter.apply(row); property.setValue(event.getNewValue()); try { saveRowData(row); } catch (Exception e) { handleException("保存出错", e); } } protected boolean deleteRow(TV row) { ViewModelService service = getViewModelService(); T entity = service.findById(row.getId().get()); if (entity != null) { try { service.delete(entity); return true; } catch (UnsupportedOperationException e) { handleException("删除出错,此操作不支持", e); } catch (Exception e) { handleException("删除出错", e); } } return false; } protected T createNewEntity(TV row) { ViewModelService service = getViewModelService(); if (service != null) { T entity = service.createNewEntity(); if (entity != null) { return entity; } } return null; } protected TV createNewViewModel() { ViewModelService service = getViewModelService(); if (service != null) { TV model = service.createNewViewModel(); if (model != null) { return model; } } return null; } protected void saveRowData(TV row) { ViewModelService service = getViewModelService(); if (service == null) { handleException("ViewModelService is null", new RuntimeException()); return; } row.saveInFxApplicationThread(service); } /** * 加载行数据 * * @param row * @return */ public T loadRowData(TV row) { if (row.getId() == null) { return null; } T entity = getViewModelService().findById(row.getId().get()); return entity; } /** * 删除行数据 * * @param entity */ public void deleteRowData(T entity) { if (entity == null) { return; } ViewModelService service = getViewModelService(); getViewModelService().delete(entity); } /** * 保存行数据 * * @param entity * @return */ public T saveRowData(T entity) { if (entity == null) { return null; } ViewModelService service = getViewModelService(); return getViewModelService().save(entity); } @Override public void loadTableDataSet() { loadTableDataSet(false); } /** * 加载表格数据 * 延时任务未执行前,再次调用此函数时,重新延时 * * @param reloadNow 是否立即刷新,立即刷新将直接submit一个任务到 Executor,否则 schedule 一个618毫秒的延时任务 */ public void loadTableDataSet(boolean reloadNow) { if (loadTableDataSetFuture != null) { loadTableDataSetFuture.cancel(true); } loadTableDataSetFuture = getExecutorService().schedule(() -> { try { _reloadTableData().thenRun(() -> loadTableDataSetFuture = null).exceptionally(this::handleException); } catch (Exception ex) { handleException("加载表格数据出错", ex); } }, reloadNow ? 0 : 618, TimeUnit.MILLISECONDS); } private CompletableFuture _reloadTableData() { CompletableFuture future = new CompletableFuture<>(); Platform.runLater(() -> { allowResize = false; // 禁用调整 dataSet.clear(); runAsync(() -> { controller.setStatus("载入中..."); // 异步加载数据 if (getViewModelService() instanceof QueryService queryService) { asyncLoadTableData(queryService, future); return; } // 同步加载方法 List models = loadTableData(); _updateModels(models, future); }).exceptionally(ex -> { future.completeExceptionally(ex); return null; }); }); return future; } private void _updateModels(List models, CompletableFuture future) { Platform.runLater(() -> { try { updateTableDataSet(models); allowResize = true; // 恢复调整 future.complete(null); } catch (Exception e) { allowResize = true; // 恢复调整 future.completeExceptionally(e); } }); } /** * 更新表格数据 * * @param models */ protected void updateTableDataSet(List models) { long timeMillis = System.currentTimeMillis(); // 清除所有选择状态,避免选择状态混乱 if (getTableView() != null && getTableView().getSelectionModel() != null) { getTableView().getSelectionModel().clearSelection(); } // 先清空再设置新数据,避免数据叠加 dataSet.clear(); if (models != null) { dataSet.addAll(models); } // 强制刷新表格布局 if (getTableView() != null) { getTableView().requestLayout(); getTableView().refresh(); } long timeCost = System.currentTimeMillis() - timeMillis; if (logger.isDebugEnabled()) { logger.debug("update table dataSet cost: {} ms", timeCost); } } /** * 加载表格数据 * * @return */ protected List loadTableData() { Map params = getSpecification(); ViewModelService service = getViewModelService(); long timeMillis = System.currentTimeMillis(); Page page = service.findAll(params, getPageable()); long timeCost = System.currentTimeMillis() - timeMillis; if (logger.isDebugEnabled()) { logger.debug("load table data cost: {} ms", timeCost); } if (timeCost > 1000) { controller.setStatus("used " + timeCost + " ms"); } updateFooter(page); return page.map(service::from).toList(); } /** * 异步加载表格数据 * * @param queryService * @param future */ private void asyncLoadTableData(QueryService queryService, CompletableFuture future) { queryService.asyncFindAll(getSpecification(), getPageable()).whenComplete((result, ex) -> { if (ex != null) { future.completeExceptionally(ex); return; } updateFooter(result); List models = result.map(getViewModelService()::from).toList(); _updateModels(models, future); }); } /** * 获取ViewModelService * * @return */ protected ViewModelService getViewModelService() { ViewModelService service = controller.getViewModelService(); if (service == null) { throw new IllegalArgumentException("ViewModelService is null"); } return service; } /** * 获取查询条件 * * @return */ public Map getSpecification() { TextField field = controller.searchKeyField; if (field != null) { return getViewModelService().getSpecification(field.getText()); } return null; } /** * 获取查询条件 * * @param searchText * @return */ protected Map getSpecification(String searchText) { return getViewModelService().getSpecification(searchText); } /** * 当表格行被双击时触发 * * @param item 被双击的行数据 */ protected void onTableRowDoubleClickedAction(TV item) { } /** * 更新页脚 * * @param page */ protected void updateFooter(Page page) { Platform.runLater(() -> { controller.previousPageBtn.setDisable(!page.hasPrevious()); controller.nextPageBtn.setDisable(!page.hasNext()); currentPageNumber.set(page.getNumber()); controller.setStatus( (page.getNumber() + 1) + "/" + page.getTotalPages() + " 页, 总 " + page.getTotalElements() + " 条"); }); } /** * 获取表格排序 * * @return */ public List getTableOrders() { return TableViewUtils.getOrders(getTableView()); } /** * 获取表格排序 * * @return */ public Sort getSortByTable() { if (getTableView() == null) { return Sort.unsorted(); } return Sort.by(getTableOrders()); } /** * 获取分页参数 * * @return */ public Pageable getPageable() { Sort sort = getSortByTable(); return currentPageable.withSort(sort); } /** * 显示在当前窗口为父窗口的新窗口 * * @param 控制器类型 * @param clz 控制器类 * @param model 数据 */ protected > void showInOwner(Class clz, TV model) { BaseController.show(clz, model, getTableView().getScene().getWindow()); } }