ProjectModel 核心设计
引言
本文件面向 VTJ 低代码平台的“项目模型”(ProjectModel)核心设计,系统性阐述其整体架构、数据模型、生命周期管理、事件系统集成以及与区块模型(BlockModel)、节点模型(NodeModel)的协作机制。文档同时提供 API 参考、使用示例路径与最佳实践,帮助开发者高效、安全地使用 ProjectModel 管理项目。
项目结构
ProjectModel 位于核心包中,承担项目级状态与文件管理职责;其事件系统通过全局发射器(mitt)统一广播;设计器引擎(Engine)订阅并驱动持久化、激活文件、历史记录等流程;BlockModel/NodeModel 则负责页面/区块的 DSL 结构与变更传播。
graph TB
subgraph "核心模型"
PM["ProjectModel<br/>项目模型"]
BM["BlockModel<br/>区块模型"]
NM["NodeModel<br/>节点模型"]
end
subgraph "事件系统"
EM["emitter(mitt)<br/>全局事件发射器"]
end
subgraph "设计器引擎"
ENG["Engine<br/>设计器引擎"]
end
PM -- "emit EVENT_*" --> EM
BM -- "emit EVENT_BLOCK_CHANGE" --> EM
NM -- "emit EVENT_NODE_CHANGE" --> EM
EM -- "on EVENT_*" --> ENG
ENG -- "active/deactivate/保存/发布" --> PM
ENG -- "构建/更新当前 Block" --> BM
BM -- "构建/更新节点树" --> NM
核心组件
- ProjectModel:项目级数据模型,提供项目属性、页面/区块/依赖/API/Meta 等管理能力,支持静默/非静默更新,内置 DSL 序列化与路由生成。
- BlockModel:区块级数据模型,承载节点树、状态、方法、计算属性、数据源、插槽、事件等,支持节点增删改移动与静默/非静默更新。
- NodeModel:节点级数据模型,描述组件/HTML 标签的属性、事件、指令、插槽及子节点,支持可见性、锁定、层级移动与 DSL 序列化。
- 事件系统:基于 mitt 的全局发射器,定义多种事件常量(如项目变更、文件激活、区块/节点变更、发布、出码等),由各模型在关键操作后触发。
架构总览
ProjectModel 作为项目数据中枢,贯穿以下关键流程:
- 初始化:从协议 Schema 构造,填充默认属性,调用 update 进行首次赋值。
- 生命周期:支持 active/deactivate 打开/关闭当前文件;setConfig/setI18n/setEnv 等配置更新;lock/unlock 项目锁定。
- 文件管理:页面/区块的创建、更新、复制、移动、删除;目录树清理(移除 dsl);页面树导出。
- 事件驱动:所有重要变更均通过 emitter 发布事件,设计器引擎订阅并执行保存、激活、发布、历史记录等动作。
- 序列化:toDsl 输出项目 DSL,用于持久化、渲染、发布与出码。
sequenceDiagram
participant UI as "设计器UI"
participant ENG as "Engine"
participant PM as "ProjectModel"
participant EM as "emitter"
participant S as "服务端/存储"
UI->>PM : 调用 createPage/updatePage/removePage...
PM->>EM : emit EVENT_PROJECT_PAGES_CHANGE + EVENT_PROJECT_CHANGE
EM-->>ENG : on(EVENT_PROJECT_PAGES_CHANGE / EVENT_PROJECT_CHANGE)
ENG->>S : saveProject/saveFile(根据事件类型)
S-->>ENG : 成功/失败
ENG-->>UI : 触发视图更新/提示
UI->>PM : active(file)/deactivate()
PM->>EM : emit EVENT_PROJECT_ACTIVED
EM-->>ENG : on(EVENT_PROJECT_ACTIVED)
ENG->>S : getFile(file.id, project.toDsl())
S-->>ENG : 返回dsl
ENG->>ENG : new BlockModel(dsl), 更新当前
详细组件分析
ProjectModel 类结构与属性
- 核心属性
- 标识与平台:id、UID、platform
- 基础信息:name、description、homepage
- 结构与状态:pages、blocks、currentFile、locked
- 配置与国际化:config、uniConfig、globals、i18n、env
- 外部依赖与资源:dependencies、apis、meta
- 静态属性:attrs 列表,用于 update 的批量赋值范围控制
- 构造函数:接收 ProjectSchema,自动生成 id/UID 并调用 update(silent=true)
事件系统集成
- 事件常量
- 项目级:EVENT_PROJECT_CHANGE、EVENT_PROJECT_ACTIVED、EVENT_PROJECT_DEPS_CHANGE、EVENT_PROJECT_PUBLISH、EVENT_PROJECT_FILE_PUBLISH、EVENT_PROJECT_GEN_SOURCE
- 文件级:EVENT_PROJECT_PAGES_CHANGE、EVENT_PROJECT_BLOCKS_CHANGE、EVENT_PROJECT_APIS_CHANGE、EVENT_PROJECT_META_CHANGE
- 触发时机
- update:非静默时触发 EVENT_PROJECT_CHANGE
- active/deactivate:非静默时触发 EVENT_PROJECT_ACTIVED
- 依赖/页面/区块/API/Meta 变更:分别触发对应文件事件与通用变更事件
- publish/genSource:触发发布/出码事件
- 设计器引擎订阅
- 订阅项目变更、文件激活、发布、出码、区块/节点变更等,驱动保存、激活文件、历史记录等
生命周期管理
- 初始化
- 从协议 Schema 构造 ProjectModel,设置 id/UID,调用 update(silent=true) 完成属性填充
- 更新
- update(schema, silent?):按 attrs 列表批量赋值,非静默则触发变更事件
- setConfig/setI18n/setEnv/setUniConfig/setGloblas:局部配置更新,非静默触发变更事件
- 序列化
- toDsl(version?):生成项目 DSL,清理页面/区块 dsl 字段,保留必要元数据
- 文件生命周期
- active(file, silent?) / deactivate(silent?):切换当前文件,非静默触发激活事件
- createPage/updatePage/removePage/movePageTo/clonePage:页面/目录管理
- createBlock/updateBlock/removeBlock/cloneBlock:区块管理
- saveToBlock:将页面保存为区块
- 项目状态
- lock(id)/unlock(id):项目锁定/解锁,非静默触发变更事件
- publish(file?)/genSource():触发发布/出码事件
与 BlockModel/NodeModel 的协作
- 页面/区块文件以 DSL 形式存在,打开文件时由引擎通过服务端获取 DSL,再由 ProjectModel.active 切换当前文件,随后由引擎创建 BlockModel 并更新当前上下文
- BlockModel.update/toDsl 与 NodeModel.update/toDsl 协同,形成从节点到区块再到项目的完整变更链路
- 设计器引擎订阅 BLOCK/ NODE 变更事件,自动保存当前文件并维护历史记录
classDiagram
class ProjectModel {
+id : string
+platform : PlatformType
+pages : PageFile[]
+blocks : BlockFile[]
+currentFile : PageFile|BlockFile|null
+update(schema, silent)
+active(file, silent)
+deactivate(silent)
+createPage(...)
+updatePage(...)
+createBlock(...)
+updateBlock(...)
+toDsl(version?)
}
class BlockModel {
+id : string
+nodes : NodeModel[]
+update(schema, silent)
+toDsl(version?)
+addNode(...)
+move(...)
+removeNode(...)
}
class NodeModel {
+id : string
+name : string
+from : NodeFrom
+children : NodeModel[]|string|JSExpression
+props : Record
+events : Record
+directives : DirectiveModel[]
+update(schema, silent)
+toDsl()
}
ProjectModel --> BlockModel : "打开文件时创建"
BlockModel --> NodeModel : "持有节点树"
API 参考(核心方法)
- update(schema: Partial, silent?: boolean): void
- 参数:部分项目 Schema;silent 控制是否触发事件
- 行为:按静态 attrs 列表批量赋值;非静默触发 EVENT_PROJECT_CHANGE
- active(file: BlockFile|PageFile, silent?: boolean): void
- 行为:设置 currentFile;非静默触发 EVENT_PROJECT_ACTIVED
- deactivate(silent?: boolean): void
- 行为:清空 currentFile;非静默触发 EVENT_PROJECT_ACTIVED
- createPage(page: PageFile, parentId?: string, silent?: boolean): Promise
- 行为:生成页面 ID,必要时创建默认 DSL,插入目录或根;非静默触发 PAGE/PROJECT 变更事件;无目录且未打开文件时自动激活新建页面
- updatePage(page: PageFile, silent?: boolean): PageFile | undefined
- 行为:按 id 查找并合并更新;非静默触发 PAGE/PROJECT 变更事件
- removePage(id: string, silent?: boolean): void
- 行为:递归删除页面/目录;若为首页或当前文件则同步取消激活;非静默触发 PAGE/PROJECT 变更事件
- movePageTo(pageId: string, parentId?: string, silent?: boolean): boolean
- 行为:移动页面至指定父目录;非静默触发 PAGE/PROJECT 变更事件
- clonePage(page: PageFile, parentId?: string, silent?: boolean): void
- 行为:克隆页面并插入相邻位置;非静默触发 PAGE/PROJECT 变更事件
- saveToBlock(page: PageFile, silent?: boolean): Promise
- 行为:将页面转换为区块并追加到 blocks;非静默触发 BLOCK/PROJECT 变更事件
- createBlock(block: BlockFile, silent?: boolean): Promise
- 行为:生成区块 ID,创建默认 DSL;非静默触发 BLOCK/PROJECT 变更事件;无当前文件且来自 Schema 时自动激活
- updateBlock(block: BlockFile, silent?: boolean): BlockFile | undefined
- 行为:按 id 查找并合并更新;必要时同步更新 DSL 名称;非静默触发 BLOCK/PROJECT 变更事件
- removeBlock(id: string, silent?: boolean): void
- 行为:从 blocks 中删除;若为当前文件则取消激活;非静默触发 BLOCK/PROJECT 变更事件
- cloneBlock(block: BlockFile, silent?: boolean): void
- 行为:克隆区块并插入相邻位置;非静默触发 BLOCK/PROJECT 变更事件
- setDeps(item: Dependencie, silent?: boolean): void
- 行为:新增/更新依赖;非静默触发 DEPS/PROJECT 变更事件
- removeDeps(item: Dependencie, silent?: boolean): void
- 行为:删除依赖;非静默触发 DEPS/PROJECT 变更事件
- setApi(item: ApiSchema, silent?: boolean): ApiSchema
- setApis(items: ApiSchema[], silent?: boolean): void
- removeApi(name: string, silent?: boolean): void
- setMeta(item: MetaSchema, silent?: boolean): void
- removeMeta(code: string, silent?: boolean): void
- existMetaCode(code: string, excludes?: string[]): boolean
- setHomepage(id: string, silent?: boolean): void
- setConfig(config: ProjectConfig, silent?: boolean): void
- setUniConfig(key: keyof UniConfig, value: Record<string, any>, silent?: boolean): void
- setGloblas(key: keyof GlobalConfig, value: string|JSFunction, silent?: boolean): void
- setI18n(i18n: I18nConfig, silent?: boolean): void
- setEnv(env: EnvConfig[], silent?: boolean): void
- publish(file?: PageFile|BlockFile): void
- genSource(): void
- lock(id: string): void
- unlock(id: string): void
- getPageRoutes(pageRouteName?: string, pageBasePath?: string): { id, path, name, title, meta }[]
- toDsl(version?: string): ProjectSchema
使用示例(路径指引)
- 在设计器中获取当前项目实例
- 在 AI 功能中使用项目 DSL
- 引擎初始化并创建 ProjectModel
- 引擎订阅事件并处理保存/激活/发布/出码
依赖关系分析
- 内聚性
- ProjectModel 聚合了项目级所有实体与行为,内聚度高
- 耦合性
- 与 BlockModel/NodeModel 通过 DSL 间接耦合;与引擎通过事件耦合
- 事件耦合
- 设计器引擎通过 emitter 订阅项目/文件/区块/节点事件,形成解耦的观察者模式
- 外部依赖
- 工具库 uid、cloneDeep、mitt 等
graph LR
PM["ProjectModel"] --> EM["emitter"]
EM --> ENG["Engine"]
ENG --> S["服务/存储"]
PM --> BM["BlockModel"]
BM --> NM["NodeModel"]
性能考量
- 静默模式:大量批量更新建议使用 silent=true,减少事件风暴与重复渲染
- DSL 清理:toDsl 与 cleanPagesDsl 会移除 dsl 字段,避免序列化体积过大
- 事件粒度:区分通用变更与文件级变更(如 pages/blocks/apis/meta),有助于前端选择性刷新
- 异步策略:创建/保存页面/区块时采用延迟激活与异步保存,提升交互流畅度
故障排查指南
- 事件未触发
- 检查是否传入 silent=true 导致静默更新
- 确认 emitter.on 是否已订阅对应事件
- 文件未激活
- 确认 active(file) 调用与 EVENT_PROJECT_ACTIVED 订阅链路
- 检查服务端 getFile 返回的 dsl 是否为空
- 保存失败
- 检查引擎锁状态(checkLocked),确认项目未被他人锁定
- 确认服务端 saveProject/saveFile 接口返回成功
- 页面/区块未更新
- 确认 updatePage/createPage/removePage 等方法的 id/parentId 参数正确
- 确认事件类型(create/update/delete/clone)与订阅处理逻辑一致
结论
ProjectModel 以清晰的数据模型与事件驱动机制,实现了项目级状态管理与文件生命周期控制;配合 BlockModel/NodeModel 的层级结构与变更传播,形成了从页面/区块到项目的完整数据流。通过静默模式、事件粒度与异步策略,可在保证一致性的同时兼顾性能与用户体验。建议在复杂批量操作中优先使用静默更新,并在关键节点订阅相应事件以确保持久化与渲染同步。
附录
- 项目与区块模型关系概览
参考资料
VTJ.PRO 是一个开源的、AI 驱动的 Vue 3 企业级应用开发平台。它通过 AI 智能体与可视化编排实现高效开发,并支持导出标准 Vue 代码以避免平台锁定。更多信息请访问:
- 📘 官方文档:vtj.pro/
- 🌐 在线平台:app.vtj.pro/
- 📦 开源仓库:gitee.com/newgateway/…