Files
contract-manager/src/main/java/com/ecep/contract/manager/ui/AbstEntityManagerSkin.java
songqq 524fccc4ed refactor(ui): 重构用户界面组件和架构
- 将 AbstEntityBasedTabSkin 和 AbstEntityTableTabSkin 移到 ui.tab 包
- 创建新的 AsyncUpdateTableCell 类用于异步加载数据
- 重构 CompanyTableCell 以使用新的 AsyncUpdateTableCell
- 更新多个控制器以使用新的 tab 包和 cell 类
- 调整导入路径和包结构以适应重构
2025-08-23 22:53:09 +08:00

464 lines
16 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.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 <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();
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) {
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 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);
}
}
}
protected <K> void acceptCellEditEvent(TableColumn.CellEditEvent<TV, K> event, Function<TV, Property<K>> function) {
TV row = event.getRowValue();
Property<K> property = function.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);
}
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<Void> _reloadTableData() {
CompletableFuture<Void> future = new CompletableFuture<>();
Platform.runLater(() -> {
dataSet.clear();
runAsync(() -> {
controller.setStatus("载入中...");
List<TV> models = loadTableData();
Platform.runLater(() -> {
try {
updateTableDataSet(models);
future.complete(null);
} catch (Exception e) {
future.completeExceptionally(e);
}
});
}).exceptionally(ex -> {
future.completeExceptionally(ex);
return null;
});
});
return future;
}
protected void updateTableDataSet(List<TV> models) {
long timeMillis = System.currentTimeMillis();
dataSet.setAll(models);
long timeCost = System.currentTimeMillis() - timeMillis;
if (logger.isDebugEnabled()) {
logger.debug("update table dataSet cost: {} ms", timeCost);
}
}
protected List<TV> loadTableData() {
Specification<T> spec = getSpecification();
ViewModelService<T, TV> service = getViewModelService();
long timeMillis = System.currentTimeMillis();
Page<T> 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<T, TV> getViewModelService() {
ViewModelService<T, TV> service = controller.getViewModelService();
if (service == null) {
if (logger.isWarnEnabled()) {
logger.warn("ViewModelService is null");
}
}
return service;
}
public Specification<T> getSpecification() {
TextField field = controller.searchKeyField;
if (field != null) {
return getViewModelService().getSpecification(field.getText());
}
return null;
}
protected Specification<T> getSpecification(String searchText) {
return controller.getViewModelService().getSpecification(searchText);
}
/**
* 当表格行被双击时触发
*
* @param item 被双击的行数据
*/
protected void onTableRowDoubleClickedAction(TV item) {
}
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() + "");
});
}
public List<Sort.Order> getTableOrders() {
return TableViewUtils.getOrders(getTableView());
}
public Sort getSortByTable() {
return Sort.by(getTableOrders());
}
public Pageable getPageable() {
Sort sort = getSortByTable();
return currentPageable.withSort(sort);
}
protected <Controller extends AbstEntityController<T, TV>> void showInOwner(Class<Controller> clz, TV model) {
BaseController.show(clz, model, getTableView().getScene().getWindow());
}
}