Update all three section dialogs to support hide/show toggle: SectionAxisDialogManager: - onHideToggle now calls hideSection()/recoverSection() SectionBoxDialogManager: - onHideToggle now calls hideSection()/recoverSection() SectionPlanePanel: - Add isHidden state tracking - Change onHide to onHideToggle(isHidden) - Add setHiddenState/getHiddenState methods - Update button to toggle active state SectionPlaneDialogManager: - Switch to onHideToggle callback - Call hideSection()/recoverSection() based on toggle state Behavior: Click hide button to hide section, click again to recover.
12 KiB
iflow-engine 框架架构审核(代码 + 文档)
日期:2026-01-29
范围
本审核仅评估 iflow-engine SDK 的内部框架/架构设计,依据:
src/下的源代码docs/下的架构与模块文档
不在本次范围内:
- 功能完整性
- UI/交互体验优劣
- 重构路线/落地方案/实现细节(按你的要求不输出)
已确认的前提(来自你的要求):
- 保留
ManagerRegistry作为核心设计(单例 + 服务定位器)。 - SDK 需要支持同一页面多个 viewer 实例。
- 主题/语言是全局共享。
- 事件应当按
BimEngine实例隔离。 - Components 设计上应当严格纯 UI:不触达 registry、不发布全局事件。
- Components 也不应直接订阅/读取全局主题/国际化服务(theme/locale 应由 manager 注入或推送更新)。
严重级别说明
- Critical(致命):架构层面存在矛盾或极易引发跨实例破坏。
- High(高):高概率造成长期维护痛点/难追踪 bug。
- Medium(中):明显增加认知负担或存在非小风险的不一致。
- Low(低):质量/风格问题,非结构性,但建议修整。
发现的问题清单
1) Critical:多实例事件隔离诉求,与全局单例事件总线结构矛盾
框架把所有事件集中在 ManagerRegistry 内部的一个全局 EventEmitter 上。
这从结构上决定了:多个 BimEngine 实例之间无法做到事件天然隔离。
证据:
src/core/manager-registry.ts:31-38(private eventEmitter: EventEmitter = new EventEmitter())src/core/manager-registry.ts:95-127(emit/on/off都委托给这一个 emitter)src/core/base-manager.ts:19-34(所有 Manager 的订阅都通过单例 registry 完成)
影响:
- 多个 viewer 共存时,任一实例发出的事件都可能被所有订阅者收到。
- 事件域是全局的,不是 per-engine 的;如果要隔离,必须在别处“人为做隔离”,但当前框架没有内建边界。
2) Critical:销毁任一实例会重置全局 registry,破坏其他实例
BimEngine.destroy() 会调用 ManagerRegistry.reset():清空事件监听并把单例置空。
当页面存在多个引擎实例时,销毁其中一个就可能让其他实例失效。
证据:
src/bim-engine.ts:150-164(调用ManagerRegistry.reset())src/core/manager-registry.ts:82-88(reset()会clear()emitter 并instance = null)
影响:
- 跨实例“断电式”故障;而且故障源头非局部,排查成本高。
3) High:全局 registry 存放实例态(container/wrapper/各类 manager 实例)
ManagerRegistry 不只是 locator:它持有 container/wrapper DOM 节点和大量 manager 实例。
这使它变成一个全局可变的“状态袋”。
证据:
src/core/manager-registry.ts:35-70(container/wrapper与各 manager 字段)src/bim-engine.ts:101-136(把容器与所有 manager 写入单例)
影响:
- 多实例情况下,“最后初始化的实例”会覆盖
registry.container/wrapper与 manager 引用。 - 排查问题时必须追踪“最后是谁写入了全局状态”。
4) High:服务定位器边界未被约束(叶子层大量直接 getInstance)
框架想表达分层,但叶子层代码频繁直接调用 ManagerRegistry.getInstance()。
这会扩大隐式依赖并增加跳转/追踪成本。
证据(示例,不完全):
src/components/button-group/toolbar/buttons/home/index.ts:15-16src/components/button-group/toolbar/buttons/measure/index.ts:14-18src/components/button-group/toolbar/buttons/map/index.ts:8-28src/components/button-group/index.ts:65-68(组件通过单例 registry 发事件)src/components/engine/index.ts:131-144(Engine 组件通过单例 registry 发事件)src/managers/engine-manager.ts:64-68(即便继承BaseManager,回调里仍getInstance())
影响:
- 依赖关系在 API/构造函数层面不可见,只能靠全局搜索发现。
- 实际改动往往需要跨层与跨全局状态跳转。
5) High:组件层通过 registry 直接参与框架通信,削弱了文档里的分层承诺
文档里写明“组件不直接依赖 Manager,由 Manager 创建和管理组件实例”。 但代码中组件通过全局 registry 发布事件。
证据:
- 文档承诺:
docs/MODULES/组件模块.md:595-602 - 反例:
src/components/engine/index.ts:131-144(ManagerRegistry.getInstance().emit(...)) - 反例:
src/components/button-group/index.ts:65-68(组件通过 registry 发事件)
影响:
- 组件层通过全局 locator 变得“半业务化/半框架化”。
- 耦合上升,测试与复用更困难。
6) High:RightKeyManager 出现重复创建/归属不清
右键管理器至少在两处被创建。 这会导致职责重复,并引入“到底谁是权威实例”的歧义。
证据:
src/bim-engine.ts:108(this.rightKey = new RightKeyManager(this.wrapper))src/managers/engine-manager.ts:50-51(this.rightKey = new RightKeyManager(this.container))
影响:
- 更高概率出现重复监听、hide/show 不一致、销毁责任不清。
7) High:Engine 组件依赖仓库相对路径的底层引擎产物(工程耦合)
代码没有从外部依赖(iflow-engine-base)导入,而是通过深层相对路径引入本仓库的 dist 文件。
这会把构建/运行绑定到特定仓库布局。
证据:
src/components/engine/index.ts:8-11(从../../../../bim_engine_base/dist/...导入createEngine)
影响:
- CI/发布构建对目录结构高度敏感。
- 线上发布包与本地开发行为可能不一致。
8) Medium:BimEngine 订阅主题变化但未保存取消订阅句柄
BimEngine.init() 对 themeManager 订阅后没有保存返回的 unsubscribe。
证据:
src/bim-engine.ts:139-142(themeManager.subscribe(...)返回值未保存)
影响:
- 在 SPA 场景反复创建/销毁引擎时可能累积订阅,形成隐性泄漏。
9) Medium:对话框定位依赖全局 registry.container(非实例局部上下文)
BaseDialogManager 的默认位置计算基于 this.registry.container。
多实例时这隐含了“全局只有一个有效 container”。
证据:
src/core/base-dialog-manager.ts:60-73(读取this.registry.container计算位置)
影响:
- 多实例下对话框可能相对“最后写入 registry 的容器”定位,表现不一致。
10) Medium:分层调用链本身会导致“改动面扩大”(层级跳转多)
文档明确了 5 层调用链(Button → DialogManager → EngineManager → Engine 组件 → 底层引擎)。 哪怕每层都写得干净,累计的间接层也会扩大改动时的触达面。
证据:
docs/API调用链.md:714-748(层级说明)docs/引擎API对接.md:18-66(架构概览与层级表)
影响:
- 一个功能的内部改动往往需要同步修改 3-5 个位置。
- 这会被外部感知为“框架复杂”,即使逻辑上自洽。
11) Medium:构建配置禁用代码分割(可能削弱 ESM tree-shaking 效果)
库构建使用 inlineDynamicImports: true,强制输出单文件 bundle。
证据:
vite.config.ts:26-38
影响:
- 使用方更难获得按需使用/裁剪的收益。
- 打包策略会进一步强化“框架很重”的印象。
12) Low:Engine.setTheme 为空实现(抽象与实现不一致)
Engine 组件暴露了 setTheme,但实现为空。
证据:
src/components/engine/index.ts:153-160
影响:
- 虽然不是典型“架构问题”,但会加重抽象与行为不一致,长期会影响信任度与维护效率。
文档一致性备注
整体上文档很完整、意图也清晰(门面 + registry + 分层)。主要不一致/落差集中在:
- 一些分层边界(如“组件不依赖 Manager”)在代码里被全局 registry 使用削弱。
- “多实例 + 事件隔离”的需求与当前“全局单例事件域”存在硬冲突。
维护成本视角的客观评价(你选的侧重点)
这里把“维护成本”拆成三类:可读性(读懂/定位)、可改动性(改一个点波及范围)、可测试性(写测试/隔离依赖的难度)。
A. 可读性(读懂/定位)
结论:中等偏高的认知负担。架构意图清晰,但“全局可取用 registry”让依赖与调用链变得隐式。
主要原因(按影响排序):
- 隐式依赖:叶子层普遍
ManagerRegistry.getInstance(),使得“一个文件真正依赖哪些子系统”无法从构造函数/参数判断,只能靠全局搜索。- 证据:
src/components/button-group/toolbar/buttons/home/index.ts:15-16、src/components/button-group/toolbar/buttons/measure/index.ts:14-18、src/components/button-group/toolbar/buttons/map/index.ts:8-28
- 证据:
- 状态来源不集中:registry 同时存放 DOM 容器(
container/wrapper)与大量 manager 实例,读代码时需要先搞清楚“谁在什么时候写入了 registry”。- 证据:
src/core/manager-registry.ts:35-70、src/bim-engine.ts:95-136
- 证据:
- 层级跳转多且链路分散:你在文档里明确分层(L1-L5),这使新同学能理解“应该去哪里找”,但也意味着定位一个行为经常要横跨 3-5 个文件。
- 证据:
docs/引擎API对接.md:18-66、docs/API调用链.md:714-748
- 证据:
B. 可改动性(改一个点波及范围)
结论:改动面容易扩大,尤其是新增/调整一个功能时,按规范往往需要在多个层同时补齐。
主要原因:
- 强制的多层落点:文档对“新增功能对接步骤”规定了 Button→Toolbar→DialogManager→Registry→BimEngine init→EngineManager→Engine 组件的连锁改动;这对一致性有好处,但会让小需求也要跨层改多个位置。
- 证据:
docs/引擎API对接.md:326-517(Step 1-7)
- 证据:
- 全局注册表字段扩散:新增一个 manager 往往意味着在 registry 增字段、在 BimEngine 填充引用;随着功能增长,registry 越来越像“全局大对象”,改动与回归风险随之增加。
- 证据:
docs/引擎API对接.md:416-446、src/core/manager-registry.ts:35-70、src/bim-engine.ts:120-136
- 证据:
- 重复实例/归属不清会放大改动成本:同一能力被多处创建/持有,会导致改动时需要同时修改多个入口,且更容易漏。
- 证据:
src/bim-engine.ts:108、src/managers/engine-manager.ts:50-51
- 证据:
C. 可测试性(隔离依赖/写单测的难度)
结论:偏难(不是因为 TypeScript/DOM,而是因为全局单例与隐式依赖)。
主要原因:
- 全局单例 + 全局事件域:测试用例之间需要非常谨慎地清理/隔离 registry 状态与事件订阅,否则容易互相污染。
- 证据:
src/core/manager-registry.ts:31-38、src/core/manager-registry.ts:82-88
- 证据:
- 组件/manager 直接触达全局状态:例如 Engine 组件在底层事件回调里直接拿单例 registry 发事件,使得组件测试必须考虑全局副作用。
- 证据:
src/components/engine/index.ts:131-144
- 证据:
- 订阅/事件监听的释放不完全:存在订阅未保存/未释放的迹象,会导致测试环境(或 SPA)出现隐性泄漏与用例间干扰。
- 证据:
src/bim-engine.ts:139-142、src/components/button-group/index.ts:97-112、src/components/button-group/index.ts:804-817
- 证据:
维护成本总结(一句话)
你的架构“按分层组织代码、让功能落点清晰”的方向是对的;维护成本主要来自 registry 单例让依赖与状态隐式化,再叠加 多层落点规范 与 少量重复实例/资源释放不全,使得定位、改动、测试都会更依赖经验与全局搜索。