refactor(ui): 优化表格数据加载和异步单元格更新逻辑
修复表格高度调整时频繁触发数据加载的问题,增加调整阈值判断 改进异步单元格更新逻辑,增加取消机制和状态检查 重构常量命名,使用更清晰的KEY_SYNC_BY前缀 添加日志记录和注释说明
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -10,7 +10,7 @@
|
||||
</parent>
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>Contract-Manager</artifactId>
|
||||
<version>0.0.46-SNAPSHOT</version>
|
||||
<version>0.0.47-SNAPSHOT</version>
|
||||
<name>Contract-Manager</name>
|
||||
<description>Contract-Manager</description>
|
||||
<url/>
|
||||
|
||||
@@ -37,7 +37,7 @@ public class ContractSyncTask extends AbstContractRepairTasker {
|
||||
holder.error("无法获取 YongYouU8Repository " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
if (getConfService().getBoolean(ContractCtx.KEY_CONTRACT_SYNC_USE_LATEST_ID)) {
|
||||
if (getConfService().getBoolean(ContractCtx.KEY_SYNC_USE_LATEST_ID)) {
|
||||
syncByLatestId(holder);
|
||||
} else {
|
||||
syncByLatestDate(holder);
|
||||
@@ -45,7 +45,7 @@ public class ContractSyncTask extends AbstContractRepairTasker {
|
||||
}
|
||||
|
||||
private void syncByLatestId(MessageHolder holder) {
|
||||
int latestId = getConfService().getInt(ContractCtx.CONTRACT_LATEST_ID);
|
||||
int latestId = getConfService().getInt(ContractCtx.KEY_SYNC_BY_LATEST_ID);
|
||||
updateTitle("用友U8系统-同步合同,从 " + latestId + " 开始");
|
||||
|
||||
Long total = repository.countAllContracts(latestId);
|
||||
@@ -81,11 +81,11 @@ public class ContractSyncTask extends AbstContractRepairTasker {
|
||||
updateProgress(counter.incrementAndGet(), total);
|
||||
});
|
||||
}
|
||||
getConfService().set(ContractCtx.CONTRACT_LATEST_ID, String.valueOf(reference.get()));
|
||||
getConfService().set(ContractCtx.KEY_SYNC_BY_LATEST_ID, String.valueOf(reference.get()));
|
||||
}
|
||||
|
||||
private void syncByLatestDate(MessageHolder holder) {
|
||||
String strDateTime = getConfService().getString(ContractCtx.CONTRACT_LATEST_DATE);
|
||||
String strDateTime = getConfService().getString(ContractCtx.KEY_SYNC_BY_LATEST_DATE);
|
||||
LocalDateTime latestDateTime = null;
|
||||
if (StringUtils.hasText(strDateTime)) {
|
||||
try {
|
||||
@@ -135,7 +135,7 @@ public class ContractSyncTask extends AbstContractRepairTasker {
|
||||
updateProgress(counter.incrementAndGet(), total);
|
||||
});
|
||||
}
|
||||
getConfService().set(ContractCtx.CONTRACT_LATEST_DATE, String.valueOf(reference.get()));
|
||||
getConfService().set(ContractCtx.KEY_SYNC_BY_LATEST_DATE, String.valueOf(reference.get()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,11 +42,11 @@ public class YongYouU8ConfigWindowController extends BaseController {
|
||||
config1.setPicker(auto_create_company_after);
|
||||
config1.initialize();
|
||||
|
||||
LocalDateConfig config2 = new LocalDateConfig(ContractCtx.CONTRACT_LATEST_DATE);
|
||||
LocalDateConfig config2 = new LocalDateConfig(ContractCtx.KEY_SYNC_BY_LATEST_DATE);
|
||||
config2.setPicker(contract_latest_date);
|
||||
config2.initialize();
|
||||
|
||||
StringConfig config3 = new StringConfig(ContractCtx.CONTRACT_LATEST_ID);
|
||||
StringConfig config3 = new StringConfig(ContractCtx.KEY_SYNC_BY_LATEST_ID);
|
||||
config3.setTextField(contract_latest_id);
|
||||
config3.initialize();
|
||||
|
||||
|
||||
@@ -44,11 +44,23 @@ import java.util.stream.Stream;
|
||||
|
||||
import static com.ecep.contract.manager.SpringApp.getBean;
|
||||
|
||||
/**
|
||||
* 合同上下文
|
||||
*/
|
||||
public class ContractCtx extends AbstractYongYouU8Ctx {
|
||||
public static final String CONTRACT_LATEST_DATE = "cloud.u8.contract.latestDate";
|
||||
public static final String CONTRACT_LATEST_ID = "cloud.u8.contract.latestId";
|
||||
public static final String KEY_CONTRACT_SYNC_USE_LATEST_ID = "cloud.u8.contract.sync.use-latest-id";
|
||||
|
||||
public static final String KEY_PREFIX = "cloud.u8.contract.";
|
||||
/**
|
||||
* 合同同步后的最后一个日期
|
||||
*/
|
||||
public static final String KEY_SYNC_BY_LATEST_DATE = KEY_PREFIX + "latestDate";
|
||||
/**
|
||||
* 合同同步后的最后一个合同ID
|
||||
*/
|
||||
public static final String KEY_SYNC_BY_LATEST_ID = KEY_PREFIX + "latestId";
|
||||
/**
|
||||
* 合同同步时是否使用最后更新的Id来判断更新范围,否则使用最后更新的合同日期来判断更新范围
|
||||
*/
|
||||
public static final String KEY_SYNC_USE_LATEST_ID = KEY_PREFIX + "sync.use-latest-id";
|
||||
|
||||
@Setter
|
||||
private ContractService contractService;
|
||||
@@ -78,7 +90,6 @@ public class ContractCtx extends AbstractYongYouU8Ctx {
|
||||
@Setter
|
||||
private int customerEntityUpdateDelayDays = 1;
|
||||
|
||||
|
||||
public ContractService getContractService() {
|
||||
if (contractService == null) {
|
||||
contractService = getBean(ContractService.class);
|
||||
@@ -661,7 +672,7 @@ public class ContractCtx extends AbstractYongYouU8Ctx {
|
||||
contract.setGuid(guid);
|
||||
contract.setCode(contractId);
|
||||
contract = service.save(contract);
|
||||
holder.info("新建合同:" + contractId+", GUID: "+guid);
|
||||
holder.info("新建合同:" + contractId + ", GUID: " + guid);
|
||||
}
|
||||
}
|
||||
return contract;
|
||||
|
||||
@@ -19,6 +19,9 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同支付计划服务
|
||||
*/
|
||||
@Lazy
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "contract-pay-plan")
|
||||
|
||||
@@ -150,10 +150,10 @@ public class ContractService implements ViewModelService<Contract, ContractViewM
|
||||
*/
|
||||
public void updateLatestIdAndDate(Integer latestId, LocalDateTime latestDate) {
|
||||
if (latestId != null) {
|
||||
confService.set(ContractCtx.CONTRACT_LATEST_ID, String.valueOf(latestId));
|
||||
confService.set(ContractCtx.KEY_SYNC_BY_LATEST_ID, String.valueOf(latestId));
|
||||
}
|
||||
if (latestDate != null) {
|
||||
confService.set(ContractCtx.CONTRACT_LATEST_DATE, latestDate.toString());
|
||||
confService.set(ContractCtx.KEY_SYNC_BY_LATEST_DATE, latestDate.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,10 +195,15 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
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;
|
||||
@@ -208,8 +213,13 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
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);
|
||||
// 只有当行数变化超过一定阈值时才重新加载数据
|
||||
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;
|
||||
}
|
||||
@@ -359,6 +369,7 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
private CompletableFuture<Void> _reloadTableData() {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
Platform.runLater(() -> {
|
||||
allowResize = false; // 禁用调整
|
||||
dataSet.clear();
|
||||
runAsync(() -> {
|
||||
controller.setStatus("载入中...");
|
||||
@@ -366,8 +377,10 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
updateTableDataSet(models);
|
||||
allowResize = true; // 恢复调整
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
allowResize = true; // 恢复调整
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
@@ -381,7 +394,23 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
|
||||
protected void updateTableDataSet(List<TV> models) {
|
||||
long timeMillis = System.currentTimeMillis();
|
||||
dataSet.setAll(models);
|
||||
// 清除所有选择状态,避免选择状态混乱
|
||||
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);
|
||||
|
||||
@@ -6,6 +6,13 @@ import com.ecep.contract.manager.ds.other.model.IdentityEntity;
|
||||
import com.ecep.contract.manager.ds.other.service.IEntityService;
|
||||
import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
|
||||
|
||||
/**
|
||||
* 视图模型服务接口
|
||||
*
|
||||
* @param <T> 实体类型
|
||||
* @param <TV> 视图模型类型
|
||||
* @author 2025-08-02
|
||||
*/
|
||||
public interface ViewModelService<T extends IdentityEntity, TV extends IdentityViewModel<T>>
|
||||
extends IEntityService<T> {
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.ecep.contract.manager.ui.table.cell;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ecep.contract.manager.Desktop;
|
||||
import com.ecep.contract.manager.ds.other.model.IdentityEntity;
|
||||
@@ -18,6 +20,8 @@ import javafx.application.Platform;
|
||||
* @param <T>
|
||||
*/
|
||||
public class AsyncUpdateTableCell<V, T extends IdentityEntity> extends javafx.scene.control.TableCell<V, T> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AsyncUpdateTableCell.class);
|
||||
|
||||
/**
|
||||
* 转换为文本
|
||||
*/
|
||||
@@ -32,6 +36,20 @@ public class AsyncUpdateTableCell<V, T extends IdentityEntity> extends javafx.sc
|
||||
}
|
||||
|
||||
private IEntityService<T> service;
|
||||
Future<?> syncFuture;
|
||||
|
||||
/**
|
||||
* 通过 #setService 设置服务类,或者子类实现, 提供服务类
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected IEntityService<T> getServiceBean() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String format(T entity) {
|
||||
return toString(entity);
|
||||
}
|
||||
|
||||
public void setService(IEntityService<T> service) {
|
||||
this.service = service;
|
||||
@@ -44,13 +62,15 @@ public class AsyncUpdateTableCell<V, T extends IdentityEntity> extends javafx.sc
|
||||
return service;
|
||||
}
|
||||
|
||||
protected IEntityService<T> getServiceBean() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(T item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
// 取消之前的异步任务
|
||||
if (syncFuture != null) {
|
||||
syncFuture.cancel(true);
|
||||
syncFuture = null;
|
||||
}
|
||||
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
return;
|
||||
@@ -59,23 +79,45 @@ public class AsyncUpdateTableCell<V, T extends IdentityEntity> extends javafx.sc
|
||||
setText(format(item));
|
||||
return;
|
||||
}
|
||||
|
||||
setText("# " + item.getId());
|
||||
submit(this::asyncLoadAndUpdate);
|
||||
syncFuture = submit(this::asyncLoadAndUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体是否已初始化
|
||||
*
|
||||
* @param proxy
|
||||
* @return
|
||||
*/
|
||||
protected boolean isInitialized(T proxy) {
|
||||
return Hibernate.isInitialized(proxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化实体
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected T initialize() {
|
||||
return getService().findById(getItem().getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交异步任务
|
||||
*
|
||||
* @param var1 异步任务
|
||||
* @return
|
||||
*/
|
||||
Future<?> submit(Runnable var1) {
|
||||
return Desktop.instance.getExecutorService().submit(var1);
|
||||
}
|
||||
|
||||
public String format(T entity) {
|
||||
return toString(entity);
|
||||
}
|
||||
|
||||
protected void asyncLoadAndUpdate() {
|
||||
if (getItem() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
T entity = initialize();
|
||||
String formated;
|
||||
try {
|
||||
@@ -84,14 +126,26 @@ public class AsyncUpdateTableCell<V, T extends IdentityEntity> extends javafx.sc
|
||||
formated = e.getMessage();
|
||||
}
|
||||
String texted = formated;
|
||||
|
||||
// 保存当前需要更新的项目信息,避免闭包引用导致的问题
|
||||
final T updatedEntity = entity;
|
||||
final String updatedText = texted;
|
||||
final T finalCurrentLoadingItem = getItem();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
setText(texted);
|
||||
setItem(entity);
|
||||
// 检查单元格是否仍然显示相同的项目
|
||||
// 如果单元格已被重用或当前项目已改变,则不更新
|
||||
if (isEmpty() || getItem() == null) {
|
||||
logger.debug("Skipping async update - cell reused ");
|
||||
return;
|
||||
}
|
||||
if (getItem().getId() != finalCurrentLoadingItem.getId()) {
|
||||
logger.debug("Skipping async update - cell item changed");
|
||||
return;
|
||||
}
|
||||
setItem(updatedEntity);
|
||||
setText(updatedText);
|
||||
});
|
||||
}
|
||||
|
||||
protected T initialize() {
|
||||
return getService().findById(getItem().getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.ecep.contract.manager.ui.table.cell;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import static com.ecep.contract.manager.SpringApp.getBean;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
/**
|
||||
* 公司单元格
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class CompanyTableCell<V> extends AsyncUpdateTableCell<V, Company> {
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<logger name="com.ecep.contract.manager.ui.service" level="info"/>
|
||||
|
||||
<logger name="com.ecep.contract.manager.ui.table.AbstEntityTableTabSkin" level="debug"/>
|
||||
<logger name="com.ecep.contract.manager.ui.table.cell.AsyncUpdateTableCell" level="debug"/>
|
||||
<logger name="com.ecep.contract.manager.ui.AbstEntityManagerSkin" level="debug"/>
|
||||
<logger name="com.ecep.contract.manager.ui.BaseController" level="debug"/>
|
||||
<logger name="com.ecep.contract.manager.ui.controller" level="info"/>
|
||||
|
||||
Reference in New Issue
Block a user