# 客户端 Controller 类规则 ## 1. 目录结构 客户端控制器位于`client/src/main/java/com/ecep/contract/controller/`目录下,按业务模块组织: - **根目录**:包含基础控制器、抽象控制器和主窗口控制器 - **业务子包**:按业务领域组织,如`company/`、`project/`、`contract/`、`vendor/`等 - **tab/子包**:包含所有Tab皮肤控制器和相关接口、抽象类 - **table/子包**:包含表格相关控制器和单元格实现 ## 2. 命名规范 - **基础控制器**:`BaseController.java` - **抽象控制器**:以`Abst`开头,如`AbstEntityController.java`、`AbstManagerWindowController.java` - **窗口控制器**:以`WindowController`结尾,如`HomeWindowController.java` - **管理窗口控制器**:以`ManagerWindowController`结尾,如`CompanyManagerWindowController.java` - **Tab皮肤控制器**:以`TabSkin`结尾,如`CompanyTabSkinBase.java` - **管理器皮肤**:以`ManagerSkin`结尾,如`CompanyManagerSkin.java` - **组件控制器**:如`OkHttpLoginController.java` ## 3. 继承关系 控制器类遵循以下继承层次结构: - `BaseController`:所有控制器的基础类,提供窗口显示、异常处理等通用功能 - `AbstEntityBasedController`:基于实体的控制器抽象类 - `AbstManagerWindowController`:管理窗口控制器的抽象类,包含搜索、分页等功能 - `AbstEntityController>`:实体详情窗口控制器的抽象类 - 具体业务窗口控制器,如`CompanyWindowController`、`ProjectWindowController`等 Tab相关控制器继承关系: - `Skin`:皮肤接口基础 - `TabSkin`:Tab皮肤接口 - `AbstGenericTabSkin`:通用Tab皮肤抽象类 - `AbstEntityBasedTabSkin, T extends IdentityEntity, V extends IdentityViewModel>`:基于实体的Tab皮肤抽象类 - 具体业务Tab皮肤控制器,如`CompanyTabSkinBase.java`、`ContractTabSkinFiles.java`等 ## 4. 注解使用 客户端控制器类应使用以下注解: - `@Component`:声明为Spring组件,使其可被Spring容器管理 - `@Scope("prototype")`:设置为原型作用域,确保每次请求创建新实例 - `@Lazy`:延迟加载,提高应用启动性能 - `@FxmlPath("/ui/[业务模块]/[文件名].fxml")`:指定对应的FXML文件路径 - `@Autowired`:自动注入依赖的服务层组件 ## 5. FXML文件规范 - **文件位置**:FXML文件通常位于`/client/src/main/resources/ui/`目录下,按业务模块组织子目录 - **命名规范**:使用小写字母和下划线,如`home.fxml`、`company/company.fxml` - **关联方式**:通过`@FxmlPath`注解指定控制器对应的FXML文件路径 - **组件ID**:FXML文件中组件的id应与控制器中对应的字段名保持一致(驼峰命名法) ## 6. 控制器方法规范 ### 6.1 初始化方法 - `initialize()`:控制器初始化方法,用于设置UI组件初始状态、绑定事件监听器等 - 此方法会在FXML加载完成后自动调用 ### 6.2 窗口生命周期方法 - `onShown(WindowEvent windowEvent)`:窗口显示时触发的方法 - 用于执行窗口显示后的初始化操作,如数据加载、组件状态更新等 - 通常需要调用`super.onShown(windowEvent)`确保父类逻辑执行 ### 6.3 窗口显示方法 - `public static void show()`:静态方法,用于便捷地显示控制器对应的窗口 - 通常有多个重载版本,支持不同参数(如viewModel、owner窗口等) - 内部调用`BaseController.show()`方法实现窗口显示逻辑 ### 6.4 Tab相关方法 Tab皮肤控制器包含以下核心方法: - `install()`:安装Tab皮肤,加载FXML并初始化UI组件 - `initializeUIComponents()`:初始化UI组件,设置数据绑定和事件监听 - `getTab()`:获取对应的Tab对象 - `save()`:保存Tab中的数据变更 - `dispose()`:释放资源,清理监听器等 ## 7. 字段规范 - **UI组件字段**:与FXML文件中的组件id对应,使用`public`访问修饰符 - **服务依赖字段**:使用`@Autowired`注解注入,通常使用`private`访问修饰符 - **ViewModel字段**:通常作为控制器的核心数据模型,提供`getter/setter`方法 - **命名规范**:使用驼峰命名法,如`nameField`、`tabPane`、`saveBtn`等 ## 8. 数据绑定与更新 - **ViewModel绑定**:控制器通常通过ViewModel与实体数据进行绑定 - **异步数据加载**:使用`CompletableFuture`进行异步数据加载,避免阻塞UI线程 - **UI更新**:在JavaFX应用线程中更新UI组件,可使用`Platform.runLater()`确保线程安全 ## 9. 事件处理 - **按钮点击事件**:通过`setOnAction()`方法绑定事件处理器 - **表单字段变更**:可通过JavaFX的属性绑定机制监听字段变更 - **Tab切换事件**:实现`onTabShown()`方法处理Tab显示事件 ## 10. 异常处理 - 使用`handleException()`方法统一处理异常 - 异常信息通常会显示在状态栏并记录日志 - 异步操作中的异常应通过`exceptionally()`或`handle()`方法捕获处理 ## 11. 最佳实践 - **职责分离**:控制器应专注于UI交互和数据展示,业务逻辑应委托给服务层 - **避免重复代码**:利用抽象类和接口提取通用逻辑 - **资源管理**:及时释放资源,避免内存泄漏 - **线程安全**:确保在JavaFX应用线程中更新UI - **代码可读性**:添加适当的注释,遵循命名规范 ## 12. 示例代码结构 ```java @Lazy @Scope("prototype") @Component @FxmlPath("/ui/[模块名]/[文件名].fxml") public class [业务]WindowController extends AbstEntityController<[Vo类型], [ViewModel类型]> { private static final Logger logger = LoggerFactory.getLogger([业务]WindowController.class); @Autowired private [业务]Service [业务]Service; // UI组件字段 public BorderPane root; public TabPane tabPane; public TextField nameField; // ...其他UI组件 public static void show([Vo类型] entity, Window window) { show([ViewModel类型].from(entity), window); } public static void show([ViewModel类型] viewModel, Window window) { BaseController.show([业务]WindowController.class, viewModel, window); } @Override public void initialize() { // 初始化UI组件和事件监听 } @Override public void onShown(WindowEvent windowEvent) { super.onShown(windowEvent); // 窗口显示后的逻辑 } } ``` ## 13. TableCell 工厂模式规范 ### 13.1 工厂方法命名 - TableCell类应提供静态工厂方法`forTableColumn`,用于创建单元格工厂 - 方法命名必须为`forTableColumn`,保持一致性 ### 13.2 工厂方法参数 - 工厂方法应接收服务层参数,如`BankService` - 参数类型应与TableCell构造函数所需的服务类型一致 ### 13.3 工厂方法返回类型 - 返回类型应为`Callback, javafx.scene.control.TableCell>` - 其中`V`为ViewModel类型,`T`为单元格值类型 ### 13.4 工厂方法实现 - 在工厂方法内部,创建并返回TableCell实例 - 使用泛型参数确保类型安全 ### 13.5 使用方式 - 在设置表格列的单元格工厂时,应调用TableCell的静态工厂方法 - 避免直接使用`new TableCell<>(service)`的方式创建实例 ### 13.6 示例代码 ```java /** * 银行单元格 */ @NoArgsConstructor public class BankTableCell extends AsyncUpdateTableCell { /** * 创建单元格工厂 * * @param bankService 银行服务 * @return 单元格工厂 */ public static Callback, javafx.scene.control.TableCell> forTableColumn( BankService bankService) { return param -> new BankTableCell(bankService); } public BankTableCell(BankService service) { setService(service); } @Override protected BankService getServiceBean() { return getBean(BankService.class); } } ``` ### 13.7 使用示例 ```java // 推荐方式:使用工厂方法 column.setCellFactory(BankTableCell.forTableColumn(getBankService())); // 不推荐方式:直接实例化 // column.setCellFactory(param -> new BankTableCell<>(getBankService()));