Compare commits

...

2 Commits

Author SHA1 Message Date
f810532824 fix: 将 AsyncUpdateTableCell 的日志级别从 debug 调整为 warn
修改日志级别以减少不必要的调试信息输出,仅保留警告及以上级别的日志
2025-08-27 14:44:45 +08:00
fb28bac53a refactor(ui): 优化表格数据加载和异步单元格更新逻辑
修复表格高度调整时频繁触发数据加载的问题,增加调整阈值判断
改进异步单元格更新逻辑,增加取消机制和状态检查
重构常量命名,使用更清晰的KEY_SYNC_BY前缀
添加日志记录和注释说明
2025-08-27 14:43:55 +08:00
11 changed files with 145 additions and 37 deletions

View File

@@ -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/>

View File

@@ -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()));
}
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -19,6 +19,9 @@ import org.springframework.util.StringUtils;
import java.util.List;
/**
* 合同支付计划服务
*/
@Lazy
@Service
@CacheConfig(cacheNames = "contract-pay-plan")

View File

@@ -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());
}
}

View File

@@ -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);

View File

@@ -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> {

View File

@@ -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());
}
}

View File

@@ -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> {

View File

@@ -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="warn"/>
<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"/>