概述
编写一个自定义组件,核心需要为其配置“属性”与“事件”面板。
通过配置“属性”面板,我们可以控制组件的名称、类型、默认值、是否为必填项等属性。
安装
npm i -S @byted-apaas/designer-react@latestt
如果是通过 ae component create创建的组件,会自动完整安装,无需再单独安装。
使用
(一)基本使用
属性声明
import { IMeta } from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
name: 'content',
type: 'String',
title: '按钮文字',
description: '我是一个按钮',
defaultValue: '按钮',
tooltip: '按钮的tooltip内容',
required: true,
// disabled: true
}
];
export default meta;
250px|700px|reset

下面对以上 8 个基础属性进行说明
属性消费
对属性进行声明后,我们便可以在自定义组件内部使用 props :
import { Button } from '@universe-design/react';
export interface Props {
content: string;
}
export default function CustomButton(
props: Props,
): JSX.Element {
const { content } = props;
console.log(props); // 打印props,{ content: "按钮"}
return
<div>
<Button>{content}</Button>
</div>
}
此时修改按钮文字为“按钮new”,可以发现页面上也同步更新了:
250px|700px|reset

(二)高级特性
响应式联动
联动是个非常常见的需求,即需要属性 A 根据属性 B 做出一些响应,比如:控制显示隐藏、计算值等。此处提供了两种表达方式,分别是:插值表达和响应式函数表达。
- 函数表达
在响应式函数表达中,在 runner 中编写响应式逻辑,可以通过 scope.$values 获取到当前其他属性的值,当响应式逻辑中使用到的属性的值发生改变时,会自动执行runner函数进行重新计算。
比如我们新增一个属性 num1 ,由开关 num1Switch 控制其显隐。
import { IMeta } from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
name: 'num1Switch',
type: 'Boolean',
title: '开关-切换属性1',
defaultValue: false,
},
{
name: 'num1',
type: 'Number',
title: '属性1-由开关切换',
visible: {
type: 'Expression',
runner: scope => scope.$values.num1Switch
},
},
]
];
export default meta;
切换 num1Switch 的值,即可控制属性 num1 的显示和隐藏。
这里演示了对属性的 visible 字段应用联动,我们同样也可以对其他字段(类型中包含了 Expression 的字段)应用联动,包括:title、tooltip、description、required、visible、hidden、disabled、disabledDescription。
- 插值表达
插值表达是响应式函数表达的极简写法,当逻辑没有那么复杂,只用一个语句就可以描述时,可以直接用插值表达。在插值表达内,可以通过 $values 读取到整个属性配置面板中其他属性的值,等同于响应式函数表达的 scope.$values。
比如上面的函数表达,同样可以换成插值表达的写法:
import { IMeta } from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
name: 'num1Switch',
type: 'Boolean',
title: '开关-切换属性1',
defaultValue: false,
},
{
name: 'num1',
type: 'Number',
title: '属性1-由开关切换',
visible: '{{$values.num1Switch}}'
}
]
];
export default meta;
主动联动
响应式联动属于被动联动,除了响应式联动之外,平台还提供了主动联动方式,即由属性 A 主动更新属性 B 。
主动联动通过在setterProps中配置 onChange 实现,在onChange内可以通过 scope.$values.someProp = xxx 的方式去改变另一个属性的值。
将以上响应式联动改为主动联动:由 num1Switch 主动控制 num1 的值。
import { IMeta } from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
name: 'num1Switch',
type: 'Boolean',
title: '开关-联动属性1',
defaultValue: false,
setterProps: {
onChange: {
type: 'Expression',
runner: scope => {
// onChange 是函数属性,这里需要返回一个函数
return value => {
// num1Switch 为 true,将 num1设置为1, 否则为0
scope.$values.num1 = value ? 1: 0
}
}
},
}
},
{
name: 'num1',
type: 'Number',
title: '属性1-由开关联动',
}
]
];
export default meta;
显示 & 隐藏
在前面的《响应式联动》小节,我们实现了对属性的显隐控制。配置一个属性置是否要隐藏,平台也提供了两种方式。(两种方式都能实现视图上的显隐控制,但是在数据层面有显著的区别,开发者可以根据实际情况选用)
- visible:控制视图显隐 & 控制数据收集
- 当为 false 时,视图隐藏,组件 props 中不存在此字段
- 当为 true 时,视图显示,组件 props 中存在此字段
- hidden:仅控制视图显隐
- 当为 false 时,视图显示,组件 props 中存在此字段
- 当为 true 时,视图隐藏,组件 props 中存在此字段
帮助理解:
visible:true | false 类似于 display:visible | none
hidden:true | false类似于 visibility: hidden | visible
请勿同时使用 visible 和 hidden,在不同浏览器下可能会有不同表现。
来实践看看 visible 和 hidden 的区别:
import { IMeta } from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
type: 'Boolean',
name: 'switch1',
title: '开关1',
defaultValue: true
},
{
type: 'Number',
name: 'num1',
title: 'num1(visible控制)',
defaultValue: 1,
visible: '{{$values.switch1}}'
},
{
type: 'Boolean',
name: 'switch2',
title: '开关2',
defaultValue: true
},
{
type: 'Number',
name: 'num2',
title: 'num2(hidden控制)',
defaultValue: 2,
hidden: '{{!$values.switch2}}'
}
]
];
export default meta;
自定义校验
除通过配置required必填校验外,开发者也可通过 validator 属性自定义校验规则,定义如下:
/**
* 属性值校验方法,表单项值发生变化时自动触发该方法
* @param value 表单项的值
* @param formValues 所有属性的值
*/
validator?: (value: any, formValues: any) => boolean |string
import { IMeta } from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
name: 'num1',
type: 'Number',
title: '属性1',
validator: (value: number, formValues:any) => {
// return value > 10
return value > 10 ? '': '只支持大于10的数字'
}
}
]
];
export default meta;
- 当返回 boolean 时,比如return value > 10,平台会在值为true时,使用错误提示的默认文案 “该字段不是一个合法的字段”;
250px|700px|reset

- 当返回 string时,开发者可以自定义错误提示
250px|700px|reset

属性布局 decorator
以上用到的属性的布局方式都是左右结构,也可能出现内容展示空间不够的情况,如下图:
250px|700px|reset

这里我们可以更改其布局方式为 Block:
import { IMeta } from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
name: 'num1',
type: 'Number',
title: '属性1-由开关切换',
visible: '{{$values.num1Switch}}',
decorator: 'Block', // 设置布局方式为 Block
decoratorProps: {
boldTitle: true, // 标题加粗
divider: true // 分隔线
}
}
]
];
export default meta;
搭配布局方式,我们还可以设置 decoratorProps,目前支持「标题加粗」和「分隔线」。
250px|700px|reset

(三)输入控件 setter
系统setter
在属性配置中,平台会根据type字段的值,提供默认匹配的输入控件,比如 type = Number时,平台会默认提供如下控件NumberSetter:
250px|700px|reset

如果需要更丰富的控件,我们可以修改对应 setter 的配置 setterProps 来修改 setter 的默认配置。比如,以上使用到的 num1 是 Number 类型,那我们可以选择一个与 Number 类型匹配的 setter——NumberSetter,并配置相应的 setterProps。
setter 产出的结构要与属性的 type 匹配
import { IMeta } from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
name: 'num1',
type: 'Number',
title: '属性1-由开关切换',
visible: '{{$values.num1Switch}}',
decorator: 'Block', // 设置布局方式为 Block
decoratorProps: {
boldTitle: true, // 标题加粗
divider: true // 分隔线
},
setter: 'NumberSetter',
setterProps: {
placeholder: '请输入',
min: 1,
max: 100,
onlyNumber: true,
controls: false,
mode: 'stepper',
step: 0.1,
},
}
]
];
export default meta;
250px|700px|reset

自定义 setter
Setter 本质上就是一个普通的组件,该组件的 props 如下定义:
type SetterProps = {
value: any, // setter 的当前值,类型同定义的类型
onChange: (value: any) => void, // 变更当前值的事件
disabled: boolean, // 处于禁用态
error: boolean, // 处于错误态
}
我们可以实现一个自定义的 CustomInputSetter:
// customInputSetter.tsx
import { Input } from '@universe-design/react';
import { ISetterProps } from "@byted-apaas/designer-react";
type InputSetterProps = ISetterProps & {
placeholder?: string
}
export const CustomInputSetter: React.FC<InputSetterProps> = (props: InputSetterProps) => {
const {
value,
onChange,
error,
disabled,
placeholder = '请输入',
} = props;
return (
<Input
value={value}
disabled={disabled}
onChange={(e) => {
onChange(e.target.value)
}}
placeholder={placeholder}
error={error}
/>
)
}
CustomInputSetter.displayName = 'CustomInputSetter'
在属性中引用 CustomInputSetter :
import { CustomInputSetter } from './customInputSetter'
import { IMeta } from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
name: 'customSetter',
title: '自定义Setter',
type: 'String',
setter: CustomInputSetter,
}
]
];
export default meta;
250px|700px|reset

(四)属性分组
纯 UI 分组
纯 UI 分组搭配 type: 'Void'和children字段使用。此时属性仅在面板展示时分组展示,但 props 输出值不分组(依然是平铺形式)。
import { IMeta,} from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
type: 'Void',
title: '属性分组',
decorator: 'Block',
decoratorProps: {
divider: true,
boldTitle: true
},
children: [
{
name: 'num1Switch',
type: 'Boolean',
title: '开关-切换属性1',
defaultValue: false,
},
{
name: 'num1',
type: 'Number',
title: '属性1-由开关切换',
visible: '{{$values.num1Switch}}',
}
]
}
]
];
export default meta;
属性面板展示
250px|700px|reset

props 数据格式(平铺格式不变)
{
num1Switch: false,
num1: ''
// ...其他属性
}
Object 数据分组
Object 数组结构分组搭配 type: 'Object'和properties使用。此时属性面板展示和 props 数据结构均会分组。
import { IMeta,} from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
type: 'Object',
title: '属性分组',
name: 'group',
decorator: 'Block',
decoratorProps: {
divider: true,
boldTitle: true
},
properties: [
{
name: 'num1Switch',
type: 'Boolean',
title: '开关-切换属性1',
defaultValue: false,
},
{
name: 'num1',
type: 'Number',
title: '属性1-由开关切换',
visible: '{{$values.num1Switch}}',
}
]
},
]
];
export default meta;
属性面板展示(与 UI 分组一样)
250px|700px|reset

props 数据格式
{
// ...其他属性
group: {
num1Switch: false,
num1: ''
}
}
Array 数据分组
我们还可以对属性进一步分组,搭配使用 type: 'Array'、setter: 'ListSetter'和 items 使用。
import { IMeta,} from '@byted-apaas/designer-react';
const meta: IMeta = [
props: [
{
type: 'Array',
title: '数组分组',
name: 'arr',
setter: "ListSetter",
defaultValue: [
{
num1Switch: false,
num1: 1
},
{
num1Switch: true,
num1: 2
}
],
item: {
type: 'Object',
properties: [
{
name: 'num1Switch',
type: 'Boolean',
title: '开关-切换属性1',
defaultValue: false,
},
{
name: 'num1',
type: 'Number',
title: '属性1-由开关切换',
visible: '{{$record.num1Switch}}',
}
]
},
decoratorProps: {
divider: true
}
}
]
];
export default meta;
平台 UI 展示
数据格式
{
// ...其他属性
arr: [
{
num1Switch: false,
num1: 1
},
{
num1Switch: true,
num1: 2
}
]
}
(五) Event 和 SlotFunction 类型
平台支持的类型包括 String、Number、Boolean、Array 、Object、Void、Event 和 SlotFunction。除了 Event和SlotFunction类型,其他类型均在以上文档内容有所体现。
- 当 type 为SlotFunction,平台会认为该字段为插槽,详细使用查看插槽 slot ;
- 当 type 为Event,平台会认为该字段为事件,详细使用查看事件与动作 。
(六) 属性表单表达式作用域变量(表单模型)
- $form - 返回当前模型的顶层表单模型
- $values - 返回当前模型的顶层表单模型的值
- $lookup - 返回当前模型的父模型的记录值
- $records - 返回当前模型的父数组模型的记录值
- $record - 返回当前模型的记录值(数组)
- $record.$index - 返回当前模型的记录值的模型索引
- $record.$lookup - 返回当前模型的记录值的父模型的记录值
- 这个可以一直向上索引