Skip to content

物料扩展

物料是 EasyEditor 页面搭建的基础构建单元。本指南将帮助你了解如何开发和集成自定义物料。

概述

物料是构建页面的基本原料,按照粒度可分为以下三种类型:

  • 组件(Component):最小的可复用单元,仅对外暴露配置项,用户无需了解其内部实现。

  • 区块(Block):符合低代码协议的一小段 schema,内部可以包含一个或多个组件。

  • 模板(Template):与区块类似,也是符合低代码协议的 schema,通常用于初始化一个页面。

在低代码编辑器中,物料需要经过一定的配置和处理,才能在平台上使用。这个过程涉及到配置文件的创建,称为资产包。资产包文件中定义了每个物料在低代码编辑器中的使用描述。

目录结构

一个完整的物料包含以下文件结构:

bash
my-component/
├── component.tsx    # 物料组件实现
├── configure.ts     # 物料配置(属性设置)
├── meta.ts          # 物料元数据
└── snippets.ts      # 物料预设

使用

组件实现 (component.tsx)

组件是物料的核心实现,需要遵循 React 组件规范:

tsx
import React, { type Ref, forwardRef } from 'react'

export interface ButtonProps {
  /**
   * 按钮文本内容
   */
  content: string
  /**
   * 按钮类型
   * @default 'default'
   */
  type?: 'primary' | 'default' | 'danger'
  /**
   * 是否禁用
   * @default false
   */
  disabled?: boolean
  /**
   * 点击事件处理函数
   */
  onClick?: () => void
  /**
   * 自定义类名
   */
  className?: string
  /**
   * 自定义样式
   */
  style?: React.CSSProperties
}

/**
 * 按钮组件
 */
const Button = forwardRef((props: ButtonProps, ref: Ref<HTMLButtonElement>) => {
  const {
    content,
    type = 'default',
    disabled = false,
    onClick,
    className = '',
    style = {}
  } = props

  // 生成按钮样式类
  const getButtonClass = () => {
    const baseClass = 'w-full h-full rounded-md transition-all duration-200'
    const typeClass = {
      default: 'bg-gray-100 hover:bg-gray-200 text-gray-800',
      primary: 'bg-blue-500 hover:bg-blue-600 text-white',
      danger: 'bg-red-500 hover:bg-red-600 text-white'
    }[type]
    const disabledClass = disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'

    return `${baseClass} ${typeClass} ${disabledClass} ${className}`
  }

  return (
    <button
      ref={ref}
      type="button"
      className={getButtonClass()}
      onClick={disabled ? undefined : onClick}
      disabled={disabled}
      style={style}
    >
      {content}
    </button>
  )
})

export default Button

属性配置 (configure.ts)

定义组件在设计器中的可配置属性:

ts
import type { Configure } from '@easy-editor/core'

const configure: Configure = {
  props: [
    {
      type: 'group',
      title: '基础',
      setter: 'GroupSetter',
      items: [
        {
          type: 'field',
          name: 'content',
          title: '按钮文本',
          setter: 'StringSetter',
        },
        {
          type: 'field',
          name: 'type',
          title: '按钮类型',
          setter: {
            componentName: 'SelectSetter',
            props: {
              options: [
                { label: '默认', value: 'default' },
                { label: '主要', value: 'primary' },
                { label: '危险', value: 'danger' }
              ]
            }
          },
        },
        {
          type: 'field',
          name: 'disabled',
          title: '是否禁用',
          setter: 'BooleanSetter',
        }
      ]
    },
    {
      type: 'group',
      title: '样式',
      setter: 'GroupSetter',
      items: [
        {
          type: 'field',
          name: 'className',
          title: '自定义类名',
          setter: 'StringSetter'
        },
        {
          type: 'field',
          name: 'style',
          title: '自定义样式',
          setter: 'StyleSetter'
        }
      ]
    },
    {
      type: 'group',
      title: '事件',
      setter: 'GroupSetter',
      items: [
        {
          type: 'field',
          name: 'onClick',
          title: '点击事件',
          setter: {
            componentName: 'FunctionSetter',
            props: {
              placeholder: '点击按钮时触发',
              defaultValue: `function() { console.log('按钮被点击'); }`
            }
          }
        }
      ]
    }
  ]
}

export default configure

元数据定义 (meta.ts)

描述组件的基本信息和分类:

ts
import type { ComponentMetadata } from '@easy-editor/core'
import configure from './configure'
import snippets from './snippets'

const meta: ComponentMetadata = {
  componentName: 'Button',        // 组件名称
  title: '按钮',                  // 显示标题
  category: '通用',               // 组件分类
  group: '基础组件',              // 组件分组
  icon: 'ButtonIcon',            // 组件图标
  description: '常用的操作按钮,支持多种类型和状态', // 组件描述
  configure,                     // 属性配置
  snippets,                      // 预设模板
  advanced: {
    callbacks: {                 // 组件回调
      onNodeAdd: (dragObject, currentNode) => {
        // 当组件被添加到画布时触发
        console.log('Button added:', currentNode.id)
        return true              // 返回 true 表示允许添加
      },
      onNodeRemove: (currentNode) => {
        // 当组件被从画布移除时触发
        console.log('Button removed:', currentNode.id)
        return true              // 返回 true 表示允许移除
      }
    },
    supports: {                  // 支持的功能
      style: true,               // 支持样式配置
      events: ['onClick'],       // 支持的事件列表
      loop: false                // 是否支持循环
    },
    component: {
      isContainer: false,        // 是否为容器组件
      nestingRule: {             // 嵌套规则
        childWhitelist: [],      // 子组件白名单
        parentWhitelist: ['Container', 'Form', 'Card'] // 父组件白名单
      }
    }
  }
}

export default meta

预设模板 (snippets.ts)

定义组件在物料面板中的预设用法:

ts
import type { Snippet } from '@easy-editor/core'

const snippets: Snippet[] = [
  {
    title: '默认按钮',
    screenshot: 'default.png', // 预览图路径
    schema: {
      componentName: 'Button',
      props: {
        content: '默认按钮',
        type: 'default'
      }
    }
  },
  {
    title: '主要按钮',
    screenshot: 'primary.png',
    schema: {
      componentName: 'Button',
      props: {
        content: '主要按钮',
        type: 'primary'
      }
    }
  },
  {
    title: '危险按钮',
    screenshot: 'danger.png',
    schema: {
      componentName: 'Button',
      props: {
        content: '危险按钮',
        type: 'danger'
      }
    }
  },
  {
    title: '禁用按钮',
    screenshot: 'disabled.png',
    schema: {
      componentName: 'Button',
      props: {
        content: '禁用按钮',
        disabled: true
      }
    }
  }
]

export default snippets

导出入口 (index.ts)

汇总导出组件和元数据:

ts
import Button from './src/component'
import meta from './src/meta'

export { Button, meta }
export default Button

注册物料

在引擎初始化时注册物料:

ts
import {
  init,
  materials,
  setters,
} from '@easy-editor/core'
import Button from './materials/button/component'
import buttonMeta from './materials/button/meta'
import Card from './materials/card/component'
import cardMeta from './materials/card/meta'

// 构建组件元数据映射
materials.buildComponentMetasMap([buttonMeta, cardMeta])

// 在渲染器中使用时,需要提供组件映射
export const components = {
  Button,
  Card
}

// 初始化引擎
await init({
  designMode: 'design',
})

在渲染器中使用

tsx
import React from 'react'
import { Renderer } from '@easy-editor/react-renderer-dashboard'
import Button from './materials/button/component'

// 组件映射表
const components = {
  Button
}

function Preview() {
  // 简单示例
  const simpleSchema = {
    componentName: 'Button',
    props: {
      content: '点击我',
      type: 'primary',
      onClick: () => console.log('按钮被点击')
    }
  }

  // 复杂示例 - 包含容器组件和子组件
  const complexSchema = {
    componentName: 'Card',
    props: {
      title: '卡片标题'
    },
    children: [
      {
        componentName: 'Button',
        props: {
          content: '卡片内按钮',
          type: 'primary'
        }
      }
    ]
  }

  return (
    <div className="preview-container">
      <h2>简单组件</h2>
      <Renderer
        components={components}
        schema={simpleSchema}
      />

      <h2 className="mt-4">复杂组件</h2>
      <Renderer
        components={components}
        schema={complexSchema}
      />
    </div>
  )
}

export default Preview

设计器的交互

物料组件与设计器的交互主要通过以下几种方式:

设计态与运行态切换

组件可以根据当前的模式调整行为:

tsx
import React, { forwardRef } from 'react'

export interface ChartProps {
  /**
   * 是否处于设计模式
   */
  __designMode?: boolean
  /**
   * 图表数据
   */
  data?: Array<any>
  // ...其他属性
}

const Chart = forwardRef<HTMLDivElement, ChartProps>((props, ref) => {
  const { __designMode, data = [] } = props

  // 在设计态下显示模拟数据
  const displayData = __designMode && (!data || data.length === 0)
    ? [
        { name: '样例数据A', value: 30 },
        { name: '样例数据B', value: 50 },
        { name: '样例数据C', value: 20 }
      ]
    : data

  return (
    <div ref={ref} className="chart-container">
      {__designMode && (
        <div className="design-indicator absolute top-0 right-0 bg-blue-500 text-white text-xs px-1">
          设计模式
        </div>
      )}
      {/* 图表实现 */}
      <div className="chart-content">
        {/* ... 渲染图表 ... */}
        {JSON.stringify(displayData)}
      </div>
    </div>
  )
})

组件回调机制

组件可以通过元数据定义回调,响应设计器中的各种事件:

ts
// 在元数据中定义回调
const meta: ComponentMetadata = {
  // ...其他配置
  advanced: {
    callbacks: {
      // 组件选中时
      onSelectHook: (currentNode) => {
        console.log('Component selected:', currentNode.id)
        return true // 返回 true 表示允许选中
      },

      // 组件属性变更前
      onNodeAdd: (addedNode, currentNode) => {
        console.log('Component added:', addedNode?.id)
        return true // 返回 true 表示允许添加
      },

      // 初始化时调用
      onNodeRemove: (removedNode, currentNode) => {
        console.log('Component removed:', removedNode?.id)
        return true // 返回 true 表示允许移除
      }
    }
  }
}

嵌套规则配置

通过元数据定义组件的嵌套行为:

ts
const meta: ComponentMetadata = {
  // ...其他配置
  advanced: {
    component: {
      // 是否为容器组件
      isContainer: true,

      // 嵌套规则
      nestingRule: {
        // 允许作为子组件的组件列表
        childWhitelist: ['Button', 'Text', 'Image'],

        // 允许作为父组件的组件列表
        parentWhitelist: ['Page', 'Section', 'Container']
      }
    }
  }
}

Released under the MIT License.