Merge branch 'GPU-Instancing' of http://106.12.111.150:4000/basil/vampire-like into GPU-Instancing
This commit is contained in:
commit
56f74eee9a
|
|
@ -91,3 +91,4 @@ Assets/GameMain/Configs/ResourceBuilder.xml
|
||||||
/.dotnet
|
/.dotnet
|
||||||
/.omx
|
/.omx
|
||||||
/.vscode
|
/.vscode
|
||||||
|
/openspec/changes/archive
|
||||||
|
|
@ -1,86 +1,123 @@
|
||||||
# UI 五层架构设计规范(RawData / Controller / View / Context / UseCase)
|
# UI 五层架构设计规范(UseCase / RawData / Controller / Context / View)
|
||||||
|
|
||||||
## 1. 适用范围
|
## 1. 文档目标
|
||||||
|
|
||||||
- 适用目录:`Assets/GameMain/Scripts/UI/*`
|
本文定义一套可长期复用的 UI 分层设计方案,用于约束 UI 模块的职责边界、依赖方向、通信方式和测试策略。
|
||||||
- 重点对象:采用五层拆分的 UI 模块(`MenuScene`、`GameScene`、`General` 下的分层 UI)
|
|
||||||
- 本文不展开 Unity GameFramework 底层实现细节,仅约束项目内 UI 代码组织与协作方式
|
|
||||||
|
|
||||||
## 2. 架构总览
|
- 本文描述的是规范,不是对某个项目现状的总结
|
||||||
|
- 项目内目录或基类只作为落地示例,不改变本文的抽象约束
|
||||||
|
- 本文重点约束 UIForm 级模块;子组件(`Item`、`Area` 等)可只实现 `Context + View`
|
||||||
|
- Unity GameFramework 的底层细节不在本文展开,本文只约束项目内 UI 代码组织
|
||||||
|
|
||||||
UI 模块采用“输入数据 -> 业务编排 -> 展示数据 -> 渲染表现”的分层方式,核心链路如下:
|
## 2. 核心原则
|
||||||
|
|
||||||
1. 外部流程(Procedure/GameState)创建并绑定 UseCase
|
### 2.1 单向数据职责
|
||||||
2. 通过 `GameEntry.UIRouter` 打开指定 UI
|
|
||||||
3. Controller 从 UseCase 取 RawData,并转换为 Context
|
|
||||||
4. View 使用 Context 渲染
|
|
||||||
5. View 通过事件回传交互,Controller 处理后驱动 UseCase 更新,再刷新 View
|
|
||||||
|
|
||||||
简化关系图:
|
UI 的职责链路固定为:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Procedure/GameState
|
外部流程
|
||||||
-> UIRouter
|
-> Controller
|
||||||
-> Controller <-> UseCase
|
-> UseCase
|
||||||
-> Context -> View
|
-> RawData / Result
|
||||||
View --(CustomEvent)--> Controller
|
-> BuildContext
|
||||||
|
-> View
|
||||||
|
|
||||||
|
View
|
||||||
|
--(UI 专用事件)--> Controller
|
||||||
```
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `UseCase` 只负责业务规则、状态推进和纯业务数据生成
|
||||||
|
- `Controller` 是 UI 编排中心,也是唯一允许构建 `Context` 的层
|
||||||
|
- `View` 只负责渲染和抛出交互,不直接处理业务状态
|
||||||
|
|
||||||
|
### 2.2 严格分离业务数据与展示数据
|
||||||
|
|
||||||
|
- `RawData` 是纯业务数据,不承载展示模型
|
||||||
|
- `Context` 是纯展示数据,不进入 `UseCase`
|
||||||
|
- `UseCase` 不能返回 `Context`
|
||||||
|
- `View` 只能消费 `Context`
|
||||||
|
|
||||||
|
### 2.3 Controller 是 UI 唯一外部入口
|
||||||
|
|
||||||
|
对于 UIForm 级模块,外部流程只能通过 `Controller` 驱动 UI:
|
||||||
|
|
||||||
|
- 打开 UI
|
||||||
|
- 关闭 UI
|
||||||
|
- 绑定 `UseCase`
|
||||||
|
- 刷新 UI
|
||||||
|
- 响应 UI 专用事件
|
||||||
|
|
||||||
|
`View` 不作为外部流程的直接依赖对象。
|
||||||
|
|
||||||
## 3. 五层职责定义
|
## 3. 五层职责定义
|
||||||
|
|
||||||
### 3.1 RawData 层
|
### 3.1 UseCase 层
|
||||||
|
|
||||||
职责:承载“业务原始数据”,作为 UseCase 到 Controller 的传输模型。
|
职责:封装 UI 对应的业务用例,负责业务规则、状态推进、校验和纯业务结果输出。
|
||||||
|
|
||||||
约束:
|
|
||||||
|
|
||||||
- 命名:`XXXFormRawData`
|
|
||||||
- 只描述数据,不包含 UI 渲染行为
|
|
||||||
- 可保留领域对象或数据表对象(例如 `DRLevelUpReward`、`WeaponBase`)
|
|
||||||
- 不依赖具体 View 组件
|
|
||||||
|
|
||||||
参考:
|
|
||||||
|
|
||||||
- `Assets/GameMain/Scripts/UI/GameScene/RawData/ShopFormRawData.cs`
|
|
||||||
- `Assets/GameMain/Scripts/UI/GameScene/RawData/LevelUpFormRawData.cs`
|
|
||||||
- `Assets/GameMain/Scripts/UI/MenuScene/RawData/SelectRoleFormRawData.cs`
|
|
||||||
|
|
||||||
### 3.2 UseCase 层
|
|
||||||
|
|
||||||
职责:封装 UI 对应业务用例,负责业务规则、状态推进、数据生成。
|
|
||||||
|
|
||||||
约束:
|
约束:
|
||||||
|
|
||||||
- 实现 `IUIUseCase`
|
- 实现 `IUIUseCase`
|
||||||
- 命名:`XXXFormUseCase`
|
- 命名:`XXXFormUseCase`
|
||||||
- 对外提供 `CreateInitialModel / TryRefresh / Select / Confirm` 等语义化方法
|
- 对外提供语义化方法,例如 `CreateInitialModel`、`TryRefresh`、`Select`、`Confirm`
|
||||||
- 返回 RawData(或结果对象),不直接操作具体 View
|
- 返回值只能是 `RawData` 或纯业务结果对象,例如 `XXXResult`、`XXXActionResult`
|
||||||
|
- 不依赖 `Context`、`View`、`UGuiForm`、`MonoBehaviour` 等 UI 类型
|
||||||
|
- 不负责 UI 资源加载、文本拼装、颜色选择、图标转换等展示处理
|
||||||
|
- 不发布 UI 专用事件
|
||||||
|
|
||||||
参考:
|
适用场景:
|
||||||
|
|
||||||
- `Assets/GameMain/Scripts/UI/GameScene/UseCase/ShopFormUseCase.cs`
|
- UI 会读写领域状态
|
||||||
- `Assets/GameMain/Scripts/UI/GameScene/UseCase/LevelUpFormUseCase.cs`
|
- UI 存在明确业务规则、条件分支、校验或状态推进
|
||||||
- `Assets/GameMain/Scripts/UI/MenuScene/UseCase/SelectRoleFormUseCase.cs`
|
- UI 的交互结果需要被测试和复用
|
||||||
|
|
||||||
### 3.3 Controller 层
|
### 3.2 RawData 层
|
||||||
|
|
||||||
职责:UI 编排层,连接 UseCase 与 View,管理 UI 生命周期、事件订阅、数据转换。
|
职责:承载 `UseCase -> Controller` 的纯业务传输模型。
|
||||||
|
|
||||||
约束:
|
约束:
|
||||||
|
|
||||||
- 继承 `UIFormControllerCommonBase<TContext, TForm>`
|
- 命名:`XXXFormRawData`
|
||||||
|
- 只描述业务数据,不包含 UI 展示行为
|
||||||
|
- 可以包含领域对象、配置对象、标识符、枚举、数值和纯数据集合
|
||||||
|
- 不允许依赖 `Context`、`View`、`Sprite`、`TMP_Text` 等展示相关类型
|
||||||
|
- 不允许直接使用 `XXXItemContext`、`XXXFormContext` 作为字段类型
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `RawData` 的目标是表达“业务上发生了什么”
|
||||||
|
- `Context` 的目标是表达“界面应该怎么显示”
|
||||||
|
- 两者不能混用
|
||||||
|
|
||||||
|
### 3.3 Controller 层
|
||||||
|
|
||||||
|
职责:UI 编排层,负责连接外部流程、`UseCase`、`View`,并统一管理 UI 生命周期与展示状态。
|
||||||
|
|
||||||
|
约束:
|
||||||
|
|
||||||
|
- UIForm 级模块默认必须有 `Controller`
|
||||||
- 命名:`XXXFormController`
|
- 命名:`XXXFormController`
|
||||||
|
- 可基于 `UIFormControllerCommonBase<TContext, TForm>` 实现
|
||||||
- 通过 `BindUseCase(IUIUseCase)` 注入用例并做类型校验
|
- 通过 `BindUseCase(IUIUseCase)` 注入用例并做类型校验
|
||||||
- `OpenUI(object userData = null)` 支持:`Context`、`RawData`、`null`
|
- `OpenUI(object userData = null)` 负责接受外部参数、准备数据并打开 UI
|
||||||
- 负责 RawData -> Context 的转换(常见 `BuildContext`)
|
- 负责 `RawData / Result -> Context` 的转换,常见形式为 `BuildContext`
|
||||||
- 在 `SubscribeCustomEvents / UnsubscribeCustomEvents` 成对管理事件
|
- 负责事件订阅与解除订阅,且必须成对出现
|
||||||
- 可做局部刷新(避免整窗重建)
|
- 负责全量刷新与局部刷新策略
|
||||||
|
- 负责过滤 UI 专用事件的 `sender`,确保事件只作用于当前 UI 实例
|
||||||
|
|
||||||
参考:
|
允许职责:
|
||||||
|
|
||||||
- `Assets/GameMain/Scripts/UI/GameScene/Controller/ShopFormController.cs`
|
- 将业务数据转换为展示友好的文本、图标、颜色、列表状态
|
||||||
- `Assets/GameMain/Scripts/UI/GameScene/Controller/LevelUpFormController.cs`
|
- 在必要时查询本地化、资源映射或展示适配逻辑
|
||||||
- `Assets/GameMain/Scripts/UI/MenuScene/Controller/SelectRoleFormController.cs`
|
|
||||||
|
禁止职责:
|
||||||
|
|
||||||
|
- 在 `Controller` 中堆叠大段领域业务规则
|
||||||
|
- 绕过 `Context` 直接把业务对象塞给 `View`
|
||||||
|
- 直接修改其他 UI 的内部 `View`
|
||||||
|
|
||||||
### 3.4 Context 层
|
### 3.4 Context 层
|
||||||
|
|
||||||
|
|
@ -89,104 +126,224 @@ View --(CustomEvent)--> Controller
|
||||||
约束:
|
约束:
|
||||||
|
|
||||||
- 继承 `UIContext`
|
- 继承 `UIContext`
|
||||||
- 命名:`XXXFormContext` 或 `XXXItemContext`
|
- 命名:`XXXFormContext`、`XXXItemContext`、`XXXAreaContext`
|
||||||
- 字段以展示友好为目标(标题、描述、图标、稀有度、列表等)
|
- 只能由 `Controller` 构建和更新
|
||||||
- 允许组合子 Context(例如列表区 + 条目)
|
- 字段以展示友好为目标,例如标题、描述、图标、颜色、状态、列表、按钮文案
|
||||||
|
- 允许组合子 `Context`
|
||||||
|
- 不进入 `UseCase`
|
||||||
|
|
||||||
参考:
|
说明:
|
||||||
|
|
||||||
- `Assets/GameMain/Scripts/UI/GameScene/Context/ShopFormContext.cs`
|
- `Context` 可以包含展示层需要的最终数据
|
||||||
- `Assets/GameMain/Scripts/UI/GameScene/Context/DisplayListAreaContext.cs`
|
- `Context` 可以是“已格式化”的显示数据
|
||||||
- `Assets/GameMain/Scripts/UI/MenuScene/Context/SelectRoleFormContext.cs`
|
- 但这些数据必须由 `Controller` 负责准备,而不是 `UseCase`
|
||||||
|
|
||||||
### 3.5 View 层
|
### 3.5 View 层
|
||||||
|
|
||||||
职责:纯表现层,负责控件绑定、显示刷新、交互事件抛出。
|
职责:纯表现层,负责控件绑定、渲染刷新、动画触发和交互事件抛出。
|
||||||
|
|
||||||
约束:
|
约束:
|
||||||
|
|
||||||
- Form 类继承 `UGuiForm`,子组件通常继承 `MonoBehaviour`
|
- Form 类继承 `UGuiForm`,子组件通常继承 `MonoBehaviour`
|
||||||
- 命名:`XXXForm` / `XXXItem` / `XXXArea`
|
- 命名:`XXXForm`、`XXXItem`、`XXXArea`
|
||||||
- 提供 `RefreshUI(Context)`、`OnInit(Context)`、`OnReset()` 等渲染入口
|
- 提供 `RefreshUI(Context)`、`OnInit(Context)`、`OnReset()` 等渲染入口
|
||||||
- 用户交互通过 `GameEntry.Event.Fire(...)` 通知 Controller
|
- 只消费 `Context`
|
||||||
- 不承载业务规则(计算、流程推进、数据筛选应在 UseCase)
|
- 用户交互通过 UI 专用事件通知 `Controller`
|
||||||
|
- 不承载业务规则、流程推进、数据筛选和领域状态修改
|
||||||
|
- 不订阅全局业务事件
|
||||||
|
|
||||||
参考:
|
允许职责:
|
||||||
|
|
||||||
- `Assets/GameMain/Scripts/UI/GameScene/View/ShopForm.cs`
|
- 本地控件显隐
|
||||||
- `Assets/GameMain/Scripts/UI/GameScene/View/DisplayListArea.cs`
|
- 动画播放
|
||||||
- `Assets/GameMain/Scripts/UI/MenuScene/View/SelectRoleForm.cs`
|
- 一次性的纯视觉状态缓存
|
||||||
|
|
||||||
## 4. 标准交互流程
|
禁止职责:
|
||||||
|
|
||||||
### 4.1 初始化与绑定
|
- 直接调用 `UseCase`
|
||||||
|
- 直接修改领域状态
|
||||||
|
- 订阅或处理全局业务事件
|
||||||
|
- 将自己作为业务逻辑的入口
|
||||||
|
|
||||||
1. Procedure/GameState 创建 UseCase
|
## 4. UI 类型分级
|
||||||
2. 调用 `GameEntry.UIRouter.BindUIUseCase(UIFormType.X, useCase)`
|
|
||||||
|
|
||||||
示例:
|
### 4.1 标准五层 UI
|
||||||
|
|
||||||
- `Assets/GameMain/Scripts/Procedure/Game/GameStateShop.cs`
|
组成:`UseCase + RawData + Controller + Context + View`
|
||||||
- `Assets/GameMain/Scripts/Procedure/Game/GameStateLevelUp.cs`
|
|
||||||
- `Assets/GameMain/Scripts/Procedure/ProcedureStartMenu.cs`
|
|
||||||
|
|
||||||
### 4.2 打开 UI
|
适用条件:
|
||||||
|
|
||||||
1. 调用 `GameEntry.UIRouter.OpenUI(UIFormType.X)`
|
- UI 需要读写领域状态
|
||||||
2. Controller 从 UseCase 取 RawData(或接收外部 RawData/Context)
|
- UI 存在明确业务规则或分支
|
||||||
3. Controller 构建 Context 后打开/刷新 Form
|
- UI 交互会改变游戏流程、角色状态、背包、战斗结果等业务对象
|
||||||
4. View 在 `OnOpen` 中校验 Context 类型并执行 `RefreshUI`
|
- UI 行为需要被自动化测试覆盖
|
||||||
|
|
||||||
### 4.3 用户交互到刷新
|
默认规则:
|
||||||
|
|
||||||
1. View 触发事件(如购买、刷新、选择)
|
- 新增业务型 UI,优先使用完整五层
|
||||||
2. Controller 监听事件并调用 UseCase
|
|
||||||
3. UseCase 返回新数据或操作结果
|
|
||||||
4. Controller 更新 Context 并刷新全部或局部 UI
|
|
||||||
|
|
||||||
### 4.4 关闭 UI
|
### 4.2 轻量 UI
|
||||||
|
|
||||||
1. 调用 `GameEntry.UIRouter.CloseUI(UIFormType.X)`
|
组成:`Controller + Context + View`
|
||||||
2. Controller 解除事件订阅并关闭窗体
|
|
||||||
3. View `OnClose` 清理本地状态
|
|
||||||
|
|
||||||
## 5. 目录与命名规范
|
说明:
|
||||||
|
|
||||||
- 目录:`UI/<SceneDomain>/RawData|UseCase|Controller|Context|View`
|
- `UseCase` 对轻量 UI 不强制
|
||||||
- 五层同名前缀保持一致:`ShopForm*`、`LevelUpForm*`、`SelectRoleForm*`
|
- `RawData` 也不是强制层,可按需要补充
|
||||||
- 子组件上下文命名:`RoleItemContext`、`DisplayItemContext`、`LevelUpRewardItemContext`
|
- 轻量 UI 仍然必须通过 `Controller` 驱动,不能让 `View` 直接承担外部入口
|
||||||
- 新增 UI Form 时优先建立完整五层;仅纯静态展示可降级为 View-only
|
|
||||||
|
|
||||||
## 6. 依赖方向约束
|
适用条件:
|
||||||
|
|
||||||
|
- 只承担展示、导航、确认、提示等轻量职责
|
||||||
|
- 没有独立的业务规则或状态推进
|
||||||
|
- 只需要把已有参数转换成界面展示
|
||||||
|
|
||||||
|
升级规则:
|
||||||
|
|
||||||
|
- 一旦轻量 UI 开始承载业务规则、校验或状态推进,应升级为标准五层 UI
|
||||||
|
|
||||||
|
## 5. 依赖方向约束
|
||||||
|
|
||||||
允许依赖:
|
允许依赖:
|
||||||
|
|
||||||
- `UseCase -> RawData / 领域对象`
|
- `UseCase -> 领域对象 / 纯业务服务 / RawData / Result`
|
||||||
- `Controller -> UseCase + RawData + Context + View + Event`
|
- `Controller -> UseCase + RawData + Result + Context + View + UI 专用事件`
|
||||||
- `View -> Context + Event`
|
- `Context -> 子 Context / 纯展示值对象`
|
||||||
|
- `View -> Context + UI 专用事件`
|
||||||
|
|
||||||
禁止依赖:
|
禁止依赖:
|
||||||
|
|
||||||
|
- `UseCase -> Context / View / Unity 具体展示组件`
|
||||||
|
- `RawData / Result -> Context / View`
|
||||||
|
- `Context -> View / UseCase`
|
||||||
- `View -> UseCase`
|
- `View -> UseCase`
|
||||||
|
- `View -> 全局业务事件`
|
||||||
- `View -> 领域状态修改`
|
- `View -> 领域状态修改`
|
||||||
- `Context/RawData -> View`
|
|
||||||
|
|
||||||
## 7. 新增一个五层 UI 的落地步骤
|
## 6. 事件通信规范
|
||||||
|
|
||||||
1. 在目标场景目录创建 `RawData / UseCase / Context / Controller / View` 对应类型
|
### 6.1 UI 与 Controller 的通信方式
|
||||||
2. 在 UseCase 中实现模型创建与交互方法
|
|
||||||
3. 在 Controller 中实现 `BindUseCase`、`OpenUI`、`BuildContext`、事件订阅
|
|
||||||
4. 在 View 中实现 `RefreshUI` 和交互事件抛出
|
|
||||||
5. 在对应 Procedure/GameState 里完成 UseCase 绑定与 Open/Close 调用
|
|
||||||
6. 自测三条主链路:首次打开、交互刷新、关闭重开
|
|
||||||
|
|
||||||
## 8. 项目当前实践说明
|
`View` 与 `Controller` 的通信通过当前 UI 模块专用事件完成。
|
||||||
|
|
||||||
- `ShopForm`、`LevelUpForm`、`SelectRoleForm` 是当前五层模式的主要样板
|
约束:
|
||||||
- `DialogForm` 也有 Controller/Context/RawData,但 UseCase 为可选
|
|
||||||
- `HudForm`、`StartMenuForm` 当前为轻用例场景,可不强制 UseCase
|
|
||||||
- `SettingForm`、`AboutForm` 属于历史直连型 UI,不属于五层完整样板
|
|
||||||
|
|
||||||
---
|
- UI 专用事件只服务于当前 UI 模块
|
||||||
|
- 这些事件不是业务公共事件
|
||||||
|
- 业务模块、流程模块、领域模块不应复用这些事件
|
||||||
|
- 如果底层使用全局事件总线实现,也只能把它当作“传输通道”,不能把事件语义扩散成全局契约
|
||||||
|
|
||||||
如后续需要统一重构,建议优先把历史直连型 UI(如 `SettingForm`)迁移到五层模板,以降低 UI 逻辑耦合度。
|
### 6.2 事件边界
|
||||||
|
|
||||||
|
- `View -> Controller`:使用 UI 专用事件
|
||||||
|
- `Controller -> View`:通过刷新 `Context` 或调用 `View` 的渲染接口
|
||||||
|
- `Controller -> UseCase`:直接方法调用
|
||||||
|
- `UseCase -> Controller`:通过返回 `RawData / Result`,不通过 UI 事件反推界面
|
||||||
|
|
||||||
|
### 6.3 事件安全要求
|
||||||
|
|
||||||
|
- `Controller` 必须校验事件 `sender`
|
||||||
|
- 同类 UI 可同时存在时,事件必须只作用于当前窗体实例
|
||||||
|
- UI 专用事件命名应体现模块归属,避免语义过宽
|
||||||
|
|
||||||
|
## 7. 标准交互流程
|
||||||
|
|
||||||
|
### 7.1 有 UseCase 的标准流程
|
||||||
|
|
||||||
|
```text
|
||||||
|
Procedure / GameState
|
||||||
|
-> 创建 UseCase
|
||||||
|
-> BindUseCase
|
||||||
|
-> OpenUI
|
||||||
|
|
||||||
|
Controller
|
||||||
|
-> UseCase.CreateInitialModel()
|
||||||
|
-> BuildContext(rawData)
|
||||||
|
-> View.RefreshUI(context)
|
||||||
|
|
||||||
|
View
|
||||||
|
--(UI 专用事件)--> Controller
|
||||||
|
|
||||||
|
Controller
|
||||||
|
-> UseCase.Action(...)
|
||||||
|
-> Result / RawData
|
||||||
|
-> BuildContext / PartialRefresh
|
||||||
|
-> View.RefreshUI(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 无 UseCase 的轻量流程
|
||||||
|
|
||||||
|
```text
|
||||||
|
外部流程
|
||||||
|
-> OpenUI(userData)
|
||||||
|
|
||||||
|
Controller
|
||||||
|
-> BuildContext(userData)
|
||||||
|
-> View.RefreshUI(context)
|
||||||
|
|
||||||
|
View
|
||||||
|
--(UI 专用事件)--> Controller
|
||||||
|
|
||||||
|
Controller
|
||||||
|
-> 处理轻量逻辑或路由动作
|
||||||
|
-> 更新 Context / 打开其他 UI / 关闭当前 UI
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 关闭流程
|
||||||
|
|
||||||
|
1. 外部流程或 `Controller` 调用关闭逻辑
|
||||||
|
2. `Controller` 解除事件订阅
|
||||||
|
3. `View.OnClose` 清理本地视觉状态
|
||||||
|
4. 下次打开时重新按 `Context` 初始化
|
||||||
|
|
||||||
|
## 8. 目录与命名规范
|
||||||
|
|
||||||
|
目录示例以 `Assets/GameMain/Scripts/UI` 为例。
|
||||||
|
|
||||||
|
- 目录:`UI/<SceneDomain>/UseCase|RawData|Controller|Context|View`
|
||||||
|
- 五层同名前缀保持一致,例如 `ShopForm*`、`LevelUpForm*`
|
||||||
|
- 子组件上下文命名:`RoleItemContext`、`RewardItemContext`、`DisplayAreaContext`
|
||||||
|
- 结果对象命名:`XXXResult`、`XXXActionResult`
|
||||||
|
- 轻量 UI 可以省略 `UseCase` 和 `RawData` 目录,但不省略 `Controller`、`Context`、`View`
|
||||||
|
|
||||||
|
## 9. 测试规范
|
||||||
|
|
||||||
|
### 9.1 自动化测试范围
|
||||||
|
|
||||||
|
如果一个 UI 具备 `UseCase`,并且需要补自动化测试,则统一使用 EditMode 测试。
|
||||||
|
|
||||||
|
优先覆盖:
|
||||||
|
|
||||||
|
- 初始化模型生成
|
||||||
|
- 业务分支与校验
|
||||||
|
- 用户动作对应的结果对象
|
||||||
|
- 边界条件和非法输入
|
||||||
|
|
||||||
|
### 9.2 Controller / View 的验证策略
|
||||||
|
|
||||||
|
`Controller` 和 `View` 以人工验收为主,重点验证:
|
||||||
|
|
||||||
|
- 首次打开
|
||||||
|
- 交互刷新
|
||||||
|
- 局部刷新
|
||||||
|
- 关闭重开
|
||||||
|
- 非法参数或空数据输入时的表现
|
||||||
|
|
||||||
|
## 10. 落地检查清单
|
||||||
|
|
||||||
|
新增 UI 时,至少检查以下事项:
|
||||||
|
|
||||||
|
1. 先判断该 UI 属于标准五层还是轻量 UI
|
||||||
|
2. 如果存在业务规则或状态推进,必须引入 `UseCase`
|
||||||
|
3. `RawData` 中不得出现 `Context` 类型
|
||||||
|
4. `Context` 只能在 `Controller` 中构建
|
||||||
|
5. `View` 不得订阅全局业务事件
|
||||||
|
6. `View` 的交互只能通过 UI 专用事件上报
|
||||||
|
7. `Controller` 必须成对管理事件订阅与解除订阅
|
||||||
|
8. 有 `UseCase` 且需要补自动化测试时,测试写入 EditMode
|
||||||
|
|
||||||
|
## 11. 非目标说明
|
||||||
|
|
||||||
|
- 本文不讨论历史实现,也不为历史写法背书
|
||||||
|
- 本文不要求所有 UI 必须强制引入 `UseCase`
|
||||||
|
- 本文不展开底层 UI 框架或事件系统实现细节
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
name: ui-five-layer-architecture
|
||||||
|
description: Define, review, and refactor UI modules using a strict five-layer architecture (UseCase, RawData, Controller, Context, View). Use when creating cross-project UI architecture standards, designing new UI modules, reviewing layer boundaries and event flow, converting ad-hoc UI code into layered structure, or validating UI test strategy for business-driven interfaces.
|
||||||
|
---
|
||||||
|
|
||||||
|
# UI Five-Layer Architecture
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. Read `./references/ui-five-layer-standard.md`.
|
||||||
|
2. Classify the UI as `standard-five-layer` or `lightweight`.
|
||||||
|
3. Apply boundary rules before editing code or writing design output.
|
||||||
|
4. Use the checklists in this file to drive design, review, or refactor tasks.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### 1. Scope the task
|
||||||
|
|
||||||
|
Decide which mode the user needs:
|
||||||
|
|
||||||
|
- architecture-spec mode: create or revise a UI architecture standard
|
||||||
|
- design mode: design one or more UI modules before coding
|
||||||
|
- implementation mode: implement or refactor code to match the standard
|
||||||
|
- review mode: audit existing code for boundary or dependency violations
|
||||||
|
|
||||||
|
### 2. Choose module level
|
||||||
|
|
||||||
|
Use `standard-five-layer` when UI owns business state transitions, validations, or branching behavior.
|
||||||
|
|
||||||
|
Use `lightweight` when UI only handles display/navigation and has no independent business rules.
|
||||||
|
|
||||||
|
### 3. Enforce non-negotiable boundaries
|
||||||
|
|
||||||
|
Apply these constraints in every mode:
|
||||||
|
|
||||||
|
- keep `UseCase` business-only and return only `RawData/Result`
|
||||||
|
- build `Context` only inside `Controller`
|
||||||
|
- keep `View` presentation-only and event-emitting only
|
||||||
|
- keep `View` out of global business event subscriptions
|
||||||
|
- route external UI open/close/refresh through `Controller`
|
||||||
|
|
||||||
|
### 4. Apply communication and dependency checks
|
||||||
|
|
||||||
|
Validate:
|
||||||
|
|
||||||
|
- dependency direction is valid for all touched files
|
||||||
|
- UI-specific events stay UI-local in meaning
|
||||||
|
- `Controller` filters sender and instance scope when needed
|
||||||
|
- no `RawData` field uses `*Context` types
|
||||||
|
|
||||||
|
### 5. Produce mode-specific output
|
||||||
|
|
||||||
|
- architecture-spec mode:
|
||||||
|
- output the final standard text
|
||||||
|
- include strict rules, permitted exceptions, and checklists
|
||||||
|
- design mode:
|
||||||
|
- output layer map and flow diagram for each UI module
|
||||||
|
- include type list and event list
|
||||||
|
- implementation mode:
|
||||||
|
- implement code changes matching the standard
|
||||||
|
- update tests if `UseCase` behavior changes
|
||||||
|
- review mode:
|
||||||
|
- report findings first, ordered by severity
|
||||||
|
- include concrete file and line references
|
||||||
|
|
||||||
|
## Architecture Checklist
|
||||||
|
|
||||||
|
Use this list before closing any task:
|
||||||
|
|
||||||
|
1. Classify each UI module correctly: `standard-five-layer` or `lightweight`.
|
||||||
|
2. Ensure `UseCase` does not construct `Context` or touch view concerns.
|
||||||
|
3. Ensure `RawData` does not include `*Context` or presentation types.
|
||||||
|
4. Ensure `Controller` owns all `RawData/Result -> Context` transformation.
|
||||||
|
5. Ensure `View` only consumes `Context` and emits UI-local events.
|
||||||
|
6. Ensure external open/close/update operations enter through `Controller`.
|
||||||
|
7. Ensure event ownership and sender filtering are explicit.
|
||||||
|
8. Ensure test approach matches policy in the reference file.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
Read `./references/ui-five-layer-standard.md` for the full specification.
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
interface:
|
||||||
|
display_name: "UI Five-Layer Architecture"
|
||||||
|
short_description: "Cross-project UI five-layer architecture standards"
|
||||||
|
default_prompt: "Use $ui-five-layer-architecture to define, review, or refactor a UI module with clear five-layer boundaries."
|
||||||
|
|
@ -0,0 +1,274 @@
|
||||||
|
# UI Five-Layer Architecture Standard
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Scope and Intent](#scope-and-intent)
|
||||||
|
2. [Core Model](#core-model)
|
||||||
|
3. [Layer Definitions](#layer-definitions)
|
||||||
|
4. [UI Module Levels](#ui-module-levels)
|
||||||
|
5. [Dependency Rules](#dependency-rules)
|
||||||
|
6. [Event Communication Rules](#event-communication-rules)
|
||||||
|
7. [Interaction Flow](#interaction-flow)
|
||||||
|
8. [Naming and Folder Conventions](#naming-and-folder-conventions)
|
||||||
|
9. [Testing Policy](#testing-policy)
|
||||||
|
10. [Anti-Patterns](#anti-patterns)
|
||||||
|
11. [Delivery Checklist](#delivery-checklist)
|
||||||
|
|
||||||
|
## Scope and Intent
|
||||||
|
|
||||||
|
Use this standard to define and enforce a stable UI architecture that can be reused across projects.
|
||||||
|
|
||||||
|
Primary goals:
|
||||||
|
|
||||||
|
- keep business logic independent from UI rendering
|
||||||
|
- keep UI rendering deterministic and testable
|
||||||
|
- make reviews and refactors consistent
|
||||||
|
- reduce coupling and regression risk
|
||||||
|
|
||||||
|
## Core Model
|
||||||
|
|
||||||
|
Base chain:
|
||||||
|
|
||||||
|
```text
|
||||||
|
External Flow
|
||||||
|
-> Controller
|
||||||
|
-> UseCase
|
||||||
|
-> RawData / Result
|
||||||
|
-> BuildContext
|
||||||
|
-> View
|
||||||
|
|
||||||
|
View
|
||||||
|
--(UI-local event)--> Controller
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- `UseCase` produces business outputs only.
|
||||||
|
- `Controller` is the only layer that creates `Context`.
|
||||||
|
- `View` only renders and emits interaction events.
|
||||||
|
|
||||||
|
## Layer Definitions
|
||||||
|
|
||||||
|
### UseCase
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
- own business rules and state transitions
|
||||||
|
- validate business actions
|
||||||
|
- return `RawData` or result objects
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
- do not depend on `Context`, `View`, or rendering types
|
||||||
|
- do not format display strings or map visual assets
|
||||||
|
- do not publish UI-local events
|
||||||
|
|
||||||
|
### RawData
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
- carry business data from `UseCase` to `Controller`
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
- keep data business-oriented
|
||||||
|
- do not reference any `*Context` type
|
||||||
|
- do not contain rendering objects (for example UI components)
|
||||||
|
|
||||||
|
### Controller
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
- be the external entry point for open/close/refresh
|
||||||
|
- bind and call `UseCase`
|
||||||
|
- transform `RawData/Result` into `Context`
|
||||||
|
- subscribe/unsubscribe UI-local events
|
||||||
|
- coordinate full or partial refresh
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
- do not let `View` bypass controller orchestration
|
||||||
|
- do not hide heavy business logic that belongs in `UseCase`
|
||||||
|
|
||||||
|
### Context
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
- carry display-ready data for rendering
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
- construct/update only in `Controller`
|
||||||
|
- do not enter `UseCase`
|
||||||
|
- allow composition (`FormContext`, `ItemContext`, `AreaContext`)
|
||||||
|
|
||||||
|
### View
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
- bind controls and render `Context`
|
||||||
|
- emit UI-local interaction events
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
- do not call `UseCase`
|
||||||
|
- do not mutate domain state
|
||||||
|
- do not subscribe to global business events
|
||||||
|
- do not become external entry points
|
||||||
|
|
||||||
|
## UI Module Levels
|
||||||
|
|
||||||
|
### Standard Five-Layer Module
|
||||||
|
|
||||||
|
Structure:
|
||||||
|
|
||||||
|
- `UseCase + RawData + Controller + Context + View`
|
||||||
|
|
||||||
|
Use when:
|
||||||
|
|
||||||
|
- module owns business rules, validations, or branching state transitions
|
||||||
|
- user actions mutate domain state
|
||||||
|
- behavior requires automated verification
|
||||||
|
|
||||||
|
### Lightweight Module
|
||||||
|
|
||||||
|
Structure:
|
||||||
|
|
||||||
|
- `Controller + Context + View`
|
||||||
|
|
||||||
|
Use when:
|
||||||
|
|
||||||
|
- module is display/navigation/confirmation only
|
||||||
|
- no independent business rules are present
|
||||||
|
|
||||||
|
Rule:
|
||||||
|
|
||||||
|
- upgrade to standard five-layer as soon as business rules appear
|
||||||
|
|
||||||
|
## Dependency Rules
|
||||||
|
|
||||||
|
Allowed:
|
||||||
|
|
||||||
|
- `UseCase -> domain/services/RawData/Result`
|
||||||
|
- `Controller -> UseCase/RawData/Result/Context/View/UI-local events`
|
||||||
|
- `Context -> child context/value objects`
|
||||||
|
- `View -> Context/UI-local events`
|
||||||
|
|
||||||
|
Forbidden:
|
||||||
|
|
||||||
|
- `UseCase -> Context/View/rendering types`
|
||||||
|
- `RawData/Result -> Context/View`
|
||||||
|
- `Context -> UseCase/View`
|
||||||
|
- `View -> UseCase`
|
||||||
|
- `View -> global business events`
|
||||||
|
- `View -> domain state mutation`
|
||||||
|
|
||||||
|
## Event Communication Rules
|
||||||
|
|
||||||
|
### Event Ownership
|
||||||
|
|
||||||
|
- `View -> Controller` uses UI-local events only
|
||||||
|
- UI-local events are not global business contracts
|
||||||
|
- business/domain modules must not consume UI-local event semantics
|
||||||
|
|
||||||
|
### Safety Requirements
|
||||||
|
|
||||||
|
- validate sender ownership in `Controller`
|
||||||
|
- scope handling to the active UI instance
|
||||||
|
- keep subscribe/unsubscribe symmetric
|
||||||
|
|
||||||
|
## Interaction Flow
|
||||||
|
|
||||||
|
### Standard Module
|
||||||
|
|
||||||
|
```text
|
||||||
|
External Flow
|
||||||
|
-> create/bind UseCase
|
||||||
|
-> open UI via Controller
|
||||||
|
|
||||||
|
Controller
|
||||||
|
-> UseCase action
|
||||||
|
-> RawData/Result
|
||||||
|
-> BuildContext
|
||||||
|
-> View refresh
|
||||||
|
|
||||||
|
View
|
||||||
|
--(UI-local event)--> Controller
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lightweight Module
|
||||||
|
|
||||||
|
```text
|
||||||
|
External Flow
|
||||||
|
-> open UI via Controller(userData)
|
||||||
|
|
||||||
|
Controller
|
||||||
|
-> BuildContext(userData)
|
||||||
|
-> View refresh
|
||||||
|
|
||||||
|
View
|
||||||
|
--(UI-local event)--> Controller
|
||||||
|
```
|
||||||
|
|
||||||
|
## Naming and Folder Conventions
|
||||||
|
|
||||||
|
Recommended folders:
|
||||||
|
|
||||||
|
- `UI/<Domain>/UseCase`
|
||||||
|
- `UI/<Domain>/RawData`
|
||||||
|
- `UI/<Domain>/Controller`
|
||||||
|
- `UI/<Domain>/Context`
|
||||||
|
- `UI/<Domain>/View`
|
||||||
|
|
||||||
|
Recommended naming:
|
||||||
|
|
||||||
|
- `XXXFormUseCase`
|
||||||
|
- `XXXFormRawData`
|
||||||
|
- `XXXFormController`
|
||||||
|
- `XXXFormContext`, `XXXItemContext`, `XXXAreaContext`
|
||||||
|
- `XXXResult`, `XXXActionResult`
|
||||||
|
|
||||||
|
## Testing Policy
|
||||||
|
|
||||||
|
Policy:
|
||||||
|
|
||||||
|
- if a UI has a `UseCase` and automated tests are added, use EditMode tests for the `UseCase`
|
||||||
|
|
||||||
|
Priority coverage:
|
||||||
|
|
||||||
|
- initial model generation
|
||||||
|
- business branch and validation behavior
|
||||||
|
- action result correctness
|
||||||
|
- boundary and invalid input handling
|
||||||
|
|
||||||
|
Manual verification focus:
|
||||||
|
|
||||||
|
- first open
|
||||||
|
- interaction refresh
|
||||||
|
- partial refresh
|
||||||
|
- close and reopen
|
||||||
|
- null/invalid userData behavior
|
||||||
|
|
||||||
|
## Anti-Patterns
|
||||||
|
|
||||||
|
Do not allow:
|
||||||
|
|
||||||
|
- `UseCase` returning `Context`
|
||||||
|
- `RawData` carrying `*Context`
|
||||||
|
- `View` subscribing global business events
|
||||||
|
- direct domain mutation inside `View`
|
||||||
|
- skipping `Controller` as module entry
|
||||||
|
- module marked lightweight while carrying business state transitions
|
||||||
|
|
||||||
|
## Delivery Checklist
|
||||||
|
|
||||||
|
Use this checklist before marking work complete:
|
||||||
|
|
||||||
|
1. classify each module as standard or lightweight
|
||||||
|
2. verify business rules sit in `UseCase` only
|
||||||
|
3. verify `Context` is built only in `Controller`
|
||||||
|
4. verify `RawData` has no presentation model leakage
|
||||||
|
5. verify `View` is render-and-emit only
|
||||||
|
6. verify UI-local events are scoped and sender-checked
|
||||||
|
7. verify dependency direction constraints pass
|
||||||
|
8. verify tests/manual checks match the testing policy
|
||||||
Loading…
Reference in New Issue