AI 驱动的 Vue3 应用开发平台 深入探究(二):核心概念之DSL模式与数据模型

DSL 模式与数据模型

VTJ 低代码平台利用一种全面的领域特定语言(DSL)来以声明式方式描述应用程序。DSL Schema 定义了所有实体(项目、页面、组件和数据源)的结构契约,而 Model 层则通过事件驱动架构提供运行时操作能力。理解这些基础数据模型对于扩展平台、创建自定义物料或构建集成至关重要。

架构概览

DSL 系统遵循 Schema 定义(静态结构)与 Model 实现(运行时行为)的清晰分离。Schema 充当数据序列化的蓝图,而 Model 封装了业务逻辑、状态管理以及用于变更跟踪的事件发射。

flowchart TD subgraph "静态 Schema 定义" ProjectSchema -->|定义| ProjectModel BlockSchema -->|定义| BlockModel NodeSchema -->|定义| NodeModel end subgraph "运行时 Model 类" ProjectModel -->|包含| BlockModel BlockModel -->|包含| NodeModel NodeModel -->|使用| PropModel NodeModel -->|使用| EventModel NodeModel -->|使用| DirectiveModel PropModel -->|包含| SharedTypes EventModel -->|包含| SharedTypes DirectiveModel -->|包含| SharedTypes end subgraph "共享类型定义" JSExpression JSFunction JSONValue end

NodeSchema - 组件结构

NodeSchema 接口定义了 VTJ DSL 中任何组件的基础构建块。每个 UI 元素------从简单的按钮到复杂的容器------都表示为具有标准化结构的节点。

核心属性

属性 类型 必填 描述
id string 节点的唯一标识符
name string 组件名称(例如 'el-button', 'div')
from NodeFrom 组件来源/原始引用
locked boolean 节点是否锁定以禁止编辑
invisible boolean 节点在设计器中是否隐藏
props NodeProps 组件属性和特性
events NodeEvents 绑定到节点的事件处理器
directives NodeDirective[] Vue 风格指令(v-if, v-for 等)
children NodeChildren 子组件
slot `string NodeSlot`

组件来源 (NodeFrom)

组件可以源于多种来源,由 NodeFrom 类型定义:

typescript 复制代码
type NodeFrom =
  | string // NPM 包名
  | NodeFromSchema // 内部 Schema 引用
  | NodeFromUrlSchema // 远程 JSON Schema
  | NodeFromPlugin; // 远程插件组件

interface NodeFromSchema {
  type: "Schema";
  id: string; // Block Id 引用
}

interface NodeFromUrlSchema {
  type: "UrlSchema";
  url: string; // JSON Schema URL
}

interface NodeFromPlugin {
  type: "Plugin";
  urls: string[]; // 插件资源 URL
  library?: string; // 导出的库名称
}

属性系统 (NodeProps)

props 对象包含所有组件属性,支持静态值、动态表达式和函数定义:

typescript 复制代码
interface NodeProps {
  key?: string | number | JSExpression;
  ref?: string | JSExpression | JSFunction;
  style?: Record<string, any>;
  class?: string | string[] | JSExpression;
  [index: string]: JSONValue | JSExpression | JSFunction;
}

NodeSchema 示例

json 复制代码
{
  "id": "button_123",
  "name": "el-button",
  "props": {
    "type": "primary",
    "size": {
      "type": "JSExpression",
      "value": "state.buttonSize"
    },
    "onClick": {
      "type": "JSFunction",
      "value": "function() { state.count++ }"
    }
  },
  "events": {
    "click": {
      "name": "click",
      "handler": {
        "type": "JSFunction",
        "value": "function(event) { console.log(event); }"
      }
    }
  }
}

BlockSchema - 组件块结构

BlockSchema 代表一个可复用的组件块,它封装了状态、逻辑和模板结构。Blocks 是创建自定义物料的主要单元,可以用作页面、组件或局部模板。

完整的 Block 结构

属性 类型 描述
id string 唯一的块标识符
name string 组件/块名称
locked boolean 锁定状态
inject BlockInject[] 依赖注入
state BlockState 响应式状态数据
lifeCycles Record<string, JSFunction> 生命周期钩子
methods Record<string, JSFunction> 自定义方法
computed Record<string, JSFunction> 计算属性
watch BlockWatch[] 侦听器
css string 组件样式
props `Array<string BlockProp>`
emits `Array<string BlockEmit>`
expose string[] 暴露的公共属性
slots `Array<string BlockSlot>`
nodes NodeSchema[] 模板节点树
dataSources Record<string, DataSourceSchema> 数据源

高级特性

状态管理:

typescript 复制代码
type BlockState = Record<string, JSONValue | JSExpression | JSFunction>;

// 示例
{
  "count": 0,
  "user": {
    "type": "JSExpression",
    "value": "fetchUser()"
  },
  "computedValue": {
    "type": "JSFunction",
    "value": "function() { return this.count * 2 }"
  }
}

侦听器:

typescript 复制代码
interface BlockWatch {
  id?: string;
  source: JSFunction | JSExpression;
  deep?: boolean;
  immediate?: boolean;
  handler: JSFunction;
}

// 示例
{
  "id": "watch_1",
  "source": {
    "type": "JSExpression",
    "value": "state.userData"
  },
  "deep": true,
  "handler": {
    "type": "JSFunction",
    "value": "function(newVal, oldVal) { console.log('Changed:', newVal); }"
  }
}

ProjectSchema - 项目配置

ProjectSchema 定义了整个应用程序结构,包括页面、块、依赖项和全局配置。它是任何 VTJ 应用程序的顶层入口点。

项目属性

属性 类型 描述
id string 项目唯一标识符
name string 项目名称
description string 项目描述
platform PlatformType 目标平台
pages PageFile[] 页面文件列表
blocks BlockFile[] 可复用的块组件
homepage string 默认页面 ID
dependencies Dependencie[] 外部依赖项
apis ApiSchema[] API 定义
meta MetaSchema[] Meta 查询配置
config ProjectConfig 项目配置
uniConfig UniConfig UniApp 特定配置
globals GlobalConfig 全局应用配置
i18n I18nConfig 国际化配置
env EnvConfig[] 环境变量

平台类型

typescript 复制代码
type PlatformType = "web" | "h5" | "uniapp";

全局配置

GlobalConfig 接口提供应用程序级别的配置,用于路由、状态管理和 HTTP 处理:

typescript 复制代码
interface GlobalConfig {
  css?: string; // 全局样式
  store?: JSFunction; // Pinia/Vuex store
  access?: JSFunction; // 权限控制
  enhance?: JSFunction; // 应用增强
  axios?: JSFunction; // Axios 配置
  request?: JSFunction; // 请求拦截器
  response?: JSFunction; // 响应拦截器
  beforeEach?: JSFunction; // 路由前置守卫
  afterEach?: JSFunction; // 路由后置钩子
}

DataSourceSchema - 数据管理

数据源支持声明式的数据获取和转换,支持 API、Mock 数据和 Meta 查询。

数据源类型

typescript 复制代码
type DataSourceType = "api" | "cube" | "meta" | "mock";

DataSourceSchema 接口

typescript 复制代码
interface DataSourceSchema {
  type: DataSourceType;
  ref?: string; // 引用项目 API
  name: string; // 数据源名称
  label?: string; // 显示标签
  transform?: JSFunction; // 数据转换
  test?: JSFunction; // 测试用例函数
  mockTemplate?: JSFunction; // Mock 数据模板
}

API Schema 定义

项目级别的 API 单独定义,并由数据源引用:

typescript 复制代码
interface ApiSchema {
  id: string; // 唯一标识符
  name: string; // API 名称
  label?: string; // 描述
  url: string; // 请求 URL
  category?: string; // 分组/类别
  method?: ApiMethod; // HTTP 方法
  settings?: Record<string, any>; // 请求设置
  headers?: JSExpression | JSFunction;
  jsonpOptions?: Record<string, any>;
  mockTemplate?: JSFunction;
  mock?: boolean; // 启用 Mock
}

type ApiMethod = "get" | "post" | "put" | "delete" | "patch" | "jsonp";

文件结构 - 页面和块

VTJ DSL 通过 PageFileBlockFile 接口区分页面文件和块文件。

通用文件属性

typescript 复制代码
interface BlockFile {
  type: FileType; // 'block' 或 'page'
  id: string;
  name: string; // 文件名
  title: string; // 显示标题
  category?: string;
  market?: MarketInstallInfo; // 市场安装信息
  fromType?: "Schema" | "UrlSchema" | "Plugin";
  preset?: boolean; // 预设(不可编辑)
  urls?: string; // 资源 URL
  library?: string; // 插件库名称
  dsl?: BlockSchema; // 文件内容
}

页面特定属性

页面扩展了块文件,增加了路由和导航特定的属性:

typescript 复制代码
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: 窗口样式
}

Model 类 - 运行时层

Schema 定义了静态结构,而 Model 类提供运行时操作能力。Models 实现了事件驱动模式以跟踪变更。

NodeModel

NodeModel 类封装 NodeSchema 并提供了操作节点结构的方法:

typescript 复制代码
class NodeModel {
  constructor(schema: NodeSchema, parent: NodeModel | null);

  // 结构操作
  update(schema: Partial<NodeSchema>, silent?: boolean): void;
  setChildren(
    children: NodeSchema[] | string | JSExpression,
    silent?: boolean,
  ): void;
  setSlot(slot?: string | NodeSlot, silent?: boolean): void;

  // 树操作
  appendChild(node: NodeModel, silent?: boolean): void;
  removeChild(node: NodeModel, silent?: boolean): void;
  insertAfter(node: NodeModel, silent?: boolean): void;
  insertBefore(node: NodeModel, silent?: boolean): void;

  // 属性管理
  setProp(
    name: string,
    value: JSONValue | JSExpression | JSFunction,
    defaultValue?: JSONValue | JSExpression | JSFunction,
    silent?: boolean,
  ): void;
  removeProp(name: string, silent?: boolean): void;
  getPropValue(name: string): any;

  // 事件和指令
  setEvent(scheam: NodeEvent, silent?: boolean): void;
  removeEvent(name: string, silent?: boolean): void;
  setDirective(scheam: NodeDirective | DirectiveModel, silent?: boolean): void;
  removeDirective(dirctive: DirectiveModel, silent?: boolean): void;

  // 状态管理
  lock(silent?: boolean): void;
  unlock(silent?: boolean): void;
  setVisible(visible: boolean, silent?: boolean): void;

  // 导出
  toDsl(): NodeSchema;
  dispose(silent?: boolean): void;
}

所有 Model 方法都接受一个可选的 silent 参数。当传入 silent: true 时,操作将执行更改而不会触发事件。这对于批量操作非常有用,可以避免过多的事件发射和重新渲染。

BlockModel

BlockModel 管理块级别的状态、逻辑和节点树:

typescript 复制代码
class BlockModel {
  constructor(schema: BlockSchema);

  // 状态和函数
  setFunction(
    type: "methods" | "computed" | "lifeCycles",
    name: string,
    value: JSFunction,
    silent?: boolean,
  ): void;
  removeFunction(
    type: "methods" | "computed" | "lifeCycles",
    name: string,
    silent?: boolean,
  ): void;
  setState(
    name: string,
    value: JSONValue | JSExpression | JSFunction,
    silent?: boolean,
  ): void;
  removeState(name: string, silent?: boolean): void;

  // 侦听器
  setWatch(watch: BlockWatch, silent?: boolean): void;
  removeWatch(watch: BlockWatch, silent?: boolean): void;

  // Props, emits, slots, inject
  setProp(prop: BlockProp, silent?: boolean): void;
  setEmit(emit: string | BlockEmit, silent?: boolean): void;
  setSlot(slot: string | BlockSlot, silent?: boolean): void;
  setInject(inject: BlockInject, silent?: boolean): void;

  // 数据源
  setDataSource(source: DataSourceSchema, silent?: boolean): void;
  removeDataSource(name: string, silent?: boolean): void;

  // 节点树操作
  addNode(
    node: NodeModel,
    target?: NodeModel,
    position: DropPosition = "inner",
    silent?: boolean,
  ): void;
  removeNode(node: NodeModel, silent?: boolean): void;
  move(
    node: NodeModel,
    target?: NodeModel,
    position: DropPosition = "inner",
    silent?: boolean,
  ): void;
  movePrev(node: NodeModel, silent?: boolean): void;
  moveNext(node: NodeModel, silent?: boolean): void;
  cloneNode(target: NodeModel, silent?: boolean): void;

  // 导出和生命周期
  toDsl(version?: string): BlockSchema;
  dispose(): void;
}

type DropPosition = "left" | "right" | "top" | "bottom" | "inner";

ProjectModel

ProjectModel 管理整个应用程序结构:

typescript 复制代码
class ProjectModel {
  constructor(schema: ProjectSchema);

  // 文件管理
  active(file: BlockFile | PageFile, silent?: boolean): void;
  deactivate(silent?: boolean): void;

  // 页面操作
  createPage(
    page: PageFile,
    parentId?: string,
    silent?: boolean,
  ): Promise<void>;
  updatePage(page: PageFile, silent?: boolean): void;
  clonePage(page: PageFile, parentId?: string, silent?: boolean): void;
  removePage(id: string, silent?: boolean): void;
  getPage(id: string): PageFile | undefined;
  getPages(): PageFile[];

  // 块操作
  createBlock(block: BlockFile, silent?: boolean): Promise<void>;
  updateBlock(block: BlockFile, silent?: boolean): void;
  cloneBlock(block: BlockFile, silent?: boolean): void;
  removeBlock(id: string, silent?: boolean): void;
  getBlock(id: string): BlockFile | undefined;

  // 依赖项
  setDeps(item: Dependencie, silent?: boolean): void;
  removeDeps(item: Dependencie, silent?: boolean): void;

  // APIs 和 Meta
  setApi(item: ApiSchema, silent?: boolean): void;
  removeApi(name: string, silent?: boolean): void;
  setMeta(item: MetaSchema, silent?: boolean): void;
  removeMeta(code: 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;

  // 导出
  toDsl(_version?: string): ProjectSchema;
}

共享类型

DSL 使用共享类型定义来区分表达式、函数和静态数据。

JSExpression 和 JSFunction

typescript 复制代码
interface JSExpression {
  type: "JSExpression";
  id?: string;
  value: string; // JavaScript 表达式字符串
}

interface JSFunction {
  type: "JSFunction";
  id?: string;
  value: string; // JavaScript 函数字符串
}

JSON 值类型

typescript 复制代码
type JSONValue =
  | boolean
  | string
  | number
  | null
  | undefined
  | JSONArray
  | JSONObject;

type JSONArray = JSONValue[];
interface JSONObject {
  [key: string]: JSONValue;
}

事件系统

Model 层实现了事件驱动架构以跟踪变更。当发生修改时,每种 Model 类型都会发射特定的事件。

事件常量

typescript 复制代码
// NodeModel 事件
export const EVENT_NODE_CHANGE = "EVENT_NODE_CHANGE";

// BlockModel 事件
export const EVENT_BLOCK_CHANGE = "EVENT_BLOCK_CHANGE";

// ProjectModel 事件
export const EVENT_PROJECT_CHANGE = "EVENT_PROJECT_CHANGE";
export const EVENT_PROJECT_ACTIVED = "EVENT_PROJECT_ACTIVED";
export const EVENT_PROJECT_DEPS_CHANGE = "EVENT_PROJECT_DEPS_CHANGE";
export const EVENT_PROJECT_PAGES_CHANGE = "EVENT_PROJECT_PAGES_CHANGE";
export const EVENT_PROJECT_BLOCKS_CHANGE = "EVENT_PROJECT_BLOCKS_CHANGE";
export const EVENT_PROJECT_APIS_CHANGE = "EVENT_PROJECT_APIS_CHANGE";
export const EVENT_PROJECT_META_CHANGE = "EVENT_PROJECT_META_CHANGE";
export const EVENT_PROJECT_PUBLISH = "EVENT_PROJECT_PUBLISH";
export const EVENT_PROJECT_FILE_PUBLISH = "EVENT_PROJECT_FILE_PUBLISH";
export const EVENT_PROJECT_GEN_SOURCE = "EVENT_PROJECT_GEN_SOURCE";

完整 DSL 示例

一个最小但完整的 VTJ 项目 DSL 演示了所有 Schema 组件是如何协同工作的:

json 复制代码
{
  "__VTJ_PROJECT__": true,
  "__VERSION__": "1.0.0",
  "name": "my-app",
  "description": "Sample VTJ Application",
  "platform": "web",
  "homepage": "home",
  "config": {
    "title": "My Application",
    "logo": "/logo.png",
    "themeSwitchable": true
  },
  "globals": {
    "css": "body { margin: 0; }",
    "store": {
      "type": "JSFunction",
      "value": "() => {\n  return {\n    state: {\n      user: null\n    }\n  }\n}"
    }
  },
  "apis": [
    {
      "id": "api_user",
      "name": "getUserInfo",
      "url": "/api/user/info",
      "method": "get",
      "category": "user"
    }
  ],
  "pages": [
    {
      "type": "page",
      "id": "home",
      "name": "home",
      "title": "Home Page",
      "dsl": {
        "__VTJ_BLOCK__": true,
        "id": "home_block",
        "name": "Home",
        "state": {
          "count": 0,
          "message": "Hello VTJ"
        },
        "methods": {
          "increment": {
            "type": "JSFunction",
            "value": "function() { this.state.count++ }"
          }
        },
        "dataSources": {
          "userData": {
            "type": "api",
            "ref": "api_user",
            "name": "userData",
            "transform": {
              "type": "JSFunction",
              "value": "function(res) { return res.data }"
            }
          }
        },
        "nodes": [
          {
            "id": "container",
            "name": "div",
            "props": {
              "class": "container"
            },
            "children": [
              {
                "id": "title",
                "name": "h1",
                "children": "Welcome to VTJ"
              },
              {
                "id": "counter",
                "name": "div",
                "children": {
                  "type": "JSExpression",
                  "value": "`Count: ${state.count}`"
                }
              },
              {
                "id": "button",
                "name": "el-button",
                "props": {
                  "type": "primary"
                },
                "events": {
                  "click": {
                    "name": "click",
                    "handler": {
                      "type": "JSFunction",
                      "value": "function() { this.methods.increment() }"
                    }
                  }
                }
              }
            ]
          }
        ]
      }
    }
  ]
}

Model 使用模式

创建和操作节点

typescript 复制代码
import { NodeModel } from "@vtj/core";

// 创建一个新节点
const buttonNode = new NodeModel({
  id: "btn_1",
  name: "el-button",
  props: {
    type: "primary",
  },
});

// 静默更新属性(批量操作)
buttonNode.update({ props: { size: "large" } }, true);

// 添加事件处理器
buttonNode.setEvent({
  name: "click",
  handler: {
    type: "JSFunction",
    value: 'function() { console.log("clicked") }',
  },
});

// 导出为 DSL
const dsl = buttonNode.toDsl();

使用块

typescript 复制代码
import { BlockModel, NodeModel } from "@vtj/core";

// 创建一个带有状态的块
const block = new BlockModel({
  name: "Counter",
  state: {
    count: 0,
  },
  methods: {
    increment: {
      type: "JSFunction",
      value: "function() { this.state.count++ }",
    },
  },
});

// 向块添加节点
const textNode = new NodeModel({ name: "div", children: "Count: 0" });
const buttonNode = new NodeModel({
  name: "el-button",
  children: "Increment",
});

block.addNode(textNode);
block.addNode(buttonNode);

// 导出完整的块 Schema
const blockDsl = block.toDsl();

项目管理

typescript 复制代码
import { ProjectModel } from "@vtj/core";

// 从 Schema 初始化项目
const project = new ProjectModel(projectSchema);

// 打开文件进行编辑
const homePage = project.getPage("home");
if (homePage) {
  project.active(homePage);
}

// 创建新页面
await project.createPage({
  type: "page",
  id: "about",
  name: "about",
  title: "About Us",
  dsl: {
    name: "About",
    nodes: [],
  },
});

// 添加 API 依赖
project.setApi({
  id: "api_items",
  name: "getItems",
  url: "/api/items",
  method: "get",
});

// 导出项目用于序列化
const exportedProject = project.toDsl();

最佳实践

当对复杂的节点树执行批量更新时,请使用 silent: true 参数以避免触发过多的变更事件。完成所有更新后,如果需要,可以手动发射单个变更事件。

Schema 版本控制

始终在你的 Schema 中包含版本元数据,以便未来的迁移和兼容性:

typescript 复制代码
const schema = {
  __VTJ_BLOCK__: true,
  __VERSION__: "1.0.0",
  // ... 其他属性
};

表达式 vs 函数

  • 使用 JSExpression 用于解析为数据的简单值绑定(例如 state.count + 1
  • 使用 JSFunction 用于具有副作用的可执行代码(例如事件处理器、生命周期钩子、计算属性)

内存管理

当 Model 不再需要时,始终调用 dispose() 来清理事件监听器并防止内存泄漏:

typescript 复制代码
block.dispose();
project.dispose();
相关推荐
牛奶1 小时前
200 OK不是"成功"?HTTP状态码潜规则
前端·http·浏览器
Hilaku2 小时前
OpenClaw 很爆火,但没人敢聊它的权限安全🤷‍♂️
前端·javascript·程序员
ConardLi2 小时前
OpenClaw 完全指南:这可能是全网最新最全的系统化教程了!
前端·人工智能·后端
丁哥3 小时前
99.9%纯AI 做了一个ICO图标生成器(免费 全尺寸 不限文件大小)2ICO.CN欢迎品鉴
前端
兆子龙3 小时前
React Native 完全入门:从原理到实战
前端·javascript
哇哇哇哇3 小时前
vue3 watch解析
前端
SuperEugene3 小时前
Vite 实战教程:alias/env/proxy 配置 + 打包优化避坑|Vue 工程化必备
前端·javascript·vue.js
leafyyuki3 小时前
用 AI 和 SDD 重构 Vue2 到 Vue3 的实践记录
前端·人工智能
boooooooom4 小时前
别再用错 ref/reactive!90%程序员踩过的响应式坑,一文根治
javascript·vue.js·面试