package com.ecep.contract.manager.ui; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin; import com.ecep.contract.manager.ui.table.TableTabSkin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 org.springframework.data.jpa.domain.Specification; import com.ecep.contract.manager.ds.other.model.IdentityEntity; import com.ecep.contract.manager.ds.other.vo.IdentityViewModel; import com.ecep.contract.manager.util.TableViewUtils; import com.ecep.contract.manager.util.UITools; 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(); public AbstEntityManagerSkin(C controller) { this.controller = controller; } @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 boolean allowResize = 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); } } } protected void acceptCellEditEvent(TableColumn.CellEditEvent event, Function> function) { TV row = event.getRowValue(); Property property = function.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); } public T loadRowData(TV row) { return getViewModelService().findById(row.getId().get()); } public void deleteRowData(T entity) { getViewModelService().delete(entity); } public T saveRowData(T entity) { return getViewModelService().save(entity); } // 记录延时任务信息 private ScheduledFuture loadTableDataSetFuture; @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("载入中..."); List models = loadTableData(); Platform.runLater(() -> { try { updateTableDataSet(models); allowResize = true; // 恢复调整 future.complete(null); } catch (Exception e) { allowResize = true; // 恢复调整 future.completeExceptionally(e); } }); }).exceptionally(ex -> { future.completeExceptionally(ex); return null; }); }); return future; } 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); } } protected List loadTableData() { Specification spec = getSpecification(); ViewModelService service = getViewModelService(); long timeMillis = System.currentTimeMillis(); Page page = service.findAll(spec, 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(); } protected ViewModelService getViewModelService() { ViewModelService service = controller.getViewModelService(); if (service == null) { if (logger.isWarnEnabled()) { logger.warn("ViewModelService is null"); } } return service; } public Specification getSpecification() { TextField field = controller.searchKeyField; if (field != null) { return getViewModelService().getSpecification(field.getText()); } return null; } protected Specification getSpecification(String searchText) { return controller.getViewModelService().getSpecification(searchText); } /** * 当表格行被双击时触发 * * @param item 被双击的行数据 */ protected void onTableRowDoubleClickedAction(TV item) { } 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() + " 条"); }); } public List getTableOrders() { return TableViewUtils.getOrders(getTableView()); } public Sort getSortByTable() { return Sort.by(getTableOrders()); } public Pageable getPageable() { Sort sort = getSortByTable(); return currentPageable.withSort(sort); } protected > void showInOwner(Class clz, TV model) { BaseController.show(clz, model, getTableView().getScene().getWindow()); } }