Files
contract-manager/.trae/rules/client_controller_rules.md
songqq e761990ebf feat: 实现SMB文件服务并优化合同文件管理
- 新增SmbFileService服务类,支持SMB/CIFS协议的文件操作
- 修改合同文件管理逻辑,支持SMB路径检查与目录创建
- 优化BankTableCell实现工厂模式并更新相关文档
- 调整Redis配置并添加连接测试
- 修复合同发票视图模型的时间处理问题
- 更新项目版本至0.0.134-SNAPSHOT
2025-11-12 16:32:03 +08:00

223 lines
8.3 KiB
Markdown
Raw Permalink 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.

# 客户端 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<T extends IdentityEntity, TV extends IdentityViewModel<T>>`:实体详情窗口控制器的抽象类
- 具体业务窗口控制器,如`CompanyWindowController``ProjectWindowController`
Tab相关控制器继承关系
- `Skin`:皮肤接口基础
- `TabSkin`Tab皮肤接口
- `AbstGenericTabSkin<C extends BaseController>`通用Tab皮肤抽象类
- `AbstEntityBasedTabSkin<C extends AbstEntityController<T, V>, T extends IdentityEntity, V extends IdentityViewModel<T>>`基于实体的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.TableColumn<V, T>, javafx.scene.control.TableCell<V, T>>`
- 其中`V`为ViewModel类型`T`为单元格值类型
### 13.4 工厂方法实现
- 在工厂方法内部创建并返回TableCell实例
- 使用泛型参数确保类型安全
### 13.5 使用方式
- 在设置表格列的单元格工厂时应调用TableCell的静态工厂方法
- 避免直接使用`new TableCell<>(service)`的方式创建实例
### 13.6 示例代码
```java
/**
* 银行单元格
*/
@NoArgsConstructor
public class BankTableCell<T> extends AsyncUpdateTableCell<T, Integer, BankVo> {
/**
* 创建单元格工厂
*
* @param bankService 银行服务
* @return 单元格工厂
*/
public static <V> Callback<javafx.scene.control.TableColumn<V, Integer>, javafx.scene.control.TableCell<V, Integer>> forTableColumn(
BankService bankService) {
return param -> new BankTableCell<V>(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()));