物料描述详解

物料描述概述

中后台前端体系中,存在大量的组件,程序员可以通过阅读文档,知悉组件的用法。可是搭建平台无法理解 README,而且很多时候,README 里并没有属性列表。这时,我们需要一份额外的描述,来告诉低代码搭建平台,组件接受哪些属性,又是该用怎样的方式来配置这些属性,于是,《中后台低代码组件描述协议》应运而生。协议主要包含三部分:基础信息、属性信息 props、能力配置/体验增强 configure。

物料配置,就是产出一份符合《中后台低代码组件描述协议》的 JSON Schema。如果需要补充属性描述信息,或需要定制体验增强部分(如修改 Setter、调整展示顺序等),就可以通过修改这份 Schema 来实现。目前有自动生成、手工配置这两种方式生成物料描述配置。

自动生成物料描述

可以使用官方提供的 @alilc/lowcode-material-parser 解析本地组件,自动生成物料描述。把物料描述放到资产包定义中,就能让低代码引擎理解如何制作物料。详见上一个章节“物料扩展”。

下面以某个组件代码片段为例:

  1. // /path/to/component
  2. import { PureComponent } from 'react';
  3. import PropTypes from 'prop-types';
  4. export default class FusionForm extends PureComponent {
  5. static displayName = 'FusionForm';
  6. static defaultProps = {
  7. name: '张三',
  8. age: 18,
  9. friends: ['李四','王五','赵六']
  10. }
  11. static propTypes = {
  12. /**
  13. * 这是用于描述姓名
  14. */
  15. name: PropTypes.string.isRequired,
  16. /**
  17. * 这是用于描述年龄
  18. */
  19. age: PropTypes.number,
  20. /**
  21. * 这是用于描述好友列表
  22. */
  23. friends: PropTypes.array
  24. };
  25. render() {
  26. return <div>dumb</div>
  27. }
  28. }

引入 parse 工具自动解析

  1. import parse from '@ali/lowcode-material-parser';
  2. (async () => {
  3. const result = await parse({ entry: '/path/to/component' });
  4. console.log(JSON.stringify(result, null, 2));
  5. })();

因为一个组件可能输出多个子组件,所以解析结果是个数组。

  1. [
  2. {
  3. "componentName": "FusionForm",
  4. "title": "",
  5. "docUrl": "",
  6. "screenshot": "",
  7. "devMode": "proCode",
  8. "npm": {
  9. "package": "",
  10. "version": "",
  11. "exportName": "default",
  12. "main": "",
  13. "destructuring": false,
  14. "subName": ""
  15. },
  16. "props": [
  17. {
  18. "name": "name",
  19. "propType": "string",
  20. "description": "这是用于描述姓名",
  21. "defaultValue": "张三"
  22. },
  23. {
  24. "name": "age",
  25. "propType": "number",
  26. "description": "这是用于描述年龄",
  27. "defaultValue": 18
  28. },
  29. {
  30. "name": "friends",
  31. "propType": "array",
  32. "description": "这是用于描述好友列表",
  33. "defaultValue": [
  34. "李四",
  35. "王五",
  36. "赵六"
  37. ]
  38. }
  39. ]
  40. }
  41. ]

手工配置物料描述

如果自动生成的物料无法满足需求,我们就需要手动配置物料描述。本节将分场景描述物料配置的内容。

常见配置

组件的属性只有有限的值

增加一个 size 属性,只能从 ‘large’、’normal’、’small’ 这个候选值中选择。

以上面自动解析的物料为例,在此基础上手工加上 size 属性:

  1. [
  2. {
  3. "componentName": "FusionForm",
  4. "title": "",
  5. "docUrl": "",
  6. "screenshot": "",
  7. "devMode": "proCode",
  8. "npm": {
  9. "package": "",
  10. "version": "",
  11. "exportName": "default",
  12. "main": "",
  13. "destructuring": false,
  14. "subName": ""
  15. },
  16. "props": [
  17. {
  18. "name": "name",
  19. "propType": "string",
  20. "description": "这是用于描述姓名",
  21. "defaultValue": "张三"
  22. },
  23. {
  24. "name": "age",
  25. "propType": "number",
  26. "description": "这是用于描述年龄",
  27. "defaultValue": 18
  28. },
  29. {
  30. "name": "friends",
  31. "propType": "array",
  32. "description": "这是用于描述好友列表",
  33. "defaultValue": [
  34. "李四",
  35. "王五",
  36. "赵六"
  37. ]
  38. }
  39. ],
  40. // 手工增加的 size 属性
  41. "configure": {
  42. "isExtend": true,
  43. "props": [
  44. {
  45. "title": "尺寸",
  46. "name": "size",
  47. "setter": {
  48. "componentName": 'RadioGroupSetter',
  49. "isRequired": true,
  50. "props": {
  51. "options": [
  52. { "title": "大", "value": "large" },
  53. { "title": "中", "value": "normal" },
  54. { "title": "小", "value": "small" },
  55. ]
  56. },
  57. }
  58. }
  59. ]
  60. }
  61. }
  62. ]

组件的属性既可以设置固定值,也可以绑定到变量

我们知道一种属性形式就需要一种 setter 来设置,如果想要将 value 属性允许输入字符串,那就需要设置为 StringSetter,如果允许绑定变量,就需要设置为 VariableSetter,具体设置器请参考预置 Setter 列表

那如果都想要呢?可以使用 MixedSetter 来实现

  1. {
  2. ...,
  3. configure: {
  4. isExtend: true,
  5. props: [
  6. {
  7. title: "输入框的值",
  8. name: "activeValue",
  9. setter: {
  10. componentName: 'MixedSetter',
  11. isRequired: true,
  12. props: {
  13. setters: [
  14. 'StringSetter',
  15. 'NumberSetter',
  16. 'VariableSetter',
  17. ],
  18. },
  19. }
  20. }
  21. ]
  22. }
  23. }

设置后,就会出现 “切换设置器” 的操作项了

物料描述详解 - 图1 物料描述详解 - 图2

开启组件样式设置

物料描述详解 - 图3

  1. {
  2. configure: {
  3. // ...,
  4. supports: {
  5. style: true,
  6. },
  7. // ...
  8. }
  9. }

设置组件的默认事件

物料描述详解 - 图4

  1. {
  2. configure: {
  3. // ...,
  4. supports: {
  5. events: ['onPressEnter', 'onClear', 'onChange', 'onKeyDown', 'onFocus', 'onBlur'],
  6. },
  7. // ...
  8. }
  9. }

设置 prop 标题的 tip

物料描述详解 - 图5

  1. {
  2. name: 'label',
  3. setter: 'StringSetter',
  4. title: {
  5. label: {
  6. type: 'i18n',
  7. zh_CN: '标签文本',
  8. en_US: 'Label',
  9. },
  10. tip: {
  11. type: 'i18n',
  12. zh_CN: '属性: label | 说明: 标签文本内容',
  13. en_US: 'prop: label | description: label content',
  14. },
  15. },
  16. }

配置 prop 对应 setter 在配置面板的展示方式

inline:物料描述详解 - 图6

  1. {
  2. configure: {
  3. props: [{
  4. description: '标签文本',
  5. display: 'inline'
  6. }]
  7. }
  8. }

block:

物料描述详解 - 图7

  1. {
  2. configure: {
  3. props: [{
  4. description: '高级',
  5. display: 'block'
  6. }]
  7. }
  8. }

accordion

物料描述详解 - 图8

  1. {
  2. configure: {
  3. props: [{
  4. description: '表单项配置',
  5. display: 'accordion'
  6. }]
  7. }
  8. }

entry

物料描述详解 - 图9

物料描述详解 - 图10

  1. {
  2. configure: {
  3. props: [{
  4. description: '风格与样式',
  5. display: 'entry'
  6. }]
  7. }
  8. }

plain

物料描述详解 - 图11

  1. {
  2. configure: {
  3. props: [{
  4. description: '返回上级',
  5. display: 'plain'
  6. }]
  7. }
  8. }

进阶配置

组件的 children 属性允许传入 ReactNode

例如有一个如下的 Tab 选项卡组件,每个 TabPane 的 children 都是一个组件

物料描述详解 - 图12

只需要增加 isContainer 配置即可

  1. {
  2. ...,
  3. configure: {
  4. ...,
  5. component: {
  6. // 新增,设置组件为容器组件,可拖入组件
  7. isContainer: true,
  8. },
  9. }
  10. }

假设我们希望只允许拖拽 Table、Button 等内容放在 TabPane 里。配置白名单 childWhitelist 即可

  1. {
  2. ...,
  3. configure: {
  4. ...,
  5. component: {
  6. isContainer: true,
  7. nestingRule: {
  8. // 允许拖入的组件白名单
  9. childWhitelist: ['Table', 'Button'],
  10. // 同理也可以设置该组件允许被拖入哪些父组件里
  11. parentWhitelist: ['Tab'],
  12. },
  13. },
  14. },
  15. }

组件的非 children 属性允许传入 ReactNode

这就需要使用 SlotSetter 开启插槽了,如下面示例,给 Tab 的 title 开启插槽,允许拖拽组件

物料描述详解 - 图13

  1. {
  2. // ...,
  3. configure: {
  4. isExtend: true,
  5. props: [
  6. {
  7. title: "选项卡标题",
  8. name: "title",
  9. setter: {
  10. componentName: 'MixedSetter',
  11. props: {
  12. setters: [
  13. 'StringSetter',
  14. 'SlotSetter',
  15. 'VariableSetter',
  16. ],
  17. },
  18. }
  19. }
  20. ]
  21. }
  22. }

屏蔽组件在设计器中的操作按钮

正常情况下,组件允许复制:

物料描述详解 - 图14

如果希望禁止组件的复制行为,我们可以这样做:

物料描述详解 - 图15

  1. {
  2. configure: {
  3. component: {
  4. disableBehaviors: ['copy'],
  5. }
  6. }
  7. }

实现一个 BackwardSetter

物料描述详解 - 图16

  1. {
  2. name: 'back',
  3. title: ' ',
  4. virtual: () => true,
  5. display: 'plain',
  6. setter: BackwardSetter,
  7. }
  8. // BackwardSetter
  9. import { SettingTarget, DynamicSetter } from '@ali/lowcode-types';
  10. const BackwardSetter: DynamicSetter = (target: SettingTarget) => {
  11. return {
  12. componentName: (
  13. <Button
  14. onClick={() => {
  15. target.getNode().parent.select();
  16. }}
  17. >
  18. <Icon type="arrow-left" /> 返回上级
  19. </Button>
  20. ),
  21. };
  22. };

高级配置

不展现一个 prop 配置

  • 始终隐藏当前 prop
  1. {
  2. // 始终隐藏当前 prop 配置
  3. condition: () => false
  4. }
  • 根据其它 prop 的值展示/隐藏当前 prop
  1. {
  2. // direction 为 hoz 则展示当前 prop 配置
  3. condition: (target) => {
  4. return target.getProps().getPropValue('direction') === 'hoz';
  5. }
  6. }

props 联动

  1. // 根据当前 prop 的值动态设置其它 prop 的值
  2. {
  3. name: 'labelAlign',
  4. // ...
  5. extraProps: {
  6. setValue: (target, value) => {
  7. if (value === 'inset') {
  8. target.getProps().setPropValue('labelCol', null);
  9. target.getProps().setPropValue('wrapperCol', null);
  10. } else if (value === 'left') {
  11. target.getProps().setPropValue('labelCol', { fixedSpan: 4 });
  12. target.getProps().setPropValue('wrapperCol', null);
  13. }
  14. return target.getProps().setPropValue('labelAlign', value);
  15. },
  16. },
  17. }
  18. // 根据其它 prop 的值来设置当前 prop 的值
  19. {
  20. name: 'status',
  21. // ...
  22. extraProps: {
  23. getValue: (target) => {
  24. const isPreview = target.getProps().getPropValue('isPreview');
  25. return isPreview ? 'readonly' : 'editable';
  26. }
  27. }
  28. }

动态 setter 配置

可以通过 DynamicSetter 传入的 target 获取一些引擎暴露的数据,例如当前有哪些组件被加载到引擎中,将这个数据作为 SelectSetter 的选项,让用户选择:

  1. {
  2. setter: (target) => {
  3. return {
  4. componentName: 'SelectSetter',
  5. props: {
  6. options: target.designer.props.componentMetadatas.filter(
  7. (item) => item.isFormItemComponent).map(
  8. (item) => {
  9. return {
  10. title: item.title || item.componentName,
  11. value: item.componentName,
  12. };
  13. }
  14. ),
  15. ),
  16. }
  17. };
  18. }
  19. }