AI 驱动的 Vue3 应用开发平台 深入探究(十二):物料系统之物料模式配置

1 阅读12分钟

物料模式配置

Material Schema Configuration 定义了组件在 VTJ 低代码系统中如何被描述和集成。该配置使设计器能够理解组件属性、事件、插槽和初始化模式,从而为开发者提供丰富的可视化编辑体验。

Schema 架构概览

Material Schema 系统由三个分层组成:类型定义层、组件描述层和物料库层。这些层协同工作,提供全面的组件元数据,用于驱动设计器界面、属性面板和代码生成。

该架构遵循声明式模式,每个组件通过一个 schema 对象描述自身。这种方法无需低代码引擎人工干预,即可自动生成属性面板、拖拽约束和代码模板。

核心类型系统

MaterialDescription 接口

MaterialDescription 接口是组件元数据的基础构建块。它提供了关于组件在设计器内应如何显示和行为的全面信息。

属性类型描述必填
namestring低代码系统的组件标识符
labelstring组件面板中显示的中文显示名称
categoryIdstring|number用于在面板中组织组件的分类 ID
aliasstring原始组件库导出名称(例如 ant-design-vue 中的 Button
parentstring嵌套导出的父组件(例如 Button.Group
iconstring组件面板的预览图标
docstring供参考的文档 URL
propsMaterialProp[]可配置属性数组
eventsArray<string| MaterialEvent>支持的组件事件
slotsArray<string|MaterialSlot>可用的组件插槽
snippetPartial<NodeSchema>初始组件配置模板
parentIncludesboolean|string[]有效的父组件,false 表示无
childIncludesboolean|string[]有效的子组件,false 表示无
hiddenboolean是否在组件面板中隐藏
fromNodeFrom组件来源标识符
packagestring用于代码生成的包名

该接口实现了全面的组件内省。例如,ElButton 组件定义了 18 个可配置属性、4 个插槽和一个点击事件,使其在设计器中完全可编辑。

MaterialProp 接口

组件属性通过 MaterialProp 接口定义,该接口描述了每个属性在设计器中应如何被编辑。

属性类型描述必填
namestring与组件 prop 匹配的属性标识符
labelstring属性面板的中文显示名称
titlestring工具提示或描述文本
defaultValueJSONValue创建组件时的初始值
settersstring| MaterialSetter | Array<string| MaterialSetter>用于编辑属性的 Setter
optionsstring[]| {label: string; value: JSONValue}[]选择/setter 的预定义值选项
typeDataType[]预期的数据类型

setters 属性尤为重要,因为它决定了属性面板中使用的编辑控件。可以提供多个 setters,允许用户在不同的编辑模式之间切换(例如,对于接受字符串和数字的属性,使用 ['InputSetter', 'NumberSetter'])。

MaterialEvent 和 MaterialSlot 接口

事件和插槽通过定义交互和组合能力完善了组件描述。

MaterialEvent 支持简单的字符串名称和带参数信息的详细事件描述:

// 简单事件定义
events: ["click"];

// 详细事件定义
events: [
  {
    name: "change",
    params: ["model"],
  },
];

MaterialSlot 同样支持简单的插槽名称和带参数的插槽:

// 简单插槽定义
slots: ["default", "icon"];

// 带参数的详细插槽定义
slots: [
  {
    name: "default",
    params: ["scope"],
  },
];

这些参数信息使设计器能够在用户绑定事件或使用插槽时提供上下文感知的代码补全。

属性配置模式

基础属性定义

最简单的属性定义由名称、标题、默认值和 setter 类型组成。此模式用于布尔值、字符串和数值属性:

{
  name: 'plain',
  title: '是否为朴素按钮',
  defaultValue: false,
  setters: 'BooleanSetter'
}

此配置在属性面板中创建一个切换开关,允许开发者轻松启用或禁用该功能。

基于选项的属性

对于具有有限有效值集的属性,使用 options 数组结合 SelectSetter

{
  name: 'type',
  title: '类型',
  defaultValue: 'default',
  setters: 'SelectSetter',
  options: ['default', 'primary', 'success', 'warning', 'danger', 'info']
}

options 数组可以包含简单的字符串或带有 label/value 对的对象,以提供更友好的显示:

{
  name: 'justify',
  defaultValue: 'start',
  title: 'flex 布局下的水平排列方式',
  options: [
    'start',
    'end',
    'center',
    'space-around',
    'space-between',
    'space-evenly'
  ],
  setters: 'SelectSetter'
}

多 Setter 属性

支持多种输入格式的复杂属性可以指定多个 setters:

{
  name: 'height',
  title: 'table 的高度',
  defaultValue: '',
  setters: ['InputSetter', 'NumberSetter']
}

此配置提供两种编辑模式:用于 CSS 值(例如 '400px')的文本输入和用于像素值的数字输入。设计器会自动处理这些模式之间的切换。

表达式和函数属性

需要 JavaScript 表达式或函数的属性使用专用的 setters:

{
  name: 'model',
  title: '表单数据对象',
  defaultValue: '',
  setters: 'ExpressionSetter'
},
{
  name: 'submitMethod',
  title: '表单提交处理方法',
  setters: 'FunctionSetter'
}

ExpressionSetter 提供带有语法高亮和验证的代码编辑器,而 FunctionSetter 包含函数定义的模板生成。

复杂数据结构属性

数组和对象使用提供丰富编辑体验的专用 setters:

{
  name: 'data',
  title: '表数据',
  defaultValue: '',
  setters: ['ArraySetter', 'JSONSetter']
},
{
  name: 'rules',
  title: '表单验证规则',
  defaultValue: '',
  setters: 'ExpressionSetter'
}

ArraySetter 提供类似表格的界面用于编辑数组,而 JSONSetter 提供结构化的 JSON 编辑器。

共享属性工具

为了在组件间保持一致性,VTJ 提供了辅助函数来生成通用属性配置。这些工具定义在 packages/materials/src/shared/props.ts 中:

export function size(name: string = "size"): MaterialProp {
  return {
    name,
    title: "尺寸",
    defaultValue: "default",
    setters: "SelectSetter",
    options: ["default", "large", "small"],
  };
}

export function type(name: string = "type"): MaterialProp {
  return {
    name,
    title: "类型",
    defaultValue: "default",
    setters: "SelectSetter",
    options: ["default", "primary", "success", "warning", "danger", "info"],
  };
}

export function effect(name: string = "effect"): MaterialProp {
  return {
    name,
    title: "主题",
    defaultValue: "light",
    setters: "SelectSetter",
    options: ["dark", "light"],
  };
}

这些辅助函数在组件定义中被广泛使用:

props: [
  size("size"),
  type("type"),
  // ... additional props
];

这种方法确保了所有共享通用属性的组件在属性命名、标签和选项上的一致性。

Snippet 系统

snippet 属性定义了将组件拖入设计器画布时创建的初始配置模板。这提供了合理的默认值,并通过减少初始配置来加速开发。

基础 Snippet

一个简单的 snippet 包含默认的 props 和子节点:

snippet: {
  name: 'ElButton',
  children: '按钮',
  props: {
    type: 'primary'
  }
}

这会在放置到画布上时创建一个带有文本“按钮”的主样式按钮。

复杂嵌套 Snippet

对于具有嵌套子节点的容器组件,snippets 定义完整的结构:

snippet: {
  name: 'ElButtonGroup',
  children: [
    {
      name: 'ElButton',
      children: 'Button1'
    },
    {
      name: 'ElButton',
      children: 'Button2'
    },
    {
      name: 'ElButton',
      children: 'Button3'
    }
  ]
}

这会创建一个包含三个预配置子按钮的按钮组,提供一个立即可用的起点。

数据丰富型 Snippet

对于需要初始数据的组件,snippets 可以包含完整的数据结构:

snippet: {
  props: {
    width: '100%',
    height: '400px',
    option: {
      xAxis: {
        type: 'category',
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      },
      yAxis: {
        type: 'value'
      },
      series: [
        {
          data: [150, 230, 224, 218, 135, 147, 260],
          type: 'line'
        }
      ]
    }
  }
}

这会创建一个带有示例数据的 ECharts 折线图,立即可视化组件的能力。

组件约束

Material schema 系统支持通过 parentIncludeschildIncludes 属性定义有效的父子关系。这些约束通过防止无效的组件放置来引导开发者。

父级约束

parentIncludes 属性限制组件可以放置的位置:

{
  name: 'ElCol',
  label: '布局列',
  categoryId: 'layout',
  package: 'element-plus',
  parentIncludes: ['ElRow'],
  // ... other configuration
}

此配置确保 ElCol 组件只能放置在 ElRow 组件内部,从而保持正确的布局结构。

子级约束

childIncludes 属性限制哪些组件可以放置在容器内部:

{
  name: 'ElButtonGroup',
  childIncludes: ['ElButton'],
  label: '按钮组',
  categoryId: 'base',
  // ... other configuration
}

这确保只有 ElButton 组件可以放置在按钮组内部,从而保持组件语义。

约束继承

默认情况下,组件接受任何子节点(省略 childIncludes)并且可以放置在任何位置(省略 parentIncludes)。显式约束会覆盖这些默认值:

  • childIncludes: false - 不允许子节点
  • childIncludes: ['ComponentA', 'ComponentB'] - 仅允许特定组件
  • parentIncludes: false - 不能放置在任何位置(很少使用)
  • parentIncludes: ['ParentA'] - 仅允许特定的父级

物料库配置

顶层的 Material 接口将所有组件描述聚合成一个连贯的库:

const material: Material = {
  name: "element-plus",
  version: version,
  label: "Element+",
  library: "ElementPlusMaterial",
  order: 2,
  categories,
  components: setPackageName(components, name),
};

分类

分类在设计器面板中组织组件:

const categories: MaterialCategory[] = [
  {
    id: "base",
    category: "基础组件",
  },
  {
    id: "layout",
    category: "排版布局",
  },
  {
    id: "form",
    category: "表单组件",
  },
  {
    id: "data",
    category: "数据展示",
  },
  {
    id: "nav",
    category: "导航",
  },
  {
    id: "other",
    category: "其他",
  },
];

每个组件通过其 categoryId 属性引用一个分类,从而实现分层组织。

组件聚合

组件被导入并聚合成一个数组:

const components: MaterialDescription[] = [
  ...button,
  ...container,
  ...layout,
  ...link,
  ...text,
  ...scrollbar,
  ...space,
  ...form,
  ...input,
  ...select,
  // ... more components
].flat();

.flat() 运算符处理导入的导出返回组件描述数组的情况。

包管理

setPackageName 工具确保所有组件具有一致的包元数据:

export function setPackageName(
  components: MaterialDescription[],
  name: string,
) {
  return components.map((n) => {
    return {
      ...n,
      package: name,
    } as MaterialDescription;
  });
}

该工具在物料库配置期间应用,将所有组件的 package 属性设置为与库名称匹配。

Setter 系统

Setters 是用于在设计器面板中编辑属性的 UI 组件。VTJ 提供了一套全面的内置 setters:

Setter使用场景输入类型
BooleanSetter布尔切换值切换开关
StringSetter简单字符串值文本输入框
NumberSetter数值数字输入框
SelectSetter预定义选项选择下拉菜单
InputSetter通用输入(接受字符串、数字)文本输入框
ArraySetter数组编辑表格界面
ObjectSetter对象编辑表单界面
JSONSetterJSON 数据结构代码编辑器
ExpressionSetterJavaScript 表达式代码编辑器
FunctionSetter函数定义带模板的代码编辑器
IconSetter图标选择图标选择器
ColorSetter颜色值颜色选择器
CssSetterCSS 属性CSS 编辑器

Setters 可以单独使用,也可以组合使用以提供灵活的编辑体验。对于具有多种有效输入格式的属性,请指定多个 setters:

{
  name: 'height',
  setters: ['InputSetter', 'NumberSetter']
}

Setter 组件位于 packages/designer/src/components/setters/ 中,可以使用自定义 setters 进行扩展以满足特殊的编辑需求。

高级模式

属性继承与省略

扩展现有组件定义的自定义组件可以选择性地继承属性:

export function omitPropItem(props: MaterialProp[] = [], names: string[] = []) {
  return props.filter((n) => !names.includes(n.name));
}

const desc: MaterialDescription = {
  name: "XForm",
  label: "表单",
  categoryId: "form",
  props: [
    // 自定义属性
    {
      name: "inlineColumns",
      title: "inline模式显示列数",
      setters: "NumberSetter",
    },
    // 继承的属性(省略 'model' 和 'inline')
    ...omitPropItem(elForm[0].props, ["model", "inline"]),
  ],
  // ... other configuration
};

这种模式实现了无重复的组件扩展,允许自定义组件基于现有定义构建,同时修改特定行为。

组件组合

对于聚合多个底层组件的复杂组件,定义显式关系:

{
  name: 'XField',
  label: '表单项',
  categoryId: 'form',
  // 结合了 ElFormItem、ElInput、验证逻辑等
}

这些组合的组件提供了特定于领域的抽象,同时保持了完整的低代码编辑能力。

跨库兼容性

Material schema 系统支持将来自不同 UI 库的组件映射到统一的描述:

{
  name: 'XButton',  // 统一名称
  alias: 'Button',  // 原始库名称
  package: 'ant-design-vue',
  // ... 统一属性
}

这使设计器能够使用一致的组件抽象,同时为特定的 UI 框架生成代码。

配置最佳实践

定义物料 schemas 时,始终在 snippets 中提供有意义的默认值。这减少了初始配置开销,并立即演示组件功能。对于复杂组件,请包含展示关键功能的真实示例数据,而不是通用的占位符值。

属性命名

使用清晰、描述性的属性名称,与底层组件 API 匹配:

// 好 - 匹配组件 API
{
  name: 'modelValue',
  title: '选中项绑定值',
  setters: ['NumberSetter', 'InputSetter', 'BooleanSetter']
}

// 避免 - 命名模糊
{
  name: 'value',
  title: '值',
  setters: 'StringSetter'
}

Setter 选择

为属性类型选择最具体的 setter:

  • 对于选项有限且已知的属性,使用 SelectSetter
  • 对于 JavaScript 表达式和复杂逻辑,使用 ExpressionSetter
  • 对于数据结构,使用 ArraySetterJSONSetter
  • 当多种输入格式有效时,组合 setters

文档链接

始终包含文档 URL 以供参考:

{
  doc: "https://element-plus.org/zh-CN/component/button.html";
}

这为开发者在设计器中工作时提供了权威文档。

分类组织

按功能而非库结构逻辑地对组件进行分组:

const categories: MaterialCategory[] = [
  { id: "base", category: "基础组件" },
  { id: "form", category: "表单组件" },
  { id: "data", category: "数据展示" },
];

无论组件源自哪个 UI 库,这都能提高可发现性。

与低代码引擎集成

Material schema 配置通过多种机制与 VTJ 低代码引擎集成:

设计器集成

设计器读取物料 schemas 以:

  • 使用分类的组件填充组件面板
  • 基于组件 props 生成属性面板
  • 强制执行父/子放置约束
  • 提供带有默认配置的拖拽 snippets

代码生成

渲染器使用物料 schemas 以:

  • 基于包信息生成导入语句
  • 使用正确的 prop 类型创建组件实例
  • 应用来自 snippet 配置的默认值
  • 将 setter 值映射到适当的数据结构

验证和类型安全

Schema 定义通过以下方式实现运行时验证:

  • 根据类型定义检查属性值的类型
  • 根据事件参数验证事件处理程序签名
  • 确保插槽使用与预期的接口匹配

扩展 Material Schemas

要创建自定义物料 schemas:

  1. 定义组件描述:按照接口规范创建 MaterialDescription 对象
  2. 选择 Setters:为每个属性选择适当的 setters,或在 packages/designer/src/components/setters/ 中创建自定义 setters
  3. 提供 Snippets:在 snippet 属性中包含有意义的默认配置
  4. 在库中组织:将相关组件分组到带有适当分类的物料库中
  5. 在引擎中注册:在应用程序配置中导入并注册物料库

有关创建自定义 setters 和控件,请参阅 Custom Setters and Property Editors。

有关创建全新的组件物料,请参阅 Creating Custom Material Components。

参考资料