区块与页面模板
块模板和页面模板构成了 VTJ 用于创建可复用 UI 组件和完整页面布局的抽象层。该系统使开发者能够设计、管理和分发预构建的 UI 解决方案,支持本地块管理和远程模板市场的集成。
模板系统架构
模板系统通过分层架构运行,该架构连接了设计器界面、核心引擎和远程市场服务。
graph TD
subgraph "Template Types"
ST[Schema - Designed]
UST[UrlSchema - Referenced]
PT[Plugin - Plugin Library]
end
subgraph "Designer Layer"
BW[Blocks Widget]
TW[Templates Widget]
UB[useBlocks Hook]
UT[useTemplates Hook]
end
subgraph "Core Engine Layer"
ENG[Engine]
PM[ProjectModel]
BM[BlockModel]
end
subgraph "Data Layer"
LS[Local Storage]
RMS[Remote Marketplace Service]
end
ST --> BW
UST --> BW
PT --> BW
BW --> UB
TW --> UT
UB --> ENG
UT --> ENG
ENG --> PM
PM --> BM
BM --> LS
UT --> RMS
RMS -.-> UT
这种架构支持双向流动:模板可以从市场安装到本地块,本地块也可以被设计、引用或从插件库获取。
块模板概述
块模板是可复用的 UI 组件,在 VTJ 中存在于三个抽象级别。每个块由 BlockFile 接口表示,包含元数据和 DSL 内容,通过 ProjectModel 管理,并实例化为 BlockModel 实例。
块文件结构
块文件包含定义其身份和来源的基本元数据:
interface BlockFile {
type: FileType; // 'block'
id: string; // 唯一标识符
name: string; // 块名称
title: string; // 显示标题
category?: string; // 分组类别
market?: MarketInstallInfo; // 市场安装信息
fromType?: "Schema" | "UrlSchema" | "Plugin";
preset?: boolean; // 是否为预设插件
urls?: string; // Plugin/UrlSchema 的资源 URL
library?: string; // 插件库名称
dsl?: BlockSchema; // 块 DSL 内容
}
fromType 属性决定了块内容的来源和管理方式。块创建后此字段不可更改,以确保内容来源的完整性。
块类型及其特性
VTJ 支持三种不同的块类型,每种类型服务于开发工作流中的不同用例。
| 类型 | 描述 | URL 格式 | 可编辑 | 用例 |
|---|---|---|---|---|
| Schema | 在 VTJ 设计器内设计的块 | 无 | 是 | 从头创建的自定义组件 |
| UrlSchema | 从外部 JSON 引用的块 | 单个 JSON URL | 是 | 通过 CDN 或外部存储共享的块 |
| Plugin | 来自插件库的块 | 多个 (.css,.js) URL | 否(除非非预设) | 第三方组件库 |
类型区别会影响块的渲染、编辑和分发方式。标记为 preset: true 的插件块无法编辑或删除,以保护第三方组件的完整性。
块模式结构
块的实际内容通过 BlockSchema 定义,其中包括组件定义、状态管理和生命周期钩子。
classDiagram
class BlockSchema {
+id: string
+name: string
+locked: boolean
+inject: BlockInject[]
+state: BlockState
+lifeCycles: object
+methods: object
+computed: object
+watch: BlockWatch[]
+css: string
+props: Array
+emits: Array
+expose: string[]
+slots: Array
+nodes: NodeSchema[]
+dataSources: object
}
class BlockModel {
+update(schema silent)
+toDsl(version)
+addNode(node target position)
+removeNode(node silent)
+setState(name value silent)
+setFunction(type name value silent)
+setCss(content silent)
+dispose()
}
BlockModel ..> BlockSchema : 实例化
该模式支持 Vue 的 Composition API 模式,包括响应式状态、计算属性、侦听器、生命周期钩子以及通过 inject 属性进行依赖注入。
页面模板
页面模板将块的概念扩展到完整的页面布局,通过扩展 BlockFile 的 PageFile 接口支持路由、导航和页面级配置。
页面文件扩展
页面继承所有块属性,同时添加页面特定配置:
interface PageFile extends BlockFile {
dir?: boolean; // 目录与页面
layout?: boolean; // 布局页面标志
icon?: string; // 菜单图标
children?: PageFile[]; // 目录的子页面
mask?: boolean; // 在主模板内
hidden?: boolean; // 从菜单隐藏
raw?: boolean; // 源代码页面(非低代码)
pure?: boolean; // 纯页面标志
cache?: boolean; // 启用页面缓存
meta?: Record<string, any>; // 路由元数据
needLogin?: boolean; // UniApp 登录要求
style?: Record<string, any>; // UniApp 窗口配置
}
这些属性支持页面级配置,包括路由、导航菜单生成、缓存策略和平台特定行为。
模板市场集成
VTJ 提供了一个远程模板市场系统,支持跨项目发现、安装和管理预构建的模板。
模板数据模型
来自市场的模板遵循 TemplateDto 结构:
interface TemplateDto {
id: string; // 模板 ID
name: string; // 模板名称
label: string; // 显示标签
category: string; // 类别分组
author: string; // 作者标识符
userId: string; // 用户 ID
cover: string; // 封面图片 URL
vip?: boolean; // VIP 标志
share?: boolean; // 共享标志
}
模板按作者所有权(用户的模板与共享模板)和类别组织,支持个性化浏览和发现。
模板安装流程
安装模板涉及从远程市场获取 DSL 并将其与当前页面或块配置合并。
graph TD
A[用户点击使用] --> B{是否已登录?}
B -- 否 --> C[显示登录对话框]
C --> N{用户登录?}
N -- 是 --> D
N -- 否 --> O[取消]
B -- 是 --> D[获取模板 DSL]
D --> E{DSL 可用?}
E -- 否 --> F[错误: 模板未发布]
E -- 是 --> G[获取当前文件 DSL]
G --> H[保留 ID 和名称]
H --> I[合并模板 DSL]
I --> J[更新项目中的文件]
J --> K[触发保存事件]
K --> L[更新当前模型]
L --> M[成功消息]
安装过程保留当前文件的身份(ID 和名称),同时用模板 DSL 替换内容,确保现有文件引用保持完整。
设计器中的块管理
设计器提供了一个全面的块小部件,支持块的创建、编辑、搜索和拖放操作。
块小部件功能
块小部件通过有组织的 UI 支持几个关键操作:
graph TD
subgraph "Block Widget"
S[Search Input]
C[Category Collapse]
I[Block Items]
end
subgraph "Block Operations"
A[Add Block]
E[Edit Block]
CP[Copy Block]
R[Remove Block]
DRG[Drag to Canvas]
end
S --> I
C --> I
I --> A
I --> E
I --> CP
I --> R
I --> DRG
块按类别分组,带有可折叠的部分,显示计数徽章,并支持对名称和标题进行关键词搜索。
块创建配置
创建新块需要通过对话框表单配置基本属性:
| 属性 | 必填 | 描述 | 验证 |
|---|---|---|---|
| fromType | 是 | 块来源类型 | Schema/UrlSchema/Plugin(创建后禁用) |
| name | 是 | 块标识符 | 英文驼峰式命名,正则模式验证 |
| title | 是 | 显示标题 | 用户友好的文本 |
| library | 条件 | 插件库名称 | 仅 Plugin 类型需要 |
| urls | 条件 | 资源 URL | Plugin/UrlSchema 类型需要,文件上传器 |
| category | 否 | 分组类别 | 带有过滤/创建选项的选择 |
名称字段使用正则验证(NAME_REGEX)以确保代码生成使用正确的标识符格式。
提示: 块名称必须遵循驼峰式命名约定,并根据现有块名称进行验证以防止重复。
createEmtpyModel函数默认创建一个带有fromType: 'Schema'的新模板,为新块奠定基础。
设计器中的模板管理
模板小部件提供市场访问,具有分类、过滤和安装功能。
模板组织
模板通过选项卡式界面组织,将用户拥有的模板与共享模板分开:
graph TD
T[All Templates] --> G1[My Templates]
T --> G2[Shared Templates]
G1 --> C1[Category 1]
G1 --> C2[Category 2]
G2 --> C3[Category N]
C1 --> TI1[Template 1]
C2 --> TI2[Template 2]
C3 --> TIN[Template N]
isOwner 函数根据用户 ID 和共享状态确定编辑/删除权限,确保用户只能管理自己的非共享模板。
模板安装要求
模板安装需要身份验证并遵循权限模型:
| 条件 | 操作 | 用户体验 |
|---|---|---|
| 未登录 | 显示登录确认 | 带有登录按钮的“模板需要登录”对话框 |
| 已登录,模板可用 | 安装模板 | “模板已加载”成功消息 |
| 已登录,模板不可用 | 错误通知 | “模板未发布版本”错误警报 |
| 模板安装到页面 | 更新页面 DSL | 当前页面内容被模板替换 |
安装过程使用 engine.onSaveBlockFileFinish 确保文件修改后安全更新状态。
核心实现模式
理解底层的实现模式可以有效地扩展和定制模板系统。
块模型生命周期
BlockModel 通过事件驱动更新管理块状态:
stateDiagram
[*] --> Created : constructor(schema)
Created --> Updated : update(schema, silent=false)
Updated --> Updated : emit EVENT_BLOCK_CHANGE
Created --> Disposed : dispose()
Updated --> Disposed : nodes cleared
更新方法中的 silent 参数控制是否发出更改事件,允许批量修改而不触发响应式级联。
状态管理方法
BlockModel 为块功能的不同方面提供细粒度的状态操作方法:
// 函数管理 (methods, computed, lifeCycles)
setFunction(type, name, value, silent);
removeFunction(type, name, silent);
// 状态管理
setState(name, value, silent);
removeState(name, silent);
// 侦听器管理
setWatch(watch, silent);
removeWatch(watch, silent);
// Props, Emits, Slots
setProp(prop, silent);
removeProp(prop, silent);
setEmit(emit, silent);
removeEmit(emit, silent);
setSlot(slot, silent);
removeSlot(slot, silent);
除非传递 silent: true,否则每个方法都会触发 EVENT_BLOCK_CHANGE,从而实现对响应式的细粒度控制。
项目模型集成
ProjectModel 作为块和页面的中央注册表,提供 CRUD 操作和验证:
// 块操作
createBlock(block: BlockFile, silent: boolean)
updateBlock(block: BlockFile, silent: boolean)
cloneBlock(block: BlockFile, silent: boolean)
removeBlock(id: string, silent: boolean)
existBlockName(name: string, excludes: string[]): boolean
// 页面操作 (扩展块)
createPage(page: PageFile, parentId?: string, silent: boolean)
updatePage(page: PageFile, silent: boolean)
clonePage(page: PageFile, parentId?: string, silent: boolean)
removePage(id: string, silent: boolean)
existPageName(name: string, excludes: string[]): boolean
isPageFile 类型守卫区分页面文件和块文件,实现类型安全操作。
提示: ProjectModel 在
project.blocks数组中维护块引用,并通过existBlockName和existPageName方法提供验证,防止项目结构中的命名冲突。
高级模板功能
VTJ 的模板系统支持复杂组件架构的高级功能。
依赖注入
块可以通过 inject 数组声明依赖,启用组合模式:
interface BlockInject {
name: string; // 注入键
from?: string | JSExpression; // 源表达式
default?: JSONValue | JSExpression; // 默认值
}
这使块能够从父上下文接收服务、工具或配置。
插槽定义
块可以定义带有类型参数的命名插槽以进行内容投影:
interface BlockSlot {
name: string; // 插槽标识符
params: string[]; // 作用域插槽的参数名称
}
插槽支持灵活的内容组合,同时通过参数声明保持类型安全。
事件发出
块通过 emits 数组声明自定义事件以与父组件通信:
interface BlockEmit {
name: string; // 事件名称
params: string[]; // 参数类型
}
事件定义支持具有 TypeScript 感知的事件处理和文档生成。
集成点
模板系统与多个 VTJ 子系统集成,以提供全面的功能。
设计器集成
设计器提供用于块和模板管理的小部件面板:
- Blocks Widget:支持 CRUD 操作的本地块管理
- Templates Widget:市场模板浏览和安装
- Drag & Drop:块可以直接拖放到设计画布上
- Context Menu:右键单击操作,用于编辑、复制和移除
渲染器集成
块通过 VTJ 渲染器在运行时渲染,支持:
- Property Binding:通过表达式进行动态属性绑定
- Event Handling:事件发出和父组件通信
- Slot Composition:命名和作用域插槽渲染
- State Management:带有侦听器和计算属性的响应式状态
渲染器解释 BlockSchema 以生成具有完整 Composition API 支持的 Vue 组件。
代码生成集成
块通过代码生成器生成 Vue SFC 代码,产生:
- Component Definition:Props、emits、expose 声明
- Setup Function:状态、方法、计算属性、侦听器
- Template:转换为 Vue 模板语法的节点
- Style:来自块模式的 CSS 内容
- Script:生命周期钩子和依赖注入
最佳实践
块设计指南
- 单一职责:设计块以实现专注的、可复用的功能
- 类型安全:使用显式类型声明 props 和 emits
- 文档:使用有意义的标题和描述
- 分类:使用 category 属性对相关块进行分组
- 版本控制:使用
__VERSION__进行模板版本跟踪
模板组织
- 类别结构:使用分层类别以提高可发现性
- 封面图片:为模板提供清晰的预览图片
- 文档:在模板描述中包含使用说明
- 版本管理:在不同模板版本之间保持向后兼容性
性能考虑
- 懒加载:对大型外部块库使用
UrlSchema - 代码分割:将复杂的块拆分为更小的可复用单元
- 资源优化:最小化 Plugin 块中的 CSS 和 JS 资源
下一步
要加深您对 VTJ 模板系统及相关概念的理解:
- 探索 Material Schema Configuration 以了解详细的组件属性定义
- 了解 Creating Custom Material Components 以构建可复用组件
- 查看 DSL to Vue Code Generation 以了解块如何转换为运行时代码
- 研究 Engine API Reference 以进行编程块操作
- 检查 Project Structure and File Organization 以了解布局策略
参考资料
- 开源代码仓库:gitee.com/newgateway/…