diff --git a/.trae/rules/server_task_rules.md b/.trae/rules/server_task_rules.md index 4610cc0..68a134b 100644 --- a/.trae/rules/server_task_rules.md +++ b/.trae/rules/server_task_rules.md @@ -86,8 +86,8 @@ public interface WebSocketServerTasker extends Callable { ```json { "tasks": { - "CustomTasker": "com.ecep.contract.service.tasker.CustomTasker" - // 其他已注册任务... + "CustomTasker": "com.ecep.contract.service.tasker.CustomTasker", + "OtherTasker": "..." }, "descriptions": "任务注册信息, 客户端的任务可以通过 WebSocket 调用" } @@ -137,6 +137,7 @@ WebSocketServerTaskManager类在启动时会读取`tasker_mapper.json`文件, protected Object execute(MessageHolder holder) throws Exception { // 调用父类初始化 super.execute(holder); + updateTitle("同步修复所有合同"); // 执行具体业务逻辑 repair(holder); return null; // 通常返回 null 或处理结果 @@ -146,7 +147,6 @@ WebSocketServerTaskManager类在启动时会读取`tasker_mapper.json`文件, 5. **使用更新方法提供状态反馈**:在执行过程中使用 `updateTitle`、`updateProgress` 等方法提供实时反馈 ```java - updateTitle("同步修复所有合同"); updateProgress(counter.incrementAndGet(), total); ``` diff --git a/client/src/main/java/com/ecep/contract/WebSocketClientService.java b/client/src/main/java/com/ecep/contract/WebSocketClientService.java index 0b85b40..83fd45a 100644 --- a/client/src/main/java/com/ecep/contract/WebSocketClientService.java +++ b/client/src/main/java/com/ecep/contract/WebSocketClientService.java @@ -353,14 +353,6 @@ public class WebSocketClientService { return online; } - public void withSession(Consumer sessionConsumer) { - WebSocketClientSession session = createSession(); - try { - sessionConsumer.accept(session); - } finally { - // closeSession(session);vvvv - } - } public void closeSession(WebSocketClientSession session) { if (session != null) { diff --git a/client/src/main/java/com/ecep/contract/WebSocketClientSession.java b/client/src/main/java/com/ecep/contract/WebSocketClientSession.java index e0a18d0..b681182 100644 --- a/client/src/main/java/com/ecep/contract/WebSocketClientSession.java +++ b/client/src/main/java/com/ecep/contract/WebSocketClientSession.java @@ -1,22 +1,19 @@ package com.ecep.contract; +import com.ecep.contract.constant.WebSocketConstant; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; + import java.beans.PropertyDescriptor; import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.logging.Level; -import javafx.application.Platform; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeanUtils; - -import com.ecep.contract.constant.WebSocketConstant; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; - -import lombok.Getter; - /** * */ @@ -27,6 +24,8 @@ public class WebSocketClientSession { */ @Getter private final String sessionId = UUID.randomUUID().toString(); + @Getter + private boolean done = false; private WebSocketClientTasker tasker; @@ -69,6 +68,8 @@ public class WebSocketClientSession { handleAsStart(args); } else if (type.equals("done")) { handleAsDone(args); + done = true; + close(); } else { tasker.updateMessage(java.util.logging.Level.INFO, "未知的消息类型: " + node.toString()); } @@ -83,7 +84,6 @@ public class WebSocketClientSession { private void handleAsDone(JsonNode args) { tasker.updateMessage(java.util.logging.Level.INFO, "任务完成"); - close(); } private void handleAsProgress(JsonNode args) { diff --git a/client/src/main/java/com/ecep/contract/WebSocketClientTasker.java b/client/src/main/java/com/ecep/contract/WebSocketClientTasker.java index 793346b..b0e40a0 100644 --- a/client/src/main/java/com/ecep/contract/WebSocketClientTasker.java +++ b/client/src/main/java/com/ecep/contract/WebSocketClientTasker.java @@ -5,20 +5,22 @@ import com.fasterxml.jackson.core.JsonProcessingException; import java.util.Locale; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; /** * WebSocket客户端任务接口 * 定义了所有通过WebSocket与服务器通信的任务的通用方法 * 包括任务名称、更新消息、更新标题、更新进度等操作 - * + *

* 所有通过WebSocket与服务器通信的任务类都应实现此接口, 文档参考 .trace/rules/client_task_rules.md */ public interface WebSocketClientTasker { - /**s + /** + * s * 获取任务名称 * 任务名称用于唯一标识任务, 服务器端会根据任务名称来调用对应的任务处理函数 - * + * * @return 任务名称 */ String getTaskName(); @@ -26,7 +28,7 @@ public interface WebSocketClientTasker { /** * 更新任务执行过程中的消息 * 客户端可以通过此方法向用户展示任务执行过程中的重要信息或错误提示 - * + * * @param level 消息级别, 用于区分不同类型的消息, 如INFO, WARNING, SEVERE等 * @param message 消息内容, 可以是任意字符串, 用于展示给用户 */ @@ -35,7 +37,7 @@ public interface WebSocketClientTasker { /** * 更新任务标题 * 客户端可以通过此方法向用户展示任务的当前执行状态或重要信息 - * + * * @param title 任务标题 */ void updateTitle(String title); @@ -43,7 +45,7 @@ public interface WebSocketClientTasker { /** * 更新任务进度 * 客户端可以通过此方法向用户展示任务的执行进度, 如文件上传进度、数据库操作进度等 - * + * * @param current 当前进度 * @param total 总进度 */ @@ -55,12 +57,15 @@ public interface WebSocketClientTasker { */ default void cancelTask() { // 默认实现为空,由具体任务重写 + // 需要获取到 session + // 发送 cancel 指令 + // 关闭 session.close() } /** * 调用远程WebSocket任务 * 客户端可以通过此方法向服务器提交任务, 并等待服务器返回任务执行结果 - * + * * @param holder 消息持有者,用于记录任务执行过程中的消息 * @param locale 语言环境 * @param args 任务参数 @@ -76,22 +81,31 @@ public interface WebSocketClientTasker { return null; } - webSocketService.withSession(session -> { - try { - session.submitTask(this, locale, args); - holder.info("已提交任务到服务器: " + getTaskName()); - } catch (JsonProcessingException e) { - String errorMsg = "任务提交失败: " + e.getMessage(); - holder.warn(errorMsg); - throw new RuntimeException("任务提交失败: " + e.getMessage(), e); - } - }); + WebSocketClientSession session = webSocketService.createSession(); + try { + session.submitTask(this, locale, args); + holder.info("已提交任务到服务器: " + getTaskName()); + } catch (JsonProcessingException e) { + String errorMsg = "任务提交失败: " + e.getMessage(); + holder.warn(errorMsg); + throw new RuntimeException("任务提交失败: " + e.getMessage(), e); + } + +// while (!session.isDone()) { +// // 使用TimeUnit +// try { +// TimeUnit.SECONDS.sleep(1); +// } catch (InterruptedException e) { +// Thread.currentThread().interrupt(); +// } +// } + return null; } /** * 异步调用远程WebSocket任务 - * + * * @param holder 消息持有者,用于记录任务执行过程中的消息 * @param locale 语言环境 * @param args 任务参数 @@ -113,7 +127,7 @@ public interface WebSocketClientTasker { /** * 生成唯一的任务ID - * + * * @return 唯一的任务ID */ default String generateTaskId() { diff --git a/client/src/main/java/com/ecep/contract/controller/contract/ContractWindowController.java b/client/src/main/java/com/ecep/contract/controller/contract/ContractWindowController.java index d529edb..8b64c0e 100644 --- a/client/src/main/java/com/ecep/contract/controller/contract/ContractWindowController.java +++ b/client/src/main/java/com/ecep/contract/controller/contract/ContractWindowController.java @@ -2,6 +2,7 @@ package com.ecep.contract.controller.contract; import java.io.File; +import javafx.scene.control.*; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -26,13 +27,6 @@ import com.ecep.contract.vo.ContractVo; import javafx.collections.ObservableList; import javafx.event.ActionEvent; -import javafx.scene.control.Button; -import javafx.scene.control.DatePicker; -import javafx.scene.control.Label; -import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; -import javafx.scene.control.TextArea; -import javafx.scene.control.TextField; import javafx.scene.layout.BorderPane; import javafx.stage.Window; import javafx.stage.WindowEvent; @@ -44,6 +38,8 @@ import javafx.stage.WindowEvent; public class ContractWindowController extends AbstEntityController { + + public static void show(ContractVo contract, Window owner) { show(ContractViewModel.from(contract), owner); } @@ -69,6 +65,7 @@ public class ContractWindowController public Button openRelativeCompanyVendorBtn; public TextField nameField; + public CheckBox contractNameLockedCk; public TextField guidField; public TextField codeField; public TextField parentCodeField; diff --git a/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinBase.java b/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinBase.java index bf290f0..1478dc9 100644 --- a/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinBase.java +++ b/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinBase.java @@ -85,7 +85,9 @@ public class ContractTabSkinBase extends AbstContractBasedTabSkin { controller.payWayField.textProperty().bind(viewModel.getPayWay().map(ContractPayWay::getText)); - controller.nameField.textProperty().bind(viewModel.getName()); + controller.nameField.textProperty().bindBidirectional(viewModel.getName()); + controller.nameField.editableProperty().bind(viewModel.getNameLocked()); + controller.contractNameLockedCk.selectedProperty().bindBidirectional(viewModel.getNameLocked()); controller.codeField.textProperty().bind(viewModel.getCode()); controller.parentCodeField.textProperty().bindBidirectional(viewModel.getParentCode()); @@ -351,17 +353,20 @@ public class ContractTabSkinBase extends AbstContractBasedTabSkin { } if (initialDirectory == null) { if (entity.getPayWay() == ContractPayWay.RECEIVE) { - // 根据项目设置初始目录 - ProjectVo project = getProjectService().findById(entity.getProject()); - if (project != null) { - // 根据项目销售方式设置初始目录 - ProjectSaleTypeVo saleType = getSaleTypeService().findById(project.getSaleTypeId()); - if (saleType != null) { - File dir = new File(saleType.getPath()); - if (saleType.isStoreByYear()) { - dir = new File(dir, "20" + project.getCodeYear()); + Integer projectId = entity.getProject(); + if (projectId != null) { + // 根据项目设置初始目录 + ProjectVo project = getProjectService().findById(projectId); + if (project != null) { + // 根据项目销售方式设置初始目录 + ProjectSaleTypeVo saleType = getSaleTypeService().findById(project.getSaleTypeId()); + if (saleType != null) { + File dir = new File(saleType.getPath()); + if (saleType.isStoreByYear()) { + dir = new File(dir, "20" + project.getCodeYear()); + } + initialDirectory = dir; } - initialDirectory = dir; } } } else if (entity.getPayWay() == ContractPayWay.PAY) { diff --git a/client/src/main/java/com/ecep/contract/task/ContractRepairAllTasker.java b/client/src/main/java/com/ecep/contract/task/ContractRepairAllTasker.java index a0e5d73..e876bc9 100644 --- a/client/src/main/java/com/ecep/contract/task/ContractRepairAllTasker.java +++ b/client/src/main/java/com/ecep/contract/task/ContractRepairAllTasker.java @@ -18,12 +18,6 @@ public class ContractRepairAllTasker extends Tasker implements WebSocket super.updateProgress(current, total); } - @Override - public void updateTitle(String title) { - // 使用Tasker的updateTitle方法更新标题 - super.updateTitle(title); - } - @Override public String getTaskName() { return "ContractRepairAllTask"; @@ -34,6 +28,7 @@ public class ContractRepairAllTasker extends Tasker implements WebSocket logger.info("开始执行合同修复任务"); updateTitle("合同数据修复"); Object result = callRemoteTask(holder, getLocale()); + logger.info("合同修复任务执行完成"); return result; } diff --git a/client/src/main/java/com/ecep/contract/task/EmployeesSyncTask.java b/client/src/main/java/com/ecep/contract/task/EmployeesSyncTask.java index b11e83f..ce1ea87 100644 --- a/client/src/main/java/com/ecep/contract/task/EmployeesSyncTask.java +++ b/client/src/main/java/com/ecep/contract/task/EmployeesSyncTask.java @@ -1,13 +1,37 @@ package com.ecep.contract.task; -import com.ecep.contract.MessageHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public class EmployeesSyncTask extends Tasker { +import com.ecep.contract.MessageHolder; +import com.ecep.contract.WebSocketClientTasker; + +/** + * 员工同步任务客户端实现 + * 用于通过WebSocket与服务器通信,执行用友U8系统员工信息同步 + */ +public class EmployeesSyncTask extends Tasker implements WebSocketClientTasker { + private static final Logger logger = LoggerFactory.getLogger(EmployeesSyncTask.class); + + @Override + public String getTaskName() { + return "EmployeesSyncTask"; + } + + @Override + public void updateProgress(long current, long total) { + super.updateProgress(current, total); + } @Override protected Object execute(MessageHolder holder) throws Exception { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'execute'"); - } + // 设置任务标题 + updateTitle("用友U8系统-同步员工信息"); + // 更新任务消息 + updateMessage("开始执行员工信息同步..."); + + // 调用远程WebSocket任务 + return callRemoteTask(holder, getLocale()); + } } diff --git a/client/src/main/java/com/ecep/contract/task/Tasker.java b/client/src/main/java/com/ecep/contract/task/Tasker.java index e1867b2..5df23c0 100644 --- a/client/src/main/java/com/ecep/contract/task/Tasker.java +++ b/client/src/main/java/com/ecep/contract/task/Tasker.java @@ -4,14 +4,11 @@ import java.util.HashMap; import java.util.Locale; import java.util.logging.Level; +import com.ecep.contract.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; -import com.ecep.contract.Desktop; -import com.ecep.contract.Message; -import com.ecep.contract.MessageHolder; -import com.ecep.contract.SpringApp; import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.EmployeeService; import com.ecep.contract.service.SysConfService; @@ -63,6 +60,22 @@ public abstract class Tasker extends Task { return currentUser; } + @Override + public void run() { + if (this instanceof WebSocketClientTasker) { + this.getState(); + } + super.run(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + if (this instanceof WebSocketClientTasker) { + ((WebSocketClientTasker) this).cancelTask(); + } + return super.cancel(mayInterruptIfRunning); + } + @Override protected T call() throws Exception { MessageHolderImpl holder = new MessageHolderImpl(); diff --git a/client/src/main/java/com/ecep/contract/util/UITools.java b/client/src/main/java/com/ecep/contract/util/UITools.java index 99c9ff1..ec8f525 100644 --- a/client/src/main/java/com/ecep/contract/util/UITools.java +++ b/client/src/main/java/com/ecep/contract/util/UITools.java @@ -4,6 +4,8 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.function.Consumer; import java.util.function.Function; @@ -221,15 +223,19 @@ public class UITools { if (task instanceof Tasker tasker) { // 提交任务 - Desktop.instance.getExecutorService().submit(tasker); + try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { + executor.submit(tasker); + } } if (init != null) { init.accept(consumer::test); } dialog.showAndWait(); - if (task.isRunning()) { + if (task.getProgress() < 1) { task.cancel(); } +// if (task.isRunning()) { +// } } private static String printStackTrace(Throwable e) { diff --git a/client/src/main/java/com/ecep/contract/vm/ContractViewModel.java b/client/src/main/java/com/ecep/contract/vm/ContractViewModel.java index ef38556..0bca0e7 100644 --- a/client/src/main/java/com/ecep/contract/vm/ContractViewModel.java +++ b/client/src/main/java/com/ecep/contract/vm/ContractViewModel.java @@ -26,6 +26,10 @@ public class ContractViewModel extends IdentityViewModel { private SimpleStringProperty code = new SimpleStringProperty(); private SimpleStringProperty name = new SimpleStringProperty(); + /** + * 名称是否锁定 + */ + private SimpleBooleanProperty nameLocked = new SimpleBooleanProperty(); private SimpleStringProperty state = new SimpleStringProperty(); /** * 分组 @@ -138,6 +142,7 @@ public class ContractViewModel extends IdentityViewModel { getPayWay().set(c.getPayWay()); getCode().set(c.getCode()); getName().set(c.getName()); + getNameLocked().set(c.isNameLocked()); getState().set(c.getState()); getGroup().set(c.getGroupId()); @@ -198,6 +203,10 @@ public class ContractViewModel extends IdentityViewModel { contract.setName(name.get()); modified = true; } + if (!Objects.equals(nameLocked.get(), contract.isNameLocked())) { + contract.setNameLocked(nameLocked.get()); + modified = true; + } if (!Objects.equals(state.get(), contract.getState())) { contract.setState(state.get()); modified = true; diff --git a/client/src/main/resources/ui/contract/contract.fxml b/client/src/main/resources/ui/contract/contract.fxml index 5ae7042..5dba94e 100644 --- a/client/src/main/resources/ui/contract/contract.fxml +++ b/client/src/main/resources/ui/contract/contract.fxml @@ -1,36 +1,50 @@ - - - + + + + + + + + + + + + + + + + + + + - + + -
- + @@ -40,262 +54,190 @@ - - - - + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + -