Переглянути джерело

feat(notice): notice template

Lind 4 роки тому
батько
коміт
0eee645203

+ 1 - 0
package.json

@@ -75,6 +75,7 @@
     "@umijs/route-utils": "^1.0.36",
     "ahooks": "^2.10.9",
     "antd": "^4.17.0-alpha.9",
+    "braft-editor": "^2.3.9",
     "classnames": "^2.2.6",
     "dexie": "^3.0.3",
     "isomorphic-form-data": "^2.0.0",

+ 10 - 1
src/components/BaseCrud/save/index.tsx

@@ -32,6 +32,7 @@ import type { ModalProps } from 'antd/lib/modal/Modal';
 import FUpload from '@/components/Upload';
 import FMonacoEditor from '@/components/FMonacoEditor';
 import type { Form as Form1 } from '@formily/core';
+import FBraftEditor from '@/components/FBraftEditor';
 
 interface Props<T> {
   schema: ISchema;
@@ -86,6 +87,7 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
       ArrayItems,
       Space,
       Radio,
+      FBraftEditor,
     },
     scope: {
       icon(name: any) {
@@ -96,7 +98,14 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
 
   const save = async () => {
     const values: T = await (customForm || form).submit();
+    // 特殊处理通知模版
+    if (service?.getUri().includes('/notifier/template')) {
+      (values as T & { template: Record<string, any> | string }).template = JSON.stringify(
+        values.template,
+      );
+    }
     await service.update(values);
+
     message.success(
       intl.formatMessage({
         id: 'pages.data.option.success',
@@ -120,7 +129,7 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
     >
       <Spin spinning={modelConfig?.loading || false}>
         <PreviewText.Placeholder value="-">
-          <Form form={customForm || form} labelCol={5} wrapperCol={16}>
+          <Form form={customForm || form} labelCol={4} wrapperCol={18}>
             <SchemaField schema={schema} {...schemaConfig} />
           </Form>
         </PreviewText.Placeholder>

+ 6 - 0
src/components/FBraftEditor/index.tsx

@@ -0,0 +1,6 @@
+import { connect, mapProps } from '@formily/react';
+import BraftEditor from 'braft-editor';
+import 'braft-editor/dist/index.css';
+
+const FBraftEditor = connect(BraftEditor, mapProps());
+export default FBraftEditor;

+ 509 - 7
src/pages/notice/Template/index.tsx

@@ -1,13 +1,17 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import BaseService from '@/utils/BaseService';
 import { useRef } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import BaseCrud from '@/components/BaseCrud';
 import { ArrowDownOutlined, BugOutlined, EditOutlined, MinusOutlined } from '@ant-design/icons';
 import { Tooltip } from 'antd';
 import { useIntl } from '@@/plugin-locale/localeExports';
+import type { ISchema } from '@formily/json-schema';
+import Service from '@/pages/notice/Template/service';
+import { useAsyncDataSource } from '@/utils/util';
+import type { Field } from '@formily/core';
+import { onFieldValueChange } from '@formily/core';
 
-export const service = new BaseService<TemplateItem>('notifier/template');
+export const service = new Service('notifier/template');
 const Template = () => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
@@ -48,7 +52,7 @@ const Template = () => {
       align: 'center',
       width: 200,
       render: (text, record) => [
-        <a onClick={() => console.log(record)}>
+        <a key="edit" onClick={() => console.log(record)}>
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.data.option.edit',
@@ -58,7 +62,7 @@ const Template = () => {
             <EditOutlined />
           </Tooltip>
         </a>,
-        <a>
+        <a key="delete">
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.data.option.remove',
@@ -68,7 +72,7 @@ const Template = () => {
             <MinusOutlined />
           </Tooltip>
         </a>,
-        <a>
+        <a key="download">
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.data.option.download',
@@ -78,7 +82,7 @@ const Template = () => {
             <ArrowDownOutlined />
           </Tooltip>
         </a>,
-        <a>
+        <a key="debug">
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.notice.option.debug',
@@ -91,8 +95,501 @@ const Template = () => {
       ],
     },
   ];
+  const providerRef = useRef<NetworkType[]>([]);
 
-  const schema = {};
+  const getTypes = async () =>
+    service.getTypes().then((resp) => {
+      providerRef.current = resp.result;
+      return resp.result.map((item: NetworkType) => ({
+        label: item.name,
+        value: item.id,
+      }));
+    });
+
+  const formEvent = () => {
+    onFieldValueChange('type', async (field, f) => {
+      const type = field.value;
+      if (!type) return;
+      f.setFieldState('provider', (state) => {
+        state.value = undefined;
+        state.dataSource = providerRef.current
+          .find((item) => type === item.id)
+          ?.providerInfos.map((i) => ({ label: i.name, value: i.id }));
+      });
+    });
+  };
+
+  const handleNetwork = (field: Field) => {
+    const provider = field.query('...provider').get('value');
+    const defaultMessage = {
+      MQTT_CLIENT:
+        'qos1 /device/${#deviceId}\n' +
+        '\n' +
+        '${T(com.alibaba.fastjson.JSON).toJSONString(#this)}',
+      HTTP_CLIENT:
+        'POST http://[host]:[port]/api\n' +
+        'Content-Type: application/json\n' +
+        '\n' +
+        '${T(com.alibaba.fastjson.JSON).toJSONString(#this)}',
+    };
+    field.value = defaultMessage[provider];
+  };
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      name: {
+        type: 'string',
+        title: '名称',
+        required: true,
+        'x-component': 'Input',
+        'x-decorator': 'FormItem',
+      },
+      grid: {
+        type: 'void',
+        'x-component': 'FormGrid',
+        'x-decorator': 'FormItem',
+        properties: {
+          type: {
+            type: 'string',
+            title: '通知类型',
+            'x-component': 'Select',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              labelCol: 8,
+              wrapperCol: 12,
+            },
+            'x-reactions': ['{{useAsyncDataSource(getTypes)}}'],
+          },
+          provider: {
+            type: 'string',
+            title: '服务商',
+            'x-component': 'Select',
+            'x-decorator-props': {
+              labelCol: 4,
+              wrapperCol: 16,
+            },
+            'x-decorator': 'FormItem',
+          },
+        },
+      },
+      template: {
+        type: 'object',
+        properties: {
+          voice: {
+            type: 'void',
+            properties: {
+              ttsCode: {
+                type: 'string',
+                title: '模版ID',
+                'x-component': 'Input',
+                'x-decorator': 'FormItem',
+              },
+              calledShowNumbers: {
+                type: 'string',
+                title: '被叫显号',
+                'x-component': 'Input',
+                'x-decorator': 'FormItem',
+              },
+              CalledNumber: {
+                type: 'string',
+                title: '被叫号码',
+                'x-component': 'Input',
+                'x-decorator': 'FormItem',
+              },
+              PlayTimes: {
+                type: 'string',
+                title: '播放次数',
+                'x-component': 'Input',
+                'x-decorator': 'FormItem',
+              },
+            },
+            'x-visible': false,
+            'x-reactions': {
+              dependencies: ['...type'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="voice"}}',
+                },
+              },
+            },
+          },
+          sms: {
+            type: 'void',
+            properties: {
+              test: {
+                type: 'void',
+                properties: {
+                  text: {
+                    type: 'string',
+                    title: '应用ID',
+                    'x-component': 'Input.TextArea',
+                    'x-decorator': 'FormItem',
+                  },
+                  sendTo: {
+                    type: 'string',
+                    title: '收件人',
+                    'x-component': 'Input.TextArea',
+                    'x-decorator': 'FormItem',
+                    'x-component-props': {
+                      placeholder: '多个收件人以 , 分割',
+                    },
+                  },
+                },
+                'x-visible': false,
+                'x-reactions': {
+                  dependencies: ['...provider'],
+                  fulfill: {
+                    state: {
+                      visible: '{{$deps[0]==="test"}}',
+                    },
+                  },
+                },
+              },
+              aliyunSms: {
+                type: 'void',
+                properties: {
+                  code: {
+                    type: 'string',
+                    title: '模版编码',
+                    'x-component': 'Input.TextArea',
+                    'x-decorator': 'FormItem',
+                    required: true,
+                    'x-component-props': {
+                      placeholder: '阿里云短信模版编码',
+                    },
+                  },
+                  signName: {
+                    type: 'string',
+                    title: '签名',
+                    'x-component': 'Input.TextArea',
+                    'x-decorator': 'FormItem',
+                    required: true,
+                    'x-component-props': {
+                      placeholder: '阿里云短信模版签名',
+                    },
+                  },
+                  phoneNumber: {
+                    type: 'string',
+                    title: '收件人',
+                    'x-component': 'Input.TextArea',
+                    'x-decorator': 'FormItem',
+                    'x-component-props': {
+                      placeholder: '短信接收者,暂只支持单个联系人',
+                    },
+                  },
+                },
+                'x-visible': false,
+                'x-reactions': {
+                  dependencies: ['...provider'],
+                  fulfill: {
+                    state: {
+                      visible: '{{$deps[0]==="aliyunSms"}}',
+                    },
+                  },
+                },
+              },
+            },
+            'x-visible': false,
+            'x-reactions': {
+              dependencies: ['...type'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="sms"}}',
+                },
+              },
+            },
+          },
+          email: {
+            type: 'void',
+            properties: {
+              subject: {
+                type: 'string',
+                title: '标题',
+                'x-component': 'Input',
+                'x-decorator': 'FormItem',
+              },
+              sendTo: {
+                type: 'string',
+                title: '收件人',
+                'x-component': 'Input.TextArea',
+                'x-decorator': 'FormItem',
+                'x-component-props': {
+                  placeholder: '多个收件人以  ,  分隔',
+                },
+              },
+              attachments: {
+                type: 'string',
+                title: '附件',
+                'x-component': 'FUpload',
+                'x-decorator': 'FormItem',
+                'x-component-props': {
+                  type: 'file',
+                },
+              },
+              emailEditor: {
+                type: 'string',
+                title: '正文',
+                'x-component': 'FBraftEditor',
+                'x-decorator': 'FormItem',
+                'x-component-props': {
+                  style: {
+                    height: '300px',
+                  },
+                  contentStyle: {
+                    height: '200px',
+                    // overflowY: 'auto',
+                  },
+                },
+              },
+            },
+            'x-visible': false,
+            'x-reactions': {
+              dependencies: ['...type'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="email"}}',
+                },
+              },
+            },
+          },
+          weixin: {
+            type: 'void',
+            properties: {
+              agentId: {
+                type: 'string',
+                title: '应用ID',
+                'x-component': 'Input',
+                'x-decorator': 'FormItem',
+              },
+              toUser: {
+                type: 'string',
+                title: '收信人ID',
+                'x-component': 'Input',
+                'x-decorator': 'FormItem',
+              },
+              toParty: {
+                type: 'string',
+                title: '收信部门ID',
+                'x-component': 'Input',
+                'x-decorator': 'FormItem',
+              },
+              toTag: {
+                type: 'string',
+                title: '按标签推送',
+                'x-component': 'Input',
+                'x-decorator': 'FormItem',
+              },
+              message: {
+                type: 'string',
+                title: '内容',
+                'x-component': 'Input.TextArea',
+                'x-decorator': 'FormItem',
+              },
+            },
+            'x-visible': false,
+            'x-reactions': {
+              dependencies: ['...type'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="weixin"}}',
+                },
+              },
+            },
+          },
+          dingTalk: {
+            type: 'void',
+            properties: {
+              dingTalkRobotWebHook: {
+                type: 'void',
+                properties: {
+                  messageType: {
+                    title: '消息类型',
+                    type: 'string',
+                    'x-component': 'Select',
+                    'x-decorator': 'FormItem',
+                    enum: ['text', 'markdown', 'link'],
+                  },
+                  text: {
+                    type: 'object',
+                    properties: {
+                      content: {
+                        title: '通知内容',
+                        type: 'string',
+                        'x-decorator': 'FormItem',
+                        'x-component': 'Input.TextArea',
+                      },
+                    },
+                    'x-visible': false,
+                    'x-reactions': {
+                      dependencies: ['.messageType'],
+                      fulfill: {
+                        state: {
+                          visible: '{{$deps[0]==="text"}}',
+                        },
+                      },
+                    },
+                  },
+                  markdown: {
+                    type: 'object',
+                    properties: {
+                      title: {
+                        title: '标题',
+                        type: 'string',
+                        'x-decorator': 'FormItem',
+                        'x-component': 'Input',
+                      },
+                      text: {
+                        title: '内容',
+                        type: 'string',
+                        'x-decorator': 'FormItem',
+                        'x-component': 'Input.TextArea',
+                      },
+                    },
+                    'x-visible': false,
+                    'x-reactions': {
+                      dependencies: ['.messageType'],
+                      fulfill: {
+                        state: {
+                          visible: '{{$deps[0]==="markdown"}}',
+                        },
+                      },
+                    },
+                  },
+                  link: {
+                    type: 'object',
+                    properties: {
+                      title: {
+                        title: '标题',
+                        type: 'string',
+                        'x-decorator': 'FormItem',
+                        'x-component': 'Input',
+                      },
+                      text: {
+                        title: '内容',
+                        type: 'string',
+                        'x-decorator': 'FormItem',
+                        'x-component': 'Input.TextArea',
+                      },
+                      picUrl: {
+                        title: '图片连接',
+                        'x-decorator': 'FormItem',
+                        'x-component': 'FUpload',
+                      },
+                      messageUrl: {
+                        title: '内容连接',
+                        'x-decorator': 'FormItem',
+                        'x-component': 'Input.TextArea',
+                      },
+                    },
+                    'x-visible': false,
+                    'x-reactions': {
+                      dependencies: ['.messageType'],
+                      fulfill: {
+                        state: {
+                          visible: '{{$deps[0]==="link"}}',
+                        },
+                      },
+                    },
+                  },
+                },
+                'x-visible': false,
+                'x-reactions': {
+                  dependencies: ['...provider'],
+                  fulfill: {
+                    state: {
+                      visible: '{{$deps[0]==="dingTalkRobotWebHook"}}',
+                    },
+                  },
+                },
+              },
+              dingTalkMessage: {
+                type: 'void',
+                properties: {
+                  agentId: {
+                    type: 'string',
+                    title: '应用ID',
+                    'x-component': 'Input',
+                    'x-decorator': 'FormItem',
+                  },
+                  userIdList: {
+                    type: 'string',
+                    title: '收信人ID',
+                    'x-component': 'Input',
+                    'x-decorator': 'FormItem',
+                  },
+                  departmentIdList: {
+                    type: 'string',
+                    title: '收信部门ID',
+                    'x-component': 'Input',
+                    'x-decorator': 'FormItem',
+                  },
+                  toAllUser: {
+                    type: 'string',
+                    title: '全部用户',
+                    'x-component': 'Select',
+                    'x-decorator': 'FormItem',
+                    enum: [
+                      { label: '是', value: true },
+                      { label: '否', value: false },
+                    ],
+                  },
+                  message: {
+                    type: 'string',
+                    title: '内容',
+                    'x-component': 'Input.TextArea',
+                    'x-decorator': 'FormItem',
+                  },
+                },
+                'x-visible': false,
+                'x-reactions': {
+                  dependencies: ['...provider'],
+                  fulfill: {
+                    state: {
+                      visible: '{{$deps[0]==="dingTalkMessage"}}',
+                    },
+                  },
+                },
+              },
+            },
+            'x-visible': false,
+            'x-reactions': {
+              dependencies: ['...type'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="dingTalk"}}',
+                },
+              },
+            },
+          },
+          network: {
+            type: 'void',
+            properties: {
+              text: {
+                type: 'string',
+                title: '消息',
+                'x-component': 'Input.TextArea',
+                'x-decorator': 'FormItem',
+                'x-component-props': {
+                  rows: 5,
+                },
+                'x-reactions': '{{handleNetwork}}',
+              },
+            },
+            'x-visible': false,
+            'x-reactions': {
+              dependencies: ['...type'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="network"}}',
+                },
+              },
+            },
+          },
+        },
+      },
+    },
+  };
   return (
     <PageContainer>
       <BaseCrud
@@ -102,7 +599,12 @@ const Template = () => {
           id: 'pages.notice.template',
           defaultMessage: '通知模版',
         })}
+        modelConfig={{
+          width: '50vw',
+        }}
+        formEffect={formEvent}
         schema={schema}
+        schemaConfig={{ scope: { useAsyncDataSource, getTypes, handleNetwork } }}
         actionRef={actionRef}
       />
     </PageContainer>

+ 17 - 0
src/pages/notice/Template/service.ts

@@ -0,0 +1,17 @@
+import BaseService from '@/utils/BaseService';
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+
+class Service extends BaseService<TemplateItem> {
+  public getTypes = () =>
+    request(`/${SystemConst.API_BASE}/notifier/config/types`, {
+      method: 'GET',
+    });
+
+  public getMetadata = (type: string, provider: string) =>
+    request(`${this.uri}/${type}/${provider}/metadata`, {
+      method: 'GET',
+    });
+}
+
+export default Service;

+ 320 - 0
src/pages/notice/Template/test.ts

@@ -0,0 +1,320 @@
+// 组件映射
+const ComponentMap = {
+  string: 'Input', // ? password/select/
+  boolean: 'Checkbox', // ?? 怎么区分Checkbox/Radio/
+  number: 'InputNumber',
+  enum: 'Select',
+  object: 'Editable.Popover',
+  password: 'Password',
+};
+
+// 测试InputSchema
+const sourceSchema =
+  '\n' +
+  '{\n' +
+  '  "type": "object",\n' +
+  '  "properties": {\n' +
+  '  "checkboxField": {\n' +
+  '    "type": "boolean"\n' +
+  '  },\n' +
+  '  "selectField": {\n' +
+  '    "type": "string",\n' +
+  '      "enum": ["A", "B", "C"]\n' +
+  '  },\n' +
+  '  "defaultValueField": {\n' +
+  '    "type": "string",\n' +
+  '      "default": "defaultValue"\n' +
+  '  },\n' +
+  '  "numberInputField": {\n' +
+  '    "type": "number"\n' +
+  '  },\n' +
+  '  "readonlyField": {\n' +
+  '    "const": "readonly"\n' +
+  '  },\n' +
+  '  "hiddenField": {\n' +
+  '    "const": null\n' +
+  '  },\n' +
+  '  "sensitiveField": {\n' +
+  '    "type": "string",\n' +
+  '      "sensitive": true\n' +
+  '  },\n' +
+  '  "recursiveField": {\n' +
+  '    "type": "object",\n' +
+  '      "properties": {\n' +
+  '      "nestedField": {\n' +
+  '        "type": "string"\n' +
+  '      },\n' +
+  '      "test": {\n' +
+  '        "type": "string"\n' +
+  '      }\n' +
+  '    }\n' +
+  '  }\n' +
+  '}\n' +
+  '}';
+
+function convertProperties(properties: Record<string, any>) {
+  const temp = {};
+  Object.keys(properties).forEach((i) => {
+    //default string
+    let componentType = properties[i].type || 'string';
+    // Special Handle Enum
+    if (componentType === 'object') {
+      temp[i] = {
+        type: componentType,
+        title: i,
+        'x-decorator': 'FormItem',
+        'x-component': ComponentMap[i],
+        'x-component-props': {
+          title: i,
+        },
+        properties: convertProperties(properties[i].properties),
+      };
+    } else {
+      // json type is no enum filed,so containing enum fields is render Select;
+      if (properties[i].enum) {
+        componentType = 'enum';
+      } else if (properties[i].sensitive) {
+        componentType = 'password';
+      }
+      temp[i] = {
+        type: properties[i].type || 'string',
+        title: i,
+        'x-decorator': 'FormItem',
+        'x-component': ComponentMap[componentType],
+      };
+
+      if (componentType === 'enum') {
+        temp[i].enum = properties[i].enum;
+      }
+
+      if (properties[i].default) {
+        temp[i].default = properties[i].default;
+      }
+
+      if (properties[i].const === null) {
+        temp[i]['x-hidden'] = true;
+      }
+
+      if (properties[i].const === 'readonly') {
+        temp[i]['x-read-only'] = true;
+      }
+    }
+  });
+  return temp;
+}
+
+function convert() {
+  const source = JSON.parse(sourceSchema);
+  const formilyJSON: any = {};
+  if (source.type !== 'object' && !source.properties) {
+    return;
+  }
+  formilyJSON.type = 'object';
+  const properties = source.properties;
+  formilyJSON.properties = convertProperties(properties);
+}
+
+convert();
+
+// Formily Standard Schema
+const schema = {
+  type: 'object',
+  properties: {
+    layout: {
+      type: 'void',
+      'x-component': 'FormLayout',
+      'x-component-props': {
+        labelCol: 4,
+        wrapperCol: 20,
+      },
+      properties: {
+        confirm: {
+          type: 'boolean',
+          title: 'confirm',
+          'x-decorator': 'FormItem',
+          'x-component': 'Checkbox',
+        },
+        checkbox: {
+          type: 'array',
+          title: 'checkbox',
+          'x-decorator': 'FormItem',
+          'x-component': 'Checkbox.Group',
+          enum: [
+            { label: 'OptionA', value: 'A' },
+            { label: 'OptionB', value: 'B' },
+            { label: 'OptionC', value: 'C' },
+            { label: 'OptionD', value: 'D' },
+          ],
+        },
+        radio: {
+          type: 'boolean',
+          title: 'radio',
+          enum: [
+            { label: 'OptionA', value: 'A' },
+            { label: 'OptionB', value: 'B' },
+            { label: 'OptionC', value: 'C' },
+          ],
+          'x-decorator': 'FormItem',
+          'x-component': 'Radio.Group',
+        },
+        select: {
+          type: 'string',
+          title: 'select',
+          required: true,
+          'x-decorator': 'FormItem',
+          'x-component': 'Select',
+          enum: [
+            { label: 'OptionA', value: 'A' },
+            { label: 'OptionB', value: 'B' },
+          ],
+        },
+        input: {
+          type: 'string',
+          title: 'input',
+          required: true,
+          'x-decorator': 'FormItem',
+          'x-decorator-props': {
+            tooltip: '123',
+          },
+          'x-component': 'Input',
+        },
+        defaultInput: {
+          type: 'string',
+          title: 'default',
+          required: true,
+          default: 'default',
+          'x-decorator': 'FormItem',
+          'x-component': 'Input',
+        },
+        number: {
+          type: 'number',
+          title: 'number',
+          'x-decorator': 'FormItem',
+          'x-component': 'InputNumber',
+        },
+        readOnly: {
+          type: 'string',
+          title: 'readOnly',
+          required: true,
+          default: 'default',
+          'x-decorator': 'FormItem',
+          'x-component': 'Input',
+          'x-read-only': true,
+        },
+        hidden: {
+          type: 'string',
+          title: 'hidden',
+          required: true,
+          default: 'default',
+          'x-decorator': 'FormItem',
+          'x-component': 'Input',
+          'x-hidden': 'true,',
+        },
+        sensitive: {
+          type: 'string',
+          title: 'password',
+          'x-decorator': 'FormItem',
+          'x-component': 'Password',
+        },
+        config: {
+          type: 'object',
+          title: 'config',
+          'x-component': 'Editable.Popover',
+          'x-component-props': {
+            title: 'detail',
+          },
+          'x-decorator': 'FormItem',
+          properties: {
+            layout: {
+              type: 'void',
+              'x-component': 'FormLayout',
+              'x-component-props': {
+                labelCol: 10,
+                wrapperCol: 14,
+              },
+              properties: {
+                id: {
+                  type: 'string',
+                  title: 'ID',
+                  required: true,
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Input',
+                },
+                name: {
+                  type: 'Name',
+                  title: 'input',
+                  required: true,
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Input',
+                },
+                describe: {
+                  type: 'string',
+                  title: 'Describe',
+                  required: true,
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Input',
+                },
+              },
+            },
+          },
+        },
+        recursive: {
+          type: 'array',
+          'x-component': 'ArrayItems',
+          'x-decorator': 'FormItem',
+          title: 'Recursive',
+          items: {
+            type: 'object',
+            properties: {
+              space: {
+                type: 'void',
+                'x-component': 'Space',
+                properties: {
+                  sort: {
+                    type: 'void',
+                    'x-decorator': 'FormItem',
+                    'x-component': 'ArrayItems.SortHandle',
+                  },
+                  input: {
+                    type: 'string',
+                    title: 'input',
+                    'x-decorator': 'FormItem',
+                    'x-component': 'Input',
+                  },
+                  select: {
+                    type: 'string',
+                    title: 'select',
+                    enum: [
+                      { label: 'select1', value: 1 },
+                      { label: 'select2', value: 2 },
+                    ],
+                    'x-decorator': 'FormItem',
+                    'x-component': 'Select',
+                    'x-component-props': {
+                      style: {
+                        width: '250px',
+                      },
+                    },
+                  },
+                  remove: {
+                    type: 'void',
+                    'x-decorator': 'FormItem',
+                    'x-component': 'ArrayItems.Remove',
+                  },
+                },
+              },
+            },
+          },
+          properties: {
+            add: {
+              type: 'void',
+              title: 'add items',
+              'x-component': 'ArrayItems.Addition',
+            },
+          },
+        },
+      },
+    },
+  },
+};
+console.log(schema);

+ 4 - 0
src/utils/BaseService.ts

@@ -49,6 +49,10 @@ class BaseService<T> implements IBaseService<T> {
       data,
     });
   }
+
+  getUri() {
+    return this.uri;
+  }
 }
 
 export default BaseService;