Files
contract-manager/client/src/main/java/com/ecep/contract/controller/AbstEntityManagerSkin.java
2025-09-03 20:56:44 +08:00

588 lines
18 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.controller.table.TableTabSkin;
import com.ecep.contract.util.UITools;
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.model.IdentityEntity;
import com.ecep.contract.service.ViewModelService;
import com.ecep.contract.util.TableViewUtils;
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 <T> Entity 的类型
* @param <TV> Entity 对应的ViewModel
* @param <SKIN> Skin 的类型
* @param <C>
*/
public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>, SKIN extends ManagerSkin, C extends AbstManagerWindowController<T, TV, SKIN>>
implements ManagerSkin, TableTabSkin<T, TV>, EditableEntityTableTabSkin<T, TV> {
private static final Logger logger = LoggerFactory.getLogger(AbstEntityManagerSkin.class);
/**
*
*/
protected C controller;
protected CompletableFuture<Void> loadedFuture;
protected ObservableList<TV> 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 TableView<TV> 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<TV> 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<TV> 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<TV> 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 <K>
* @param event
* @param propGetter
*/
protected <K> void acceptCellEditEvent(TableColumn.CellEditEvent<TV, K> event,
Function<TV, Property<K>> propGetter) {
TV row = event.getRowValue();
Property<K> property = propGetter.apply(row);
property.setValue(event.getNewValue());
try {
saveRowData(row);
} catch (Exception e) {
handleException("保存出错", e);
}
}
protected boolean deleteRow(TV row) {
ViewModelService<T, TV> 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<T, TV> service = getViewModelService();
if (service != null) {
T entity = service.createNewEntity();
if (entity != null) {
return entity;
}
}
return null;
}
protected TV createNewViewModel() {
ViewModelService<T, TV> service = getViewModelService();
if (service != null) {
TV model = service.createNewViewModel();
if (model != null) {
return model;
}
}
return null;
}
protected void saveRowData(TV row) {
ViewModelService<T, TV> 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<T, TV> service = getViewModelService();
getViewModelService().delete(entity);
}
/**
* 保存行数据
*
* @param entity
* @return
*/
public T saveRowData(T entity) {
if (entity == null) {
return null;
}
ViewModelService<T, TV> 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<Void> _reloadTableData() {
CompletableFuture<Void> future = new CompletableFuture<>();
Platform.runLater(() -> {
allowResize = false; // 禁用调整
dataSet.clear();
runAsync(() -> {
controller.setStatus("载入中...");
List<TV> 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;
}
/**
* 更新表格数据
*
* @param models
*/
protected void updateTableDataSet(List<TV> 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<TV> loadTableData() {
Map<String, Object> params = getSpecification();
ViewModelService<T, TV> service = getViewModelService();
long timeMillis = System.currentTimeMillis();
Page<T> 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();
}
/**
* 获取ViewModelService
*
* @return
*/
protected ViewModelService<T, TV> getViewModelService() {
ViewModelService<T, TV> service = controller.getViewModelService();
if (service == null) {
throw new IllegalArgumentException("ViewModelService is null");
}
return service;
}
/**
* 获取查询条件
*
* @return
*/
public Map<String, Object> getSpecification() {
TextField field = controller.searchKeyField;
if (field != null) {
return getViewModelService().getSpecification(field.getText());
}
return null;
}
/**
* 获取查询条件
*
* @param searchText
* @return
*/
protected Map<String, Object> getSpecification(String searchText) {
return getViewModelService().getSpecification(searchText);
}
/**
* 当表格行被双击时触发
*
* @param item 被双击的行数据
*/
protected void onTableRowDoubleClickedAction(TV item) {
}
/**
* 更新页脚
*
* @param page
*/
protected void updateFooter(Page<T> 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<Sort.Order> 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 <Controller> 控制器类型
* @param clz 控制器类
* @param model 数据
*/
protected <Controller extends AbstEntityController<T, TV>> void showInOwner(Class<Controller> clz, TV model) {
BaseController.show(clz, model, getTableView().getScene().getWindow());
}
}