物料描述概述
中后台前端体系中,存在大量的组件,程序员可以通过阅读文档,知悉组件的用法。可是搭建平台无法理解 README,而且很多时候,README 里并没有属性列表。这时,我们需要一份额外的描述,来告诉低代码搭建平台,组件接受哪些属性,又是该用怎样的方式来配置这些属性,于是,《中后台低代码组件描述协议》应运而生。协议主要包含三部分:基础信息、属性信息 props、能力配置/体验增强 configure。
物料配置,就是产出一份符合《中后台低代码组件描述协议》的 JSON Schema。如果需要补充属性描述信息,或需要定制体验增强部分(如修改 Setter、调整展示顺序等),就可以通过修改这份 Schema 来实现。目前有自动生成、手工配置这两种方式生成物料描述配置。
可视化生成物料描述
使用Parts造物平台:https://www.yuque.com/lce/xhk5hf/qa9pbx
自动生成物料描述
可以使用官方提供的 @alilc/lowcode-material-parser 解析本地组件,自动生成物料描述。把物料描述放到资产包定义中,就能让低代码引擎理解如何制作物料。详见上一个章节“物料扩展”。
下面以某个组件代码片段为例:
// /path/to/componentimport { PureComponent } from 'react';import PropTypes from 'prop-types';export default class FusionForm extends PureComponent {static displayName = 'FusionForm';static defaultProps = {name: '张三',age: 18,friends: ['李四','王五','赵六']}static propTypes = {/*** 这是用于描述姓名*/name: PropTypes.string.isRequired,/*** 这是用于描述年龄*/age: PropTypes.number,/*** 这是用于描述好友列表*/friends: PropTypes.array};render() {return <div>dumb</div>}}
引入 parse 工具自动解析
import parse from '@alilc/lowcode-material-parser';(async () => {const result = await parse({ entry: '/path/to/component' });console.log(JSON.stringify(result, null, 2));})();
因为一个组件可能输出多个子组件,所以解析结果是个数组。
[{"componentName": "FusionForm","title": "","docUrl": "","screenshot": "","devMode": "proCode","npm": {"package": "","version": "","exportName": "default","main": "","destructuring": false,"subName": ""},"props": [{"name": "name","propType": "string","description": "这是用于描述姓名","defaultValue": "张三"},{"name": "age","propType": "number","description": "这是用于描述年龄","defaultValue": 18},{"name": "friends","propType": "array","description": "这是用于描述好友列表","defaultValue": ["李四","王五","赵六"]}]}]
手工配置物料描述
如果自动生成的物料无法满足需求,我们就需要手动配置物料描述。本节将分场景描述物料配置的内容。
常见配置
组件的属性只有有限的值
增加一个 size 属性,只能从 ‘large’、’normal’、’small’ 这个候选值中选择。
以上面自动解析的物料为例,在此基础上手工加上 size 属性:
[{"componentName": "FusionForm","title": "","docUrl": "","screenshot": "","devMode": "proCode","npm": {"package": "","version": "","exportName": "default","main": "","destructuring": false,"subName": ""},"props": [{"name": "name","propType": "string","description": "这是用于描述姓名","defaultValue": "张三"},{"name": "age","propType": "number","description": "这是用于描述年龄","defaultValue": 18},{"name": "friends","propType": "array","description": "这是用于描述好友列表","defaultValue": ["李四","王五","赵六"]}],// 手工增加的 size 属性"configure": {"isExtend": true,"props": [{"title": "尺寸","name": "size","setter": {"componentName": 'RadioGroupSetter',"isRequired": true,"props": {"options": [{ "title": "大", "value": "large" },{ "title": "中", "value": "normal" },{ "title": "小", "value": "small" },]},}}]}}]
组件的属性既可以设置固定值,也可以绑定到变量
我们知道一种属性形式就需要一种 setter 来设置,如果想要将 value 属性允许输入字符串,那就需要设置为 StringSetter,如果允许绑定变量,就需要设置为 VariableSetter,具体设置器请参考预置 Setter 列表
那如果都想要呢?可以使用 MixedSetter 来实现
{...,configure: {isExtend: true,props: [{title: "输入框的值",name: "activeValue",setter: {componentName: 'MixedSetter',isRequired: true,props: {setters: ['StringSetter','NumberSetter','VariableSetter',],},}}]}}
设置后,就会出现 “切换设置器” 的操作项了

开启组件样式设置

{configure: {// ...,supports: {style: true,},// ...}}
设置组件的默认事件

{configure: {// ...,supports: {events: ['onPressEnter', 'onClear', 'onChange', 'onKeyDown', 'onFocus', 'onBlur'],},// ...}}
设置 prop 标题的 tip

{name: 'label',setter: 'StringSetter',title: {label: {type: 'i18n',zh_CN: '标签文本',en_US: 'Label',},tip: {type: 'i18n',zh_CN: '属性: label | 说明: 标签文本内容',en_US: 'prop: label | description: label content',},},}
配置 prop 对应 setter 在配置面板的展示方式
inline:
{configure: {props: [{description: '标签文本',display: 'inline'}]}}
block:
{configure: {props: [{description: '高级',display: 'block'}]}}
accordion

{configure: {props: [{description: '表单项配置',display: 'accordion'}]}}
entry


{configure: {props: [{description: '风格与样式',display: 'entry'}]}}
plain

{configure: {props: [{description: '返回上级',display: 'plain'}]}}
进阶配置
组件的 children 属性允许传入 ReactNode
例如有一个如下的 Tab 选项卡组件,每个 TabPane 的 children 都是一个组件
只需要增加 isContainer 配置即可
{...,configure: {...,component: {// 新增,设置组件为容器组件,可拖入组件isContainer: true,},}}
假设我们希望只允许拖拽 Table、Button 等内容放在 TabPane 里。配置白名单 childWhitelist 即可
{...,configure: {...,component: {isContainer: true,nestingRule: {// 允许拖入的组件白名单childWhitelist: ['Table', 'Button'],// 同理也可以设置该组件允许被拖入哪些父组件里parentWhitelist: ['Tab'],},},},}
组件的非 children 属性允许传入 ReactNode
这就需要使用 SlotSetter 开启插槽了,如下面示例,给 Tab 的 title 开启插槽,允许拖拽组件
{// ...,configure: {isExtend: true,props: [{title: "选项卡标题",name: "title",setter: {componentName: 'MixedSetter',props: {setters: ['StringSetter','SlotSetter','VariableSetter',],},}}]}}
屏蔽组件在设计器中的操作按钮
正常情况下,组件允许复制:
如果希望禁止组件的复制行为,我们可以这样做:

{configure: {component: {disableBehaviors: ['copy'],}}}
实现一个 BackwardSetter

{name: 'back',title: ' ',virtual: () => true,display: 'plain',setter: BackwardSetter,}// BackwardSetterimport { SettingTarget, DynamicSetter } from '@alilc/lowcode-types';const BackwardSetter: DynamicSetter = (target: SettingTarget) => {return {componentName: (<ButtononClick={() => {target.getNode().parent.select();}}><Icon type="arrow-left" /> 返回上级</Button>),};};
高级配置
不展现一个 prop 配置
- 始终隐藏当前 prop
{
// 始终隐藏当前 prop 配置
condition: () => false
}
- 根据其它 prop 的值展示/隐藏当前 prop
{
// direction 为 hoz 则展示当前 prop 配置
condition: (target) => {
return target.getProps().getPropValue('direction') === 'hoz';
}
}
props 联动
// 根据当前 prop 的值动态设置其它 prop 的值
{
name: 'labelAlign',
// ...
extraProps: {
setValue: (target, value) => {
if (value === 'inset') {
target.getProps().setPropValue('labelCol', null);
target.getProps().setPropValue('wrapperCol', null);
} else if (value === 'left') {
target.getProps().setPropValue('labelCol', { fixedSpan: 4 });
target.getProps().setPropValue('wrapperCol', null);
}
return target.getProps().setPropValue('labelAlign', value);
},
},
}
// 根据其它 prop 的值来设置当前 prop 的值
{
name: 'status',
// ...
extraProps: {
getValue: (target) => {
const isPreview = target.getProps().getPropValue('isPreview');
return isPreview ? 'readonly' : 'editable';
}
}
}
动态 setter 配置
可以通过 DynamicSetter 传入的 target 获取一些引擎暴露的数据,例如当前有哪些组件被加载到引擎中,将这个数据作为 SelectSetter 的选项,让用户选择:
{
setter: (target) => {
return {
componentName: 'SelectSetter',
props: {
options: target.designer.props.componentMetadatas.filter(
(item) => item.isFormItemComponent).map(
(item) => {
return {
title: item.title || item.componentName,
value: item.componentName,
};
}
),
),
}
};
}
}
