基础组件转物料信息整理说明
背景说明
为了能够在低代码平台运行使用我们的基础组件,需要把基础组件转换成低代码平台可识别的组件,也就是物料。物料需要进行一定的配置和处理,这个过程中,需要一份配置文件,也就是资产包(就是一份json)。资产包文件中,针对每个物料定义了它们在低代码编辑器中的使用描述。
如何引入组件
我们需要在资产包里定义这个包的获取方式,如果不定义,就不会被低代码引擎动态加载并对应上组件实例。定义方式是 UMD 的包,低代码引擎会尝试在 window 上寻找对应 library 的实例;
定义低代码编辑器中加载的资源列表,包含公共库和组件(库) cdn 资源等;
字段 | 字段描述 | 字段类型 | 规范等级 | 备注 |
---|---|---|---|---|
packages[].id? | 资源唯一标识 | String | A | 资源唯一标识,如果为空,则以 package 为唯一标识 |
packages[].title? | 资源标题 | String | A | 资源标题 |
packages[].package | npm 包名 | String | A | 组件资源唯一标识 |
packages[].version | npm 包版本号 | String | A | 组件资源版本号 |
packages[].type | 资源包类型 | String | AA | 取值为: proCode(源码)、lowCode(低代码,默认为 proCode |
packages[].schema | 低代码组件 schema 内容 | object | AA | 取值为: proCode(源码)、lowCode(低代码) |
packages[].deps | 当前资源包的依赖资源的唯一标识列表 | Array | A | 唯一标识为 id 或者 package 对应的值 |
packages[].library | 作为全局变量引用时的名称,用来定义全局变量名 | String | A | 低代码引擎通过该字段获取组件实例 |
packages[].editUrls | 组件编辑态视图打包后的 CDN url 列表,包含 js 和 css | Array | A | 低代码引擎编辑器会加载这些 url |
packages[].urls | 组件渲染态视图打包后的 CDN url 列表,包含 js 和 css | Array | AA | 低代码引擎渲染模块会加载这些 url |
packages[].advancedEditUrls | 组件多个编辑态视图打包后的 CDN url 列表集合,包含 js 和 css | Object | AAA | 上层平台根据特定标识提取某个编辑态的资源,低代码引擎编辑器会加载这些资源,优先级高于 packages[].editUrls |
packages[].advancedUrls | 组件多个端的渲染态视图打包后的 CDN url 列表集合,包含 js 和 css | Object | AAA | 上层平台根据特定标识提取某个渲染态的资源, 低代码引擎渲染模块会加载这些资源,优先级高于 packages[].urls |
packages[].external | 当前资源在作为其他资源的依赖,在其他依赖打包时时是否被排除了(同 webpack 中 external 概念) | Boolean | AAA | 某些资源会被单独提取出来,是其他依赖的前置依赖,根据这个字段决定是否提前加载该资源 |
packages[].loadEnv | 指定当前资源加载的环境 | Array | AAA | 主要用于指定 external 资源加载的环境,取值为 design(设计态)、runtime(预览态)中的一个或多个 |
packages[].exportSourceId | 标识当前 package 内容是从哪个 package 导出来的 | String | AAA | 此时 urls 无效 |
packages[].exportSourceLibrary | 标识当前 package 是从 window 上的哪个属性导出来的 | String | AAA | exportSourceId 的优先级高于exportSourceLibrary ,此时 urls 无效 |
packages[].async | 标识当前 package 资源加载在 window.library 上的是否是一个异步对象 | Boolean | A | async 为 true 时,需要通过 await 才能拿到真正内容 |
packages[].exportMode | 标识当前 package 从其他 package 的导出方式 | String | A | 目前只支持 "functionCall", exportMode等于 "functionCall" 时,当前package 的内容以函数的方式从其他 package 中导出,具体导出接口如: (library: string, packageName: string, isRuntime?: boolean) => any |
示例:
"packages": [
{
"package": "element-ui",
"version": "2.5.12",
"urls": [
"https://unpkg.com/element-ui/lib/index.js",
"https://unpkg.com/element-ui/lib/theme-chalk/index.css"
],
"library": "ELEMENT"
},
{
"package": "vant",
"version": "2.12.53",
"urls": [
"https://unpkg.com/vant@2.12/lib/vant.min.js",
"https://unpkg.com/vant@2.12/lib/index.css"
],
"library": "vant"
}
],
如何定义组件
单个组件描述内容为 json 结构,主要包含以下三部分内容,分别为:
- 基础信息 (A): 描述组件的基础信息,通常包含包信息、组件名称、标题、描述等。
- 组件属性信息 (A): 描述组件属性信息,通常包含参数、说明、类型、默认值 4 项内容。
- 能力配置/体验增强: 推荐用于优化搭建产品编辑体验,定制编辑能力的配置信息。
整体结构概览:http://lowcode-engine.cn/doc?url=sde3wf
基础信息 ElRow示例:
{
"category": "基础组件",
"componentName": "ElRow",
"npm": {
"package": "element-ui",
"version": "2.30.6",
"exportName": "Row",
"destructuring": true
}
}
基础信息 字段说明
字段 | 字段描述 | 字段类型 | 允许空 |
---|---|---|---|
componentName | 组件名称 | String | 否 |
title | 组件中文名称 | String | 否 |
description | 组件描述 | String | 是 |
docUrl | 组件文档链接 | String | 否 |
screenshot | 组件快照 | String | 否 |
icon | 组件的小图标 | String (URL) | 是 |
tags | 组件标签 | String | 是 |
keywords | 组件关键词,用于搜索联想 | String | 是 |
devMode | 组件研发模式 | String (proCode,lowCode) | 是 |
npm | npm 源引入完整描述对象 | Object | 否 |
npm.package | 源码组件库名 | String | 否 |
npm.exportName | 源码组件名称 | String | 否 |
npm.subName | 子组件名 | String | 否 |
npm.destructuring | 解构 | Bool | 否 |
npm.main | 组件路径 | String | 否 |
npm.version | 源码组件版本号 | String | 否 |
snippets | 内容为组件不同状态下的低代码 schema (可以有多个), 用户从组件面板拖入组件到设计器时会向页面 schema 中插入 snippets中定义的组件低代码 schema | Object[] | 否 |
group | 用于描述当前组件位于组件面板的哪个 tab | String | 否 |
category | 用于描述组件位于组件面板同一 tab 的哪个区域 | String | 否 |
priority | 用于描述组件在同一 category 中的排序 | String | 否 |
组件属性信息 ElRow 示例:
"props": [
{
"name": "gutter",
"propType": "number"
},
{
"name": "type",
"propType": "string",
"defaultValue": "flex"
},
{
"name": "justify",
"propType": {
"type": "oneOf",
"value": ["start", "end", "center", "space-around", "space-between"]
},
"defaultValue": "start"
},
{
"name": "align",
"propType": {
"type": "oneOf",
"value": ["top", "middle", "bottom"]
}
},
{
"name": "tag",
"propType": "string",
"defaultValue": "div"
}
]
组件属性信息 props
描述组件属性信息,通常包含名称、类型、描述、默认值 4 项内容。
字段 | 字段描述 | 字段类型 | 允许空 |
---|---|---|---|
name | 属性名称 | String | 否 |
propType | 属性类型 | String/Object | 否 |
description | 属性描述 | String | 是 |
defaultValue | 属性默认值 | Any | 是 |
经验:描述可改为用 title: {label:'',tip:''}
存在基本类型和复合类型,描述如下:Prop — Vue.js
基本类型
propType 值 | 类型描述 | PropType 类型 |
---|---|---|
'array' | 数组类型 | Array |
'bool' | 布尔类型 | Boolean |
'func' | 函数类型 | Function |
'number' | 数字类型 | Number |
'object' | 对象类型 | Object |
'string' | 字符串类型 | String |
'node' | 节点类型 | 定义具名插槽 |
'any' | 任意值类型 | 会生成多个setter切换,如:node、string、object、array类型 |
复合类型
能力配置/体验增强 configure
configure ElRow 示例:
"configure": {
"supports": {
"events": ["click", "change"],
"loop": true,
"condition": true,
"style": true
},
"component": {
"isContainer": true,
"isLayout": true
},
"props": [
{
"type": "group",
"title": "布局样式",
"display": "block",
"items": [
{
"type": "field",
"title": "类型",
"name": "type",
"defaultValue": "flex",
"condition": false
},
{
"type": "field",
"title": "栅格间隔",
"name": "gutter",
"setter": "NumberSetter"
},
{
"type": "field",
"title": "水平排列",
"name": "justify",
"setter": {
"componentName": "SelectSetter",
"defaultValue": "start",
"props": {
"options": [
{ "title": "start", "value": "start" },
{ "title": "end", "value": "end" },
{ "title": "center", "value": "center" },
{ "title": "space-around", "value": "space-around" },
{ "title": "space-between", "value": "space-between" }
]
}
}
},
{
"type": "field",
"title": "垂直排列",
"name": "align",
"setter": {
"componentName": "SelectSetter",
"defaultValue": "start",
"props": {
"options": [
{ "title": "top", "value": "top" },
{ "title": "middle", "value": "middle" },
{ "title": "bottom", "value": "bottom" }
]
}
}
}
]
}
]
}
基础字段
字段 | 字段描述 | 字段类型 | 备注 |
---|---|---|---|
props (A) | 属性面板配置 | Array | 用于属性面板能力描述 |
component(A) | 组件能力配置 | Object | 与组件相关的能力、约束、行为等描述,有些信息可从组件视图实例上直接获取 |
supports (AA) | 通用扩展配置能力支持性 | Object | 用于通用扩展面板能力描述 |
advanced (AAA) | 高级特性配置 | Object | 用户可以在这些配置通过引擎上下文控制组件在设计器中的表现,例如自动初始化组件的子组件、截获组件的操作事件进行个性化处理等 |
属性面板配置 props
根据属性值类型 propType,确定对应控件类型 (setter) ,详见https://lowcode-engine.cn/site/docs/guide/appendix/setters
configure下的props还可以是对象的形式,与组件属性信息props并存
优先使用组件下的props,当不能满足用户体验时,需用configure来扩展
坑点:设置ArraySetter类型时,不要定义type:filed, 不然会导致面板数据改变后视图不同步。isRequired 属性配置快捷编辑项。
extraProps示例标签demo:
const getValue = function (target, fieldValue) {
const map = target.getNode().children.map((child) => {
const primaryKey = child.getPropValue('primaryKey')
? String(child.getPropValue('primaryKey'))
: child.id
return {
primaryKey: primaryKey,
title: child.getPropValue('title'),
}
})
return map
}
const setValue = function (target, value) {
const node = target.getNode()
const map = {}
if (!Array.isArray(value)) {
value = []
}
value.forEach((item) => {
const tabitem = Object.assign({}, item)
map[item.primaryKey] = tabitem
})
node.children.mergeChildren(
(child) => {
const primaryKey = String(child.getPropValue('primaryKey'))
if (Object.hasOwnProperty.call(map, primaryKey)) {
child.setPropValue('title', map[primaryKey].title)
delete map[primaryKey]
return false
}
return true
},
() => {
const items = []
for (const primaryKey in map) {
if (Object.hasOwnProperty.call(map, primaryKey)) {
items.push({
componentName: 'van-tab',
props: map[primaryKey],
})
}
}
return items
},
(child1, child2) => {
const a = value.findIndex(
(item) => String(item.primaryKey) === String(child1.getPropValue('primaryKey'))
)
const b = value.findIndex(
(item) => String(item.primaryKey) === String(child2.getPropValue('primaryKey'))
)
return a - b
}
)
}
API说明:https://lowcode-engine.cn/site/docs/api/project#mergechildren
通用扩展面板支持性配置 supports
样式配置面板能力描述,描述是否支持行业样式编辑、是否支持类名设置等。
{
"configure": {
// 支持的事件枚举
"supports": {
// 支持事件列表
"events": ["click", "change"],
// 支持循环设置
"loop": true,
// 支持条件设置
"condition": true,
// 支持样式设置
"style": true
}
}
}
组件能力配置 component
特别说明: 当默认插槽和其他具名插槽是互斥的情况下,需要显示的定义default, 且isContainer 属性需设为false
描述举例:
{
configure: {
component: {
// 组件是否是容器组件(可拖入,类似于插槽)
isContainer: true,
isModal: false,
descriptor: 'title',
nestingRule: {
childWhitelist: ['SelectOption'],
parentWhitelist: ['Select', 'Table'],
},
rootSelector: '.next-dialog',
disableBehaviors: ['copy', 'remove'],
actions: {
name: 'copy', // string;
content: '+', // string | ReactNode | ActionContentObject;
items: [], // ComponentAction[];
condition: 'always', // boolean | ((currentNode: any) => boolean) | 'always';
important: true, // boolean;
},
},
},
}
高级功能配置 advanced
字段 | 用途 | 类型 | 备注 |
---|---|---|---|
initialChildren | 组件拖入“设计器”时根据此配置自动生成 children 节点 schema | NodeData[]/Function NodeData[] | ((target: SettingTarget) => NodeData[]); |
getResizingHandlers | 用于配置设计器中组件 resize 操作工具的样式和内容 | Function | (currentNode: any) => Array<{ type: 'N' |
callbacks | 配置 callbacks 可捕获引擎抛出的一些事件,例如 onNodeAdd、onResize 等 | Callback | - |
callbacks.onNodeAdd | 在容器中拖入组件时触发的事件回调 | Function | (e: MouseEvent, currentNode: any) => any |
callbacks.onNodeRemove | 在容器中删除组件时触发的事件回调 | Function | (e: MouseEvent, currentNode: any) => any |
callbacks.onResize | 调整容器尺寸时触发的事件回调,常常与 getResizingHandlers搭配使用 | Function | 详见 Types 定义 |
callbacks.onResizeStart | 调整容器尺寸开始时触发的事件回调,常常与 getResizingHandlers搭配使用 | Function | 详见 Types 定义 |
callbacks.onResizeEnd | 调整容器尺寸结束时触发的事件回调,常常与 getResizingHandlers搭配使用 | Function | 详见 Types 定义 |
callbacks.onSubtreeModified | 容器节点结构树发生变化时触发的回调 | Function | (currentNode: any, options: any) => void; |
callbacks.onMouseDownHook | 鼠标按下操作回调 | Function | (e: MouseEvent, currentNode: any) => any; |
callbacks.onClickHook | 鼠标单击操作回调 | Function | (e: MouseEvent, currentNode: any) => any; |
callbacks.onDblClickHook | 鼠标双击操作回调 | Function | (e: MouseEvent, currentNode: any) => any; |
callbacks.onMoveHook | 节点被拖动回调 | Function | (currentNode: any) => boolean; |
callbacks.onHoverHook | 节点被 hover 回调 | Function | (currentNode: any) => boolean; |
callbacks.onChildMoveHook | 容器节点的子节点被拖动回调 | Function | (childNode: any, currentNode: any) => boolean; |
描述举例:
{
configure: {
advanced: {
callbacks: {
onNodeAdd: (dragment, currentNode) => {
const layoutPNode = currentNode.document.createNode({
componentName: "WsCard",
title: "卡片组件",
props: { },
children: [dragment.schema],
});
setTimeout(() => {
if (!currentNode.getChildren().has(dragment)) {
return;
}
const newNode = currentNode.document.createNode(
Object.assign(layoutPNode.schema)
);
currentNode.insertBefore(newNode, dragment, false);
dragment.remove(false);
newNode.getChildren().get(0).select();
}, 1);
}
},
getResizingHandlers: () => {
return [ 'E' ];
},
initials: [
{
name: 'linkType',
initial: () => 'link'
},
]
},
}
}
开发规范
目录结构
引入的第三方库vant、element和wshoto的组件库,需在public下的lib下以库名建立文件夹,文件名为 index 。示例:
物料资产
一个UI 组件库对应一个json文件,方便管理。示例:
组件定义规范
宗旨: 以低代码平台用户体验为优先级,而不是物料书写方便
- description 字段描述定义清晰,需用中文,描述文字过长时需定义成 title 字段
"title": {
"label": "自定义搜索逻辑",
"tip": "第一个参数是节点node,第二个参数是搜索关键词keyword,通过返回布尔值表示是否命中"
},
- defaultValue 默认值字段 按需在 componentsList 下的 props 上定义默认值
"componentList": [
{
"title": "WSHOTO_PC 组件",
"children": [
{
"componentName": "WeShineExportButton",
"library": "WSHOTO_PC_UI",
"snippets": [
{
"screenshot": "https://helios-allpublic-1257616148.cos.ap-shanghai.myqcloud.com/img/input.png",
"title": "导出列表",
"schema": {
"componentName": "WeShineExportButton",
"props": {
"moduleId": "demo",
"moduleFileName": "导出列表文件",
"btnText": "导出列表",
"getParams": {
"type": "JSFunction",
"value": "function getParams() {\n return {};\n }"
},
"createFn": {
"type": "JSFunction",
"value": "function createFn(data) {\n return { url: '/v3/base-auditlog/api/audit/log/download/jobId', method: 'post', data};\n }"
},
"getProgressFn": {
"type": "JSFunction",
"value": "function getProgressFn(options) {\n return { url: '/v3/base-auditlog/api/audit/log/download/progress', method: 'get', params: {...options}}; \n }"
},
"getUrlFn": {
"type": "JSFunction",
"value": "function getProgressFn(options) {\n return { url: '/v3/base-auditlog/api/audit/log/download/file', method: 'get', params: {...options}}; \n }"
}
}
}
}
]
}
]
}
]
- propType 需严格定义类型
- 可枚举的字符串类型需定义成 oneOf 枚举值类型,而不能定义成 string
{
"name": "justify",
"propType": {
"type": "oneOf",
"value": ["start", "end", "center", "space-around", "space-between"]
}
}
- 可同时存在多个类型时,需用 oneOfType 的复合类型定义出来
{
"name": "v-model",
"description": "绑定的值",
"propType": {
"type": "oneOfType",
"value": [
"string",
"bool",
"number"
]
}
},
- 对象类型需定义成 shape 或 exact 复合类型
"propType": {
"type": "shape",
"value": [
{
"name": "span",
"description": "span",
"propType": "number"
},
{
"name": "offset",
"description": "offset",
"propType": "number"
}
]
},
- 当props的值为颜色值时,需使用 ColorSetter
{
"name": "inactive-color",
"title": "关闭时的背景色",
"setter": "ColorSetter"
}
完整demo示例
Element row和col组件
{
"version": "0.0.1",
"components": [
{
"category": "基础组件",
"componentName": "ElRow",
"npm": {
"package": "element-ui",
"version": "2.30.6",
"exportName": "Row",
"destructuring": true
},
"props": [
{
"name": "gutter",
"propType": "number"
},
{
"name": "type",
"propType": "string",
"defaultValue": "flex"
},
{
"name": "justify",
"propType": {
"type": "oneOf",
"value": ["start", "end", "center", "space-around", "space-between"]
},
"defaultValue": "start"
},
{
"name": "align",
"propType": {
"type": "oneOf",
"value": ["top", "middle", "bottom"]
}
},
{
"name": "tag",
"propType": "string",
"defaultValue": "div"
}
],
"configure": {
"supports": {
"loop": true,
"condition": true,
"style": true
},
"component": {
"isContainer": true,
"isLayout": true
},
"props": [
{
"type": "group",
"title": "布局样式",
"display": "block",
"items": [
{
"type": "field",
"title": "类型",
"name": "type",
"defaultValue": "flex",
"condition": false
},
{
"type": "field",
"title": "栅格间隔",
"name": "gutter",
"setter": "NumberSetter"
},
{
"type": "field",
"title": "水平排列",
"name": "justify",
"setter": {
"componentName": "SelectSetter",
"defaultValue": "start",
"props": {
"options": [
{
"title": "start",
"value": "start"
},
{
"title": "end",
"value": "end"
},
{
"title": "center",
"value": "center"
},
{
"title": "space-around",
"value": "space-around"
},
{
"title": "space-between",
"value": "space-between"
}
]
}
}
},
{
"type": "field",
"title": "垂直排列",
"name": "align",
"setter": {
"componentName": "SelectSetter",
"defaultValue": "start",
"props": {
"options": [
{
"title": "top",
"value": "top"
},
{
"title": "middle",
"value": "middle"
},
{
"title": "bottom",
"value": "bottom"
}
]
}
}
}
]
}
]
}
},
{
"category": "基础组件",
"componentName": "ElCol",
"npm": {
"package": "element-ui",
"version": "2.30.6",
"exportName": "Col",
"destructuring": true
},
"configure": {
"supports": {
"loop": true,
"condition": true,
"style": true
},
"component": {
"isContainer": true,
"isLayout": true,
"nestingRule": {
"descendantBlacklist": ["ElCol"],
"parentWhitelist": ["ElRow"]
}
},
"props": [
{
"type": "group",
"title": "布局样式",
"display": "block",
"items": [
{
"type": "field",
"title": "占据列宽",
"name": "span",
"setter": "NumberSetter"
},
{
"type": "field",
"title": "左侧间隔",
"name": "offset",
"setter": "NumberSetter"
},
{
"type": "field",
"title": "向右移动",
"name": "push",
"setter": "NumberSetter"
},
{
"type": "field",
"title": "向左移动",
"name": "pull",
"setter": "NumberSetter"
}
]
}
]
}
}
],
"componentList": [
{
"title": "布局组件",
"children": [
{
"componentName": "ElRow",
"library": "element-ui",
"snippets": [
{
"screenshot": "https://helios-allpublic-1257616148.cos.ap-shanghai.myqcloud.com/img/card.png",
"title": "栅格-行",
"schema": {
"componentName": "ElRow",
"children": []
}
}
]
}
]
},
{
"title": "布局组件",
"children": [
{
"componentName": "ElCol",
"library": "element-ui",
"snippets": [
{
"screenshot": "https://helios-allpublic-1257616148.cos.ap-shanghai.myqcloud.com/img/card.png",
"title": "栅格-列",
"schema": {
"componentName": "ElCol",
"children": []
}
}
]
}
]
}
]
}
Vant grid和gridItem组件
{
"version": "0.0.1",
"components": [
{
"category": "基础组件",
"componentName": "van-grid",
"npm": {
"package": "vant",
"version": "2.12.53",
"exportName": "Grid",
"destructuring": true
},
"props": [
{
"name": "column-num",
"title": "列数",
"propType": "number",
"defaultValue": "4"
},
{
"name": "gutter",
"title": "格子间隔",
"propType": "number",
"defaultValue": "0"
},
{
"name": "direction",
"title": "排列方向",
"propType": {
"type": "oneOf",
"value": ["vertical", "horizontal"]
},
"defaultValue": "vertical"
},
{
"name": "border",
"propType": "bool",
"title": "显示边框",
"defaultValue": true
},
{
"name": "center",
"propType": "bool",
"title": "居中显示",
"defaultValue": true
},
{
"name": "square",
"propType": "bool",
"title": "显示正方形",
"defaultValue": false
},
{
"name": "clickable",
"propType": "bool",
"title": "开启点击反馈",
"defaultValue": false
}
],
"configure": {
"supports": {
"events": ["click"],
"loop": true,
"condition": true,
"style": true
},
"component": {
"isContainer": true
}
}
},
{
"category": "基础组件",
"componentName": "van-grid-item",
"npm": {
"package": "vant",
"version": "2.12.53",
"exportName": "GridItem",
"destructuring": true
},
"props": [
{
"name": "default",
"title": "默认插槽",
"propType": "node"
},
{
"name": "text",
"description": "文本内容",
"propType": {
"type": "oneOfType",
"value": ["string", "node"]
},
"defaultValue": ""
},
{
"name": "icon",
"propType": {
"type": "oneOfType",
"value": ["string", "node"]
},
"defaultValue": ""
},
{
"name": "dot",
"propType": "bool",
"defaultValue": false
},
{
"name": "badge",
"propType": "string",
"defaultValue": ""
},
{
"name": "url",
"propType": "string",
"defaultValue": ""
},
{
"name": "to",
"propType": "string",
"defaultValue": ""
},
{
"name": "replace",
"propType": "bool",
"defaultValue": false
}
],
"configure": {
"supports": {
"events": ["onClick"],
"loop": true,
"condition": true,
"style": true
},
"component": {
"isContainer": false
}
}
}
],
"componentList": [
{
"title": "布局组件",
"children": [
{
"componentName": "van-grid",
"library": "vant",
"snippets": [
{
"screenshot": "https://helios-allpublic-1257616148.cos.ap-shanghai.myqcloud.com/img/card.png",
"title": "宫格-容器",
"schema": {
"componentName": "van-grid",
"children": []
}
}
]
}
]
},
{
"title": "布局组件",
"children": [
{
"componentName": "van-grid-item",
"library": "vant",
"snippets": [
{
"screenshot": "https://helios-allpublic-1257616148.cos.ap-shanghai.myqcloud.com/img/card.png",
"title": "宫格-子项",
"schema": {
"componentName": "van-grid-item",
"children": []
}
}
]
}
]
}
]
}