Kaynağa Gözat

feat(merge): merge sc

lind 4 yıl önce
ebeveyn
işleme
a98df470fb
56 değiştirilmiş dosya ile 1229 ekleme ve 1214 silme
  1. 7 7
      package.json
  2. BIN
      public/images/alarm/background.png
  3. BIN
      public/images/alarm/device.png
  4. BIN
      public/images/alarm/log.png
  5. BIN
      public/images/alarm/org.png
  6. BIN
      public/images/alarm/other.png
  7. BIN
      public/images/alarm/product.png
  8. BIN
      public/images/metadata-map.png
  9. BIN
      public/images/notice/doc/template/weixin-official/02-mini-Program-Appid.png
  10. BIN
      public/images/scene.png
  11. 1 1
      src/components/CheckButton/index.less
  12. 49 0
      src/components/ProTableCard/CardItems/scene.tsx
  13. 0 4
      src/pages/device/Instance/Detail/Info/index.tsx
  14. 12 23
      src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx
  15. 0 160
      src/pages/device/Instance/Detail/Reation/Edit.tsx
  16. 0 88
      src/pages/device/Instance/Detail/Reation/index.tsx
  17. 0 14
      src/pages/device/Instance/service.ts
  18. 0 1
      src/pages/device/Instance/typings.d.ts
  19. 1 224
      src/pages/device/components/Metadata/Base/Edit/index.tsx
  20. 2 24
      src/pages/device/components/Metadata/Cat/index.tsx
  21. 1 1
      src/pages/device/components/Metadata/index.tsx
  22. 25 31
      src/pages/link/AccessConfig/Detail/Access/index.tsx
  23. 1 1
      src/pages/link/AccessConfig/Detail/Provider/index.tsx
  24. 2 3
      src/pages/link/AccessConfig/service.ts
  25. 20 34
      src/pages/notice/Template/Debug/index.tsx
  26. 14 31
      src/pages/notice/Template/Detail/doc/WeixinApp.tsx
  27. 4 27
      src/pages/notice/Template/Log/index.tsx
  28. 0 1
      src/pages/notice/Template/index.tsx
  29. 0 1
      src/pages/notice/Template/typings.d.ts
  30. 1 32
      src/pages/rule-engine/Alarm/Log/model.ts
  31. 162 0
      src/pages/rule-engine/Scene/Save/action/action.tsx
  32. 18 0
      src/pages/rule-engine/Scene/Save/action/index.less
  33. 26 0
      src/pages/rule-engine/Scene/Save/action/index.tsx
  34. 60 0
      src/pages/rule-engine/Scene/Save/action/messageContent.tsx
  35. 28 0
      src/pages/rule-engine/Scene/Save/action/service.ts
  36. 7 0
      src/pages/rule-engine/Scene/Save/components/ItemGroup/index.less
  37. 10 0
      src/pages/rule-engine/Scene/Save/components/ItemGroup/index.tsx
  38. 49 0
      src/pages/rule-engine/Scene/Save/components/TimeSelect/index.tsx
  39. 89 0
      src/pages/rule-engine/Scene/Save/components/TimingTrigger/index.tsx
  40. 24 0
      src/pages/rule-engine/Scene/Save/components/TriggerWay/index.less
  41. 65 0
      src/pages/rule-engine/Scene/Save/components/TriggerWay/index.tsx
  42. 4 0
      src/pages/rule-engine/Scene/Save/components/index.ts
  43. 67 1
      src/pages/rule-engine/Scene/Save/index.tsx
  44. 10 0
      src/pages/rule-engine/Scene/Save/trigger/index.tsx
  45. 360 0
      src/pages/rule-engine/Scene/Save2/index.tsx
  46. 12 4
      src/pages/rule-engine/Scene/index.tsx
  47. 6 3
      src/pages/rule-engine/Scene/typings.d.ts
  48. 4 1
      src/pages/system/Menu/Detail/edit.tsx
  49. 0 251
      src/pages/system/Relationship/Save/index.tsx
  50. 0 146
      src/pages/system/Relationship/index.tsx
  51. 0 12
      src/pages/system/Relationship/service.ts
  52. 0 12
      src/pages/system/Relationship/typings.d.ts
  53. 3 1
      src/pages/system/User/ResetPassword/index.tsx
  54. 9 2
      src/utils/menu/index.ts
  55. 2 0
      src/utils/menu/router.ts
  56. 74 73
      yarn.lock

+ 7 - 7
package.json

@@ -62,13 +62,13 @@
     "@ant-design/pro-descriptions": "^1.6.8",
     "@ant-design/pro-form": "^1.18.3",
     "@ant-design/pro-layout": "^6.27.2",
-    "@formily/antd": "2.0.19",
-    "@formily/core": "2.0.19",
-    "@formily/json-schema": "2.0.19",
-    "@formily/react": "2.0.19",
-    "@formily/reactive": "2.0.19",
-    "@formily/reactive-react": "2.0.19",
-    "@formily/shared": "2.0.19",
+    "@formily/antd": "2.0.0-rc.17",
+    "@formily/core": "2.0.0-rc.17",
+    "@formily/json-schema": "2.0.0-rc.17",
+    "@formily/react": "2.0.0-rc.17",
+    "@formily/reactive": "2.0.0-rc.17",
+    "@formily/reactive-react": "2.0.0-rc.17",
+    "@formily/shared": "2.0.0-rc.17",
     "@jetlinks/pro-list": "^1.10.8",
     "@jetlinks/pro-table": "^2.63.11",
     "@liveqing/liveplayer": "^2.6.4",

BIN
public/images/alarm/background.png


BIN
public/images/alarm/device.png


BIN
public/images/alarm/log.png


BIN
public/images/alarm/org.png


BIN
public/images/alarm/other.png


BIN
public/images/alarm/product.png


BIN
public/images/metadata-map.png


BIN
public/images/notice/doc/template/weixin-official/02-mini-Program-Appid.png


BIN
public/images/scene.png


+ 1 - 1
src/components/CheckButton/index.less

@@ -1,4 +1,4 @@
-@import '../../../node_modules/antd/lib/style/themes/variable';
+@import '~antd/es/style/themes/default.less';
 
 .box {
   display: flex;

+ 49 - 0
src/components/ProTableCard/CardItems/scene.tsx

@@ -0,0 +1,49 @@
+import React from 'react';
+import { StatusColorEnum } from '@/components/BadgeStatus';
+import { TableCard } from '@/components';
+import '@/style/common.less';
+import '../index.less';
+import type { SceneItem } from '@/pages/rule-engine/Scene/typings';
+
+export interface DeviceCardProps extends SceneItem {
+  tools: React.ReactNode[];
+}
+
+const defaultImage = require('/public/images/scene.png');
+
+export default (props: DeviceCardProps) => {
+  return (
+    <TableCard
+      showMask={false}
+      actions={props.tools}
+      status={props.state.value}
+      statusText={props.state.text}
+      statusNames={{
+        online: StatusColorEnum.processing,
+        offline: StatusColorEnum.error,
+        notActive: StatusColorEnum.warning,
+      }}
+    >
+      <div className={'pro-table-card-item'}>
+        <div className={'card-item-avatar'}>
+          <img width={88} height={88} src={defaultImage} alt={''} />
+        </div>
+        <div className={'card-item-body'}>
+          <div className={'card-item-header'}>
+            <span className={'card-item-header-name ellipsis'}>{props.name}</span>
+          </div>
+          <div className={'card-item-content'}>
+            <div>
+              <label>触发方式</label>
+              <div className={'ellipsis'}>{'test'}</div>
+            </div>
+            <div>
+              <label>说明</label>
+              <div className={'ellipsis'}>{props.describe || '--'}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </TableCard>
+  );
+};

+ 0 - 4
src/pages/device/Instance/Detail/Info/index.tsx

@@ -4,7 +4,6 @@ import moment from 'moment';
 import { observer } from '@formily/react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import Config from '@/pages/device/Instance/Detail/Config';
-import Reation from '@/pages/device/Instance/Detail/Reation';
 import Save from '../../Save';
 import { useState } from 'react';
 import type { DeviceInstance } from '../../typings';
@@ -116,9 +115,6 @@ const Info = observer(() => {
         </Descriptions>
         <Config />
         {InstanceModel.detail?.tags && InstanceModel.detail?.tags.length > 0 && <Tags />}
-        {InstanceModel.detail?.relations && InstanceModel.detail?.relations.length > 0 && (
-          <Reation />
-        )}
       </Card>
       <Save
         model={'edit'}

+ 12 - 23
src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx

@@ -27,7 +27,6 @@ interface EditableCellProps {
   dataIndex: string;
   record: any;
   list: any[];
-  properties: any[];
   handleSave: (record: any) => void;
 }
 
@@ -38,17 +37,15 @@ const EditableCell = ({
   dataIndex,
   record,
   list,
-  properties,
   handleSave,
   ...restProps
 }: EditableCellProps) => {
   const form: any = useContext(EditableContext);
-  const [temp, setTemp] = useState<any>({});
 
   const save = async () => {
     try {
       const values = await form.validateFields();
-      handleSave({ ...record, originalId: values?.originalId });
+      handleSave({ ...record, metadataId: values?.metadataId });
     } catch (errInfo) {
       console.log('Save failed:', errInfo);
     }
@@ -57,7 +54,6 @@ const EditableCell = ({
   useEffect(() => {
     if (record) {
       form.setFieldsValue({ [dataIndex]: record[dataIndex] });
-      setTemp(properties.find((i) => i.id === record.originalId));
     }
   }, [record]);
 
@@ -75,11 +71,6 @@ const EditableCell = ({
           }
         >
           <Select.Option value={record.metadataId}>使用原始属性</Select.Option>
-          {record.originalId !== record.metadataId && (
-            <Select.Option value={record.originalId}>
-              {temp?.name}({temp?.id})
-            </Select.Option>
-          )}
           {list.length > 0 &&
             list.map((item: any) => (
               <Select.Option key={item?.id} value={item?.id}>
@@ -107,7 +98,7 @@ const EditableTable = (props: Props) => {
     },
     {
       title: '设备上报属性',
-      dataIndex: 'originalId',
+      dataIndex: 'metadataId',
       width: '30%',
       editable: true,
     },
@@ -145,7 +136,7 @@ const EditableTable = (props: Props) => {
     },
   };
 
-  const initData = async (lists: any[]) => {
+  const initData = async () => {
     let resp = null;
     if (props.type === 'device') {
       resp = await service.queryDeviceMetadata(props.data.id);
@@ -156,10 +147,10 @@ const EditableTable = (props: Props) => {
       const data = resp.result;
       const obj: any = {};
       data.map((i: any) => {
-        obj[i?.metadataId] = i;
+        obj[i?.originalId] = i;
       });
-      if (lists.length > 0) {
-        setPmList(lists.filter((i) => !_.map(data, 'originalId').includes(i.id)));
+      if (protocolMetadata.length > 0) {
+        setPmList(protocolMetadata.filter((i) => !_.map(data, 'metadataId').includes(i.id)));
       } else {
         setPmList([]);
       }
@@ -193,9 +184,8 @@ const EditableTable = (props: Props) => {
         )
         .then((resp) => {
           if (resp.status === 200) {
-            const list = JSON.parse(resp.result || '{}')?.properties || [];
-            setProtocolMetadata(list);
-            initData(list);
+            setProtocolMetadata(JSON.parse(resp.result || '{}')?.properties || []);
+            initData();
           }
         });
     }
@@ -205,21 +195,21 @@ const EditableTable = (props: Props) => {
     const newData = [...dataSource.data];
     const index = newData.findIndex((item) => row.id === item.id);
     const item = newData[index];
-    if (item?.originalId !== row?.originalId) {
+    if (item?.metadataId !== row?.metadataId) {
       const resp = await service[
         props.type === 'device' ? 'saveDeviceMetadata' : 'saveProductMetadata'
       ](props.data?.id, [
         {
           metadataType: 'property',
-          metadataId: row.metadataId,
-          originalId: row.metadataId !== row.originalId ? row.originalId : '',
+          metadataId: row.metadataId === row.id ? row.metadataId : row.id,
+          originalId: row.metadataId === row.id ? row.id : '',
           others: {},
         },
       ]);
       if (resp.status === 200) {
         message.success('操作成功!');
         // 刷新
-        initData(protocolMetadata);
+        initData();
       }
     }
   };
@@ -263,7 +253,6 @@ const EditableTable = (props: Props) => {
         dataIndex: col.dataIndex,
         title: col.title,
         list: pmList,
-        properties: protocolMetadata,
         handleSave: handleSave,
       }),
     };

+ 0 - 160
src/pages/device/Instance/Detail/Reation/Edit.tsx

@@ -1,160 +0,0 @@
-import type { Field } from '@formily/core';
-import { createForm } from '@formily/core';
-import { createSchemaField } from '@formily/react';
-import { InstanceModel, service } from '@/pages/device/Instance';
-import type { ISchema } from '@formily/json-schema';
-import { Form, FormGrid, FormItem, Select, PreviewText } from '@formily/antd';
-import { useParams } from 'umi';
-import { Button, Drawer, message, Space } from 'antd';
-import { action } from '@formily/reactive';
-import type { Response } from '@/utils/typings';
-import { useEffect, useState } from 'react';
-
-interface Props {
-  close: () => void;
-  data: any[];
-}
-
-const Edit = (props: Props) => {
-  const { data } = props;
-  const params = useParams<{ id: string }>();
-  const id = InstanceModel.detail?.id || params?.id;
-  const [initData, setInitData] = useState<any>({});
-
-  const getUsers = () => service.queryUserListNopaging();
-
-  const useAsyncDataSource = (api: any) => (field: Field) => {
-    field.loading = true;
-    api(field).then(
-      action.bound!((resp: Response<any>) => {
-        field.dataSource = resp.result?.map((item: Record<string, unknown>) => ({
-          ...item,
-          label: item.name,
-          value: JSON.stringify({
-            id: item.id,
-            name: item.name,
-          }),
-        }));
-        field.loading = false;
-      }),
-    );
-  };
-
-  const form = createForm({
-    validateFirst: true,
-    initialValues: initData,
-  });
-
-  const SchemaField = createSchemaField({
-    components: {
-      FormItem,
-      Select,
-      FormGrid,
-      PreviewText,
-    },
-  });
-
-  const configToSchema = (list: any[]) => {
-    const config = {};
-    list.forEach((item) => {
-      config[item.relation] = {
-        type: 'string',
-        title: item.relationName,
-        'x-decorator': 'FormItem',
-        'x-component': 'Select',
-        'x-component-props': {
-          placeholder: '请选择关联方',
-          showSearch: true,
-          showArrow: true,
-          mode: 'multiple',
-          filterOption: (input: string, option: any) =>
-            option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
-        },
-        'x-reactions': ['{{useAsyncDataSource(getUsers)}}'],
-      };
-    });
-    return config;
-  };
-
-  const renderConfigCard = () => {
-    const itemSchema: ISchema = {
-      type: 'object',
-      properties: {
-        grid: {
-          type: 'void',
-          'x-component': 'FormGrid',
-          'x-component-props': {
-            minColumns: [1],
-            maxColumns: [1],
-          },
-          properties: configToSchema(data),
-        },
-      },
-    };
-
-    return (
-      <>
-        <PreviewText.Placeholder value="-">
-          <Form form={form} layout="vertical">
-            <SchemaField schema={itemSchema} scope={{ useAsyncDataSource, getUsers }} />
-          </Form>
-        </PreviewText.Placeholder>
-      </>
-    );
-  };
-
-  useEffect(() => {
-    const obj: any = {};
-    (props?.data || []).map((item: any) => {
-      obj[item.relation] = [...(item?.related || []).map((i: any) => JSON.stringify(i))];
-    });
-    setInitData(obj);
-  }, [props.data]);
-
-  return (
-    <Drawer
-      title="编辑"
-      placement="right"
-      onClose={() => {
-        props.close();
-      }}
-      visible
-      extra={
-        <Space>
-          <Button
-            type="primary"
-            onClick={async () => {
-              const values = (await form.submit()) as any;
-              if (Object.keys(values).length > 0) {
-                const param: any[] = [];
-                Object.keys(values).forEach((key) => {
-                  const item = data.find((i) => i.relation === key);
-                  const items = (values[key] || []).map((i: string) => JSON.parse(i));
-                  if (item) {
-                    param.push({
-                      relatedType: 'user',
-                      relation: item.relation,
-                      description: '',
-                      related: [...items],
-                    });
-                  }
-                });
-                const resp = await service.saveRelations(id || '', param);
-                if (resp.status === 200) {
-                  message.success('操作成功!');
-                  props.close();
-                }
-              }
-            }}
-          >
-            保存
-          </Button>
-        </Space>
-      }
-    >
-      {renderConfigCard()}
-    </Drawer>
-  );
-};
-
-export default Edit;

+ 0 - 88
src/pages/device/Instance/Detail/Reation/index.tsx

@@ -1,88 +0,0 @@
-import { Descriptions, Tooltip } from 'antd';
-import { InstanceModel, service } from '@/pages/device/Instance';
-import { useEffect, useState } from 'react';
-import { history, useParams } from 'umi';
-import { EditOutlined, QuestionCircleOutlined } from '@ant-design/icons';
-import Edit from './Edit';
-import { PermissionButton } from '@/components';
-import _ from 'lodash';
-
-const Reation = () => {
-  const params = useParams<{ id: string }>();
-  useEffect(() => {
-    const id = InstanceModel.current?.id || params.id;
-    if (id) {
-      service.getConfigMetadata(id).then((response) => {
-        InstanceModel.config = response?.result;
-      });
-    } else {
-      history.goBack();
-    }
-  }, []);
-
-  const [data, setData] = useState<any[]>([]);
-  const [visible, setVisible] = useState<boolean>(false);
-  const { permission } = PermissionButton.usePermission('device/Instance');
-
-  const id = InstanceModel.detail?.id || params?.id;
-
-  const getDetail = () => {
-    service.detail(id || '').then((resp) => {
-      if (resp.status === 200) {
-        InstanceModel.detail = { id, ...resp.result };
-      }
-    });
-  };
-
-  useEffect(() => {
-    if (id) {
-      setData(InstanceModel.detail?.relations || []);
-    }
-  }, [id]);
-
-  return (
-    <div style={{ width: '100%', marginTop: '20px' }}>
-      <Descriptions
-        style={{ marginBottom: 20 }}
-        bordered
-        column={3}
-        size="small"
-        title={
-          <span>
-            关系信息
-            <PermissionButton
-              isPermission={permission.update}
-              type="link"
-              onClick={async () => {
-                setVisible(true);
-              }}
-            >
-              <EditOutlined />
-              编辑
-              <Tooltip title={`管理设备与其他业务的关联关系,关系来源于关系配置`}>
-                <QuestionCircleOutlined />
-              </Tooltip>
-            </PermissionButton>
-          </span>
-        }
-      >
-        {(data || [])?.map((item: any) => (
-          <Descriptions.Item span={1} label={item.relationName} key={item.objectId}>
-            {_.map(item?.related || [], 'name').join(',')}
-          </Descriptions.Item>
-        ))}
-      </Descriptions>
-      {visible && (
-        <Edit
-          data={data || []}
-          close={() => {
-            setVisible(false);
-            getDetail();
-          }}
-        />
-      )}
-    </div>
-  );
-};
-
-export default Reation;

+ 0 - 14
src/pages/device/Instance/service.ts

@@ -251,20 +251,6 @@ class Service extends BaseService<DeviceInstance> {
 
   //接入方式
   public queryGatewayList = () => request(`/${SystemConst.API_BASE}/gateway/device/providers`);
-  // 保存设备关系
-  public saveRelations = (id: string, data: any) =>
-    request(`/${SystemConst.API_BASE}/device/instance/${id}/relations`, {
-      method: 'PATCH',
-      data,
-    });
-  // 查询用户
-  public queryUserListNopaging = () =>
-    request(`/${SystemConst.API_BASE}/user/_query/no-paging`, {
-      method: 'POST',
-      data: {
-        paging: false,
-      },
-    });
 }
 
 export default Service;

+ 0 - 1
src/pages/device/Instance/typings.d.ts

@@ -29,7 +29,6 @@ export type DeviceInstance = {
   orgId: string;
   orgName: string;
   configuration: Record<string, any>;
-  relations?: any[];
   cachedConfiguration: any;
   transport: string;
   protocol: string;

+ 1 - 224
src/pages/device/components/Metadata/Base/Edit/index.tsx

@@ -2,14 +2,11 @@ import { Button, Drawer, Dropdown, Menu, message } from 'antd';
 import { createSchemaField, observer } from '@formily/react';
 import MetadataModel from '../model';
 import type { Field, IFieldState } from '@formily/core';
-import { createForm, onFieldReact, registerValidateRules } from '@formily/core';
+import { createForm, registerValidateRules } from '@formily/core';
 import {
   ArrayItems,
-  Checkbox,
-  DatePicker,
   Editable,
   Form,
-  FormGrid,
   FormItem,
   FormLayout,
   Input,
@@ -57,44 +54,6 @@ const Edit = observer((props: Props) => {
     () =>
       createForm({
         initialValues: MetadataModel.item as Record<string, unknown>,
-        effects: () => {
-          onFieldReact('expands.metrics.*.*', (field, form1) => {
-            console.log('指标配置');
-            const type = field.query('valueType.type').take() as Field;
-            console.log(type.value, 'value');
-
-            const componentMap = {
-              int: 'NumberPicker',
-              long: 'NumberPicker',
-              float: 'NumberPicker',
-              double: 'NumberPicker',
-              number: 'NumberPicker',
-              date: 'DatePicker',
-              boolean: 'Select',
-            };
-
-            form1.setFieldState('expands.metrics.*.edit.space.value.*', (state) => {
-              state.componentType = componentMap[type.value] || 'Input';
-              if (type.value === 'date') {
-                state.componentProps = {
-                  showTime: true,
-                };
-              } else if (type.value === 'boolean') {
-                state.componentType = 'Select';
-                // 获取 boolean配置的值
-                const values = form1.getValuesIn('valueType');
-                state.dataSource = [
-                  { label: values.trueText, value: values.trueValue },
-                  { label: values.falseText, value: values.falseValue },
-                ];
-              }
-            });
-          });
-          /// 处理Boolean 类型
-          // expands.metrics.0.edit.space.value.0 路径
-          // const metricsPath = field.query('expands.metrics.value.0');
-          // form.setValuesIn('expands.metrics.value.0', 'testtttt')
-        },
       }),
     [],
   );
@@ -143,9 +102,6 @@ const Edit = observer((props: Props) => {
       BooleanEnum,
       ConfigParam,
       FRuleEditor,
-      Checkbox,
-      FormGrid,
-      DatePicker,
     },
     scope: {
       async asyncOtherConfig(field: Field) {
@@ -660,185 +616,6 @@ const Edit = observer((props: Props) => {
             'x-component': 'ConfigParam',
             'x-reactions': '{{asyncOtherConfig}}',
           },
-          // 指标
-          metrics: {
-            type: 'array',
-            'x-component': 'ArrayItems',
-            'x-decorator': 'FormItem',
-            title: '指标配置',
-            items: {
-              type: 'object',
-              'x-decorator': 'ArrayItems.Item',
-              properties: {
-                left: {
-                  type: 'void',
-                  'x-component': 'Space',
-                  properties: {
-                    sort: {
-                      type: 'void',
-                      'x-decorator': 'FormItem',
-                      'x-component': 'ArrayItems.SortHandle',
-                    },
-                    index: {
-                      type: 'void',
-                      'x-decorator': 'FormItem',
-                      'x-component': 'ArrayItems.Index',
-                    },
-                  },
-                },
-                edit: {
-                  type: 'void',
-                  'x-component': 'Editable.Popover',
-                  title: '指标数据',
-                  properties: {
-                    id: {
-                      // 标识
-                      title: '标识',
-                      'x-decorator': 'FormItem',
-                      'x-component': 'Input',
-                      'x-decorator-props': {
-                        labelAlign: 'left',
-                        layout: 'vertical',
-                      },
-                    },
-                    name: {
-                      // 名称
-                      title: '名称',
-                      'x-decorator': 'FormItem',
-                      'x-component': 'Input',
-                      'x-decorator-props': {
-                        labelAlign: 'left',
-                        layout: 'vertical',
-                      },
-                    },
-                    space: {
-                      type: 'void',
-                      title: '指标值',
-                      'x-decorator': 'FormItem',
-                      'x-component': 'FormGrid',
-                      'x-decorator-props': {
-                        labelAlign: 'left',
-                        layout: 'vertical',
-                      },
-                      'x-component-props': {
-                        maxColumns: 12,
-                        minColumns: 12,
-                      },
-                      properties: {
-                        'value[0]': {
-                          'x-decorator': 'FormItem',
-                          'x-component': 'Input',
-                          'x-decorator-props': {
-                            gridSpan: 5,
-                          },
-                          'x-reactions': {
-                            dependencies: ['..range', 'valueType.type'],
-                            fulfill: {
-                              state: {
-                                decoratorProps: {
-                                  gridSpan: '{{!!$deps[0]?5:$deps[1]==="boolean"?12:10}}',
-                                },
-                                componentType:
-                                  '{{["int","long","double","float"].includes($deps[1])?"NumberPicker":["date"].includes($deps[1])?"DatePicker":"Input"}}',
-                              },
-                            },
-                          },
-                          // 根据数据类型来渲染不同的组件
-                        },
-                        'value[1]': {
-                          title: '~',
-                          'x-decorator': 'FormItem',
-                          'x-component': 'Input',
-                          'x-decorator-props': {
-                            gridSpan: 5,
-                          },
-                          'x-reactions': [
-                            {
-                              dependencies: ['..range', 'valueType.type'],
-                              fulfill: {
-                                state: {
-                                  visible: '{{!!$deps[0]}}',
-                                  componentType:
-                                    '{{["int","long","double","float"].includes($deps[1])?"NumberPicker":["date"].includes($deps[1])?"DatePicker":"Input"}}',
-                                },
-                              },
-                            },
-                            {
-                              dependencies: ['valueType.type'],
-                              fulfill: {
-                                state: {
-                                  visible: '{{!$deps[0]==="boolean"}}',
-                                },
-                              },
-                            },
-                          ],
-                        },
-                        // 根据数据类型来渲染不同的组件
-                        range: {
-                          type: 'boolean',
-                          default: false,
-                          'x-decorator': 'FormItem',
-                          'x-component': 'Checkbox',
-                          'x-component-props': {
-                            children: '范围',
-                          },
-                          'x-decorator-props': {
-                            gridSpan: 2,
-                          },
-                          'x-reactions': {
-                            dependencies: ['valueType.type'],
-                            when: '{{$deps[0]==="boolean"}}',
-                            fulfill: {
-                              state: {
-                                visible: false,
-                                decoratorProps: {
-                                  gridSpan: 0,
-                                },
-                              },
-                            },
-                            otherwise: {
-                              state: {
-                                visible: true,
-                                decoratorProps: {
-                                  gridSpan: 2,
-                                },
-                              },
-                            },
-                          },
-                        },
-                      },
-                    },
-                  },
-                },
-                right: {
-                  type: 'void',
-                  'x-component': 'Space',
-                  properties: {
-                    remove: {
-                      type: 'void',
-                      'x-component': 'ArrayItems.Remove',
-                    },
-                  },
-                },
-              },
-            },
-            properties: {
-              addition: {
-                type: 'void',
-                title: '添加指标',
-                'x-component': 'ArrayItems.Addition',
-              },
-            },
-            'x-reactions': {
-              dependencies: ['valueType.type'],
-              fulfill: {
-                state: {
-                  visible:
-                    "{{['int','float','double','long','date','string','boolean'].includes($deps[0])}}",
-                },
-              },
-            },
-          },
         },
       },
     },

+ 2 - 24
src/pages/device/components/Metadata/Cat/index.tsx

@@ -3,45 +3,23 @@ import { useEffect, useState } from 'react';
 import { productModel, service } from '@/pages/device/Product';
 import MonacoEditor from 'react-monaco-editor';
 import { observer } from '@formily/react';
-import { InstanceModel } from '@/pages/device/Instance';
-import { useLocation } from 'umi';
-import InstanceService from '@/pages/device/Instance/service';
 
 interface Props {
   visible: boolean;
   close: () => void;
-  type: 'product' | 'device';
 }
 
-const instanceService = new InstanceService('device-instance');
 const Cat = observer((props: Props) => {
-  const location = useLocation<{ id: string }>();
   const [codecs, setCodecs] = useState<{ id: string; name: string }[]>();
-  const metadataMap = {
-    product: productModel.current?.metadata as string,
-    device: InstanceModel.current?.metadata as string, // 有问题
-  };
-  const metadata = metadataMap[props.type];
+  const metadata = productModel.current?.metadata as string;
   const [value, setValue] = useState(metadata);
-  const _path = location.pathname.split('/');
-  const id = _path[_path.length - 1];
   useEffect(() => {
     service.codecs().subscribe({
       next: (data) => {
         setCodecs([{ id: 'jetlinks', name: 'jetlinks' }].concat(data));
       },
     });
-
-    if (props.type === 'device' && id) {
-      instanceService.detail(id).then((resp) => {
-        if (resp.status === 200) {
-          InstanceModel.current = resp.result;
-          const _metadata = resp.result?.metadata;
-          setValue(_metadata);
-        }
-      });
-    }
-  }, [id]);
+  }, []);
 
   const convertMetadata = (key: string) => {
     if (key === 'alink') {

+ 1 - 1
src/pages/device/components/Metadata/index.tsx

@@ -94,7 +94,7 @@ const Metadata = observer((props: Props) => {
         </Tabs.TabPane>
       </Tabs>
       <Import visible={visible} close={() => setVisible(false)} />
-      <Cat visible={cat} close={() => setCat(false)} type={props.type} />
+      <Cat visible={cat} close={() => setCat(false)} />
     </div>
   );
 });

+ 25 - 31
src/pages/link/AccessConfig/Detail/Access/index.tsx

@@ -42,7 +42,6 @@ const Access = (props: Props) => {
   const [config, setConfig] = useState<any>();
   const networkPermission = PermissionButton.usePermission('link/Type').permission;
   const protocolPermission = PermissionButton.usePermission('link/Protocol').permission;
-  const [steps, setSteps] = useState<string[]>(['网络组件', '消息协议', '完成']);
 
   const MetworkTypeMapping = new Map();
   MetworkTypeMapping.set('websocket-server', 'WEB_SOCKET_SERVER');
@@ -70,7 +69,7 @@ const Access = (props: Props) => {
     });
   };
 
-  const queryProcotolList = (id?: string, params?: any) => {
+  const queryProcotolList = (id: string, params?: any) => {
     service.getProtocolList(ProcotoleMapping.get(id), params).then((resp) => {
       if (resp.status === 200) {
         setProcotolList(resp.result);
@@ -80,39 +79,25 @@ const Access = (props: Props) => {
 
   useEffect(() => {
     if (props.provider?.id && !props.data?.id) {
-      if (props.provider?.id !== 'child-device') {
-        setSteps(['网络组件', '消息协议', '完成']);
-        queryNetworkList(props.provider?.id, {
-          include: networkCurrent || '',
-        });
-        setCurrent(0);
-      } else {
-        setSteps(['消息协议', '完成']);
-        setCurrent(1);
-        queryProcotolList(props.provider?.id);
-      }
+      queryNetworkList(props.provider?.id, {
+        include: networkCurrent || '',
+      });
+      setCurrent(0);
     }
   }, [props.provider]);
 
   useEffect(() => {
     if (props.data?.id) {
       setProcotolCurrent(props.data?.protocol);
+      setNetworkCurrent(props.data?.channelId);
       form.setFieldsValue({
         name: props.data?.name,
         description: props.data?.description,
       });
-      if (props.data?.provider !== 'child-device') {
-        setCurrent(0);
-        setSteps(['网络组件', '消息协议', '完成']);
-        setNetworkCurrent(props.data?.channelId);
-        queryNetworkList(props.data?.provider, {
-          include: props.data?.channelId,
-        });
-      } else {
-        setSteps(['消息协议', '完成']);
-        setCurrent(1);
-        queryProcotolList(props.data?.provider);
-      }
+      setCurrent(0);
+      queryNetworkList(props.data?.provider, {
+        include: props.data?.channelId,
+      });
     }
   }, [props.data]);
 
@@ -145,6 +130,18 @@ const Access = (props: Props) => {
     setCurrent(current - 1);
   };
 
+  const steps = [
+    {
+      title: '网络组件',
+    },
+    {
+      title: '消息协议',
+    },
+    {
+      title: '完成',
+    },
+  ];
+
   const columnsMQTT: any[] = [
     {
       title: '分组',
@@ -528,10 +525,7 @@ const Access = (props: Props) => {
                               description: values.description,
                               provider: props.provider.id,
                               protocol: procotolCurrent,
-                              transport:
-                                props.provider?.id === 'child-device'
-                                  ? 'Gateway'
-                                  : ProcotoleMapping.get(props.provider.id),
+                              transport: ProcotoleMapping.get(props.provider.id),
                               channel: 'network', // 网络组件
                               channelId: networkCurrent,
                             })
@@ -654,13 +648,13 @@ const Access = (props: Props) => {
         <div className={styles.steps}>
           <Steps size="small" current={current}>
             {steps.map((item) => (
-              <Steps.Step key={item} title={item} />
+              <Steps.Step key={item.title} title={item.title} />
             ))}
           </Steps>
         </div>
         <div className={styles.content}>{renderSteps(current)}</div>
         <div className={styles.action}>
-          {current === 1 && props.provider.id !== 'child-device' && (
+          {current === 1 && (
             <Button style={{ margin: '0 8px' }} onClick={() => prev()}>
               上一步
             </Button>

+ 1 - 1
src/pages/link/AccessConfig/Detail/Provider/index.tsx

@@ -50,7 +50,7 @@ const Provider = (props: Props) => {
                     <div className={styles.images}>{item.name}</div>
                     <div style={{ margin: '10px', width: 'calc(100% - 84px)' }}>
                       <div style={{ fontWeight: 600 }}>{item.name}</div>
-                      <div className={styles.desc}>{item.description || '--'}</div>
+                      <div className={styles.desc}>{item.description}</div>
                     </div>
                   </div>
                   <div style={{ width: '70px' }}>

+ 2 - 3
src/pages/link/AccessConfig/service.ts

@@ -31,12 +31,11 @@ class Service extends BaseService<AccessItem> {
       method: 'GET',
       params,
     });
-  public getProtocolList = (transport?: string, params?: any) => {
-    return request(`/${SystemConst.API_BASE}/protocol/supports/${transport ? transport : ''}`, {
+  public getProtocolList = (transport: string, params?: any) =>
+    request(`/${SystemConst.API_BASE}/protocol/supports/${transport}`, {
       method: 'GET',
       params,
     });
-  };
   public getConfigView = (id: string, transport: string) =>
     request(`/${SystemConst.API_BASE}/protocol/${id}/transport/${transport}`, {
       method: 'GET',

+ 20 - 34
src/pages/notice/Template/Debug/index.tsx

@@ -38,32 +38,27 @@ const Debug = observer(() => {
             }
           });
 
-          onFieldReact('variableDefinitions.*.id', (field) => {
+          onFieldReact('variableDefinitions.*.type', (field) => {
             const value = (field as Field).value;
-            const format = field.query('.value').take() as Field;
-
-            if (format) {
-              switch (value) {
-                case 'date':
-                  format.setComponent(DatePicker, {
-                    showTime: true,
-                  });
-                  break;
-                case 'string':
-                  format.setComponent(Input);
-                  break;
-                case 'number':
-                  format.setComponent(NumberPicker, {});
-                  break;
-                case 'file':
-                  format.setComponent(FUpload, {
-                    type: 'file',
-                  });
-                  break;
-                case 'other':
-                  format.setComponent(Input);
-                  break;
-              }
+            const format = field.query('.value').take() as any;
+            switch (value) {
+              case 'date':
+                format.setComponent(DatePicker);
+                break;
+              case 'string':
+                format.setComponent(Input);
+                break;
+              case 'number':
+                format.setComponent(NumberPicker);
+                break;
+              case 'file':
+                format.setComponent(FUpload, {
+                  type: 'file',
+                });
+                break;
+              case 'other':
+                format.setComponent(Input);
+                break;
             }
           });
         },
@@ -88,9 +83,6 @@ const Debug = observer(() => {
       Select,
       ArrayTable,
       PreviewText,
-      NumberPicker,
-      DatePicker,
-      FUpload,
     },
   });
 
@@ -172,12 +164,6 @@ const Debug = observer(() => {
                   'x-decorator': 'FormItem',
                   'x-component': 'Input',
                 },
-                type: {
-                  'x-hidden': true,
-                  type: 'string',
-                  'x-decorator': 'FormItem',
-                  'x-component': 'Input',
-                },
               },
             },
           },

+ 14 - 31
src/pages/notice/Template/Detail/doc/WeixinApp.tsx

@@ -2,50 +2,33 @@ import { Image } from 'antd';
 import './index.less';
 
 const WeixinApp = () => {
-  const appId = require('/public/images/notice/doc/template/weixin-official/02-mini-Program-Appid.png');
+  const agentId = require('/public/images/notice/doc/template/weixin-official/01-Agentid.jpg');
+  const appId = require('/public/images/notice/doc/template/weixin-official/02-mini-Program-Appid.jpg');
 
   return (
     <div className="doc">
       <div className="url">
-        企业微信管理后台:<a href="https://work.weixin.qq.com">https://work.weixin.qq.com</a>
+        微信公众平台:<a href="https://mp.weixin.qq.com/">https://mp.weixin.qq.com/</a>
       </div>
       <h1>1. 概述</h1>
       <div>
-        通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务
+        通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用
       </div>
-      <h1>2.模板配置说明</h1>
+      <h1>2.通知配置说明</h1>
       <div>
-        <h2>1. 绑定配置</h2>
-        <div>绑定通知配置</div>
-      </div>
-      <div>
-        <h2>2. 用户标签</h2>
-        <div>以标签的维度通知该标签下所有用户</div>
-      </div>
-      <div>
-        <h2>3. 消息模板</h2>
-        <div>微信公众号中配置的消息模板</div>
-      </div>
-      <div>
-        <h2>4. 模板跳转链接</h2>
-        <div>点击消息之后进行页面跳转</div>
-      </div>
-      <div>
-        <h2>5. 跳转小程序Appid</h2>
-        <div>点击消息之后打开对应的小程序</div>
-      </div>
-      <div>
-        <h2>6. 跳转小程序具体路径</h2>
-        <div>点击消息之后跳转到小程序的具体页面</div>
+        <h2>1. AppID</h2>
+        <div>微信服务号的唯一专属编号。</div>
+        <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
         <div className="image">
-          <Image width="100%" src={appId} />
+          <Image width="100%" src={agentId} />
         </div>
       </div>
+      <h2>2. AppSecret</h2>
       <div>
-        <h2>7. 模板内容</h2>
-        <div>
-          支持填写带变量的动态模板。变量填写规范示例:${name}
-          。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。
+        <div>公众号开发者身份的密码</div>
+        <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
+        <div className="image">
+          <Image width="100%" src={appId} />
         </div>
       </div>
     </div>

+ 4 - 27
src/pages/notice/Template/Log/index.tsx

@@ -1,4 +1,4 @@
-import { Badge, Modal } from 'antd';
+import { Modal } from 'antd';
 import { observer } from '@formily/react';
 import { service, state } from '..';
 import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
@@ -15,7 +15,6 @@ const Log = observer(() => {
     {
       dataIndex: 'id',
       title: 'id',
-      width: 200,
     },
     {
       dataIndex: 'sendTime',
@@ -24,30 +23,6 @@ const Log = observer(() => {
     {
       dataIndex: 'state',
       title: '状态',
-      renderText: (text: { value: string; text: string }, record) => {
-        return (
-          <>
-            <Badge status={text.value === 'success' ? 'success' : 'error'} text={text.text} />
-            {text.value !== 'success' && (
-              <a
-                key="info"
-                onClick={() => {
-                  Modal.info({
-                    title: '错误信息',
-                    width: '30vw',
-                    content: (
-                      <div style={{ height: '300px', overflowY: 'auto' }}>{record.errorStack}</div>
-                    ),
-                    onOk() {},
-                  });
-                }}
-              >
-                <InfoCircleOutlined />
-              </a>
-            )}
-          </>
-        );
-      },
     },
     {
       dataIndex: 'action',
@@ -59,7 +34,9 @@ const Log = observer(() => {
             Modal.info({
               title: '详情信息',
               width: '30vw',
-              content: <div style={{ height: '300px', overflowY: 'auto' }}>{record.message}</div>,
+              content: (
+                <div style={{ height: '300px', overflowY: 'auto' }}>{record.errorStack}</div>
+              ),
               onOk() {},
             });
           }}

+ 0 - 1
src/pages/notice/Template/index.tsx

@@ -315,7 +315,6 @@ const Template = observer(() => {
                     actionRef.current?.reset?.();
                   },
                 }}
-                isPermission={templatePermission.delete}
                 key="delete"
               >
                 <DeleteOutlined />

+ 0 - 1
src/pages/notice/Template/typings.d.ts

@@ -16,5 +16,4 @@ type LogItem = {
   sendTime: number;
   state: string;
   errorStack?: string;
-  message?: string;
 };

+ 1 - 32
src/pages/rule-engine/Alarm/Log/model.ts

@@ -1,38 +1,7 @@
 import { model } from '@formily/reactive';
-import type { ProColumns } from '@jetlinks/pro-table';
 
 export const AlarmLogModel = model<{
-  tab: string;
-  current: Partial<AlarmLogItem>;
-  solveVisible: boolean;
-  logVisible: boolean;
-  defaultLevel: {
-    level: number;
-    title: string;
-  }[];
-  columns: ProColumns<AlarmLogHistoryItem>[];
+  tab: 'product' | 'device' | 'department' | 'other';
 }>({
   tab: 'product',
-  current: {},
-  solveVisible: false,
-  logVisible: false,
-  defaultLevel: [],
-  columns: [
-    {
-      dataIndex: 'alarmTime',
-      title: '告警时间',
-    },
-    {
-      dataIndex: 'alarmName',
-      title: '告警名称',
-    },
-    {
-      dataIndex: 'description',
-      title: '说明',
-    },
-    {
-      dataIndex: 'action',
-      title: '操作',
-    },
-  ],
 });

+ 162 - 0
src/pages/rule-engine/Scene/Save/action/action.tsx

@@ -0,0 +1,162 @@
+import { Button, Form, InputNumber, Select } from 'antd';
+import { useEffect, useState } from 'react';
+import { useRequest } from 'umi';
+import { queryMessageConfig, queryMessageTemplate, queryMessageType } from './service';
+import MessageContent from './messageContent';
+
+interface ActionProps {
+  restField: any;
+  name: number;
+  title?: string;
+  onRemove: () => void;
+}
+
+const ActionItem = (props: ActionProps) => {
+  const { name } = props;
+  const [type1, setType1] = useState('');
+  const [templateData, setTemplateData] = useState<any>(undefined);
+
+  const { data: messageType, run: queryMessageTypes } = useRequest(queryMessageType, {
+    manual: true,
+    formatResult: (res) => res.result,
+  });
+
+  const {
+    data: messageConfig,
+    run: queryMessageConfigs,
+    loading: messageConfigLoading,
+  } = useRequest(queryMessageConfig, {
+    manual: true,
+    formatResult: (res) => res.result,
+  });
+
+  const {
+    data: messageTemplate,
+    run: queryMessageTemplates,
+    loading: messageTemplateLoading,
+  } = useRequest(queryMessageTemplate, {
+    manual: true,
+    formatResult: (res) => res.result,
+  });
+
+  const MessageNodes = (
+    <>
+      <Form.Item {...props.restField} name={[name, 'notify', 'type']}>
+        <Select
+          options={messageType}
+          fieldNames={{ value: 'id', label: 'name' }}
+          placeholder={'请选择通知方式'}
+          style={{ width: 140 }}
+          onChange={async (key: string) => {
+            setTemplateData(undefined);
+            await queryMessageConfigs({
+              terms: [{ column: 'type$IN', value: key }],
+            });
+          }}
+        />
+      </Form.Item>
+      <Form.Item {...props.restField} name={[name, 'notify', 'notifierId']}>
+        <Select
+          options={messageConfig}
+          loading={messageConfigLoading}
+          fieldNames={{ value: 'id', label: 'name' }}
+          onChange={async (key: string) => {
+            setTemplateData(undefined);
+            await queryMessageTemplates({
+              terms: [{ column: 'configId', value: key }],
+            });
+          }}
+          style={{ width: 160 }}
+          placeholder={'请选择通知配置'}
+        />
+      </Form.Item>
+      <Form.Item {...props.restField} name={[name, 'notify', 'templateId']}>
+        <Select
+          options={messageTemplate}
+          loading={messageTemplateLoading}
+          fieldNames={{ value: 'id', label: 'name' }}
+          style={{ width: 160 }}
+          placeholder={'请选择通知模板'}
+          onChange={async (key: string, nodeData: any) => {
+            setTemplateData(nodeData);
+          }}
+        />
+      </Form.Item>
+    </>
+  );
+
+  const DeviceNodes = (
+    <>
+      <Select options={[]} placeholder={'请选择产品'} style={{ width: 220 }} />
+      <Select
+        defaultValue={'1'}
+        options={[
+          { label: '固定设备', value: '1' },
+          { label: '按标签', value: '2' },
+          { label: '按关系', value: '3' },
+        ]}
+        style={{ width: 120 }}
+      />
+      <Select options={[]} placeholder={'请选择'} style={{ width: 180 }} />
+      <Select
+        defaultValue={'1'}
+        options={[
+          { label: '设置属性', value: '1' },
+          { label: '功能调用', value: '2' },
+          { label: '读取属性', value: '3' },
+        ]}
+        style={{ width: 120 }}
+      />
+    </>
+  );
+
+  useEffect(() => {
+    if (type1 === 'message') {
+      queryMessageTypes();
+    }
+  }, [type1]);
+
+  const TimeTypeAfter = (
+    <Select
+      defaultValue={'second'}
+      options={[
+        { label: '秒', value: 'second' },
+        { label: '分', value: 'minute' },
+        { label: '小时', value: 'hour' },
+      ]}
+    />
+  );
+
+  return (
+    <div className={'actions-item'}>
+      <div className={'actions-item-title'}>
+        执行动作 {props.title} <Button onClick={props.onRemove}>删除</Button>
+      </div>
+      <div style={{ display: 'flex', gap: 12 }}>
+        <Form.Item {...props.restField} name={[name, 'executor']}>
+          <Select
+            options={[
+              { label: '消息通知', value: 'message' },
+              { label: '设备输出', value: 'device' },
+              { label: '延迟执行', value: 'delay' },
+            ]}
+            style={{ width: 100 }}
+            onSelect={(key: string) => {
+              setType1(key);
+            }}
+          />
+        </Form.Item>
+        {type1 === 'message' && MessageNodes}
+        {type1 === 'device' && DeviceNodes}
+        {type1 === 'delay' && (
+          <InputNumber addonAfter={TimeTypeAfter} style={{ width: 150 }} min={0} max={9999} />
+        )}
+        {type1 === 'message' && templateData ? (
+          <MessageContent template={templateData} name={props.name} />
+        ) : null}
+      </div>
+    </div>
+  );
+};
+
+export default ActionItem;

+ 18 - 0
src/pages/rule-engine/Scene/Save/action/index.less

@@ -0,0 +1,18 @@
+.actions-items {
+  padding: 26px 24px 12px 24px;
+  background-color: #fafafa;
+
+  .actions-item {
+    .actions-item-title {
+      padding-bottom: 18px;
+    }
+
+    &:not(:first-child) {
+      margin-top: 18px;
+    }
+
+    .template-variable {
+      margin-top: 12px;
+    }
+  }
+}

+ 26 - 0
src/pages/rule-engine/Scene/Save/action/index.tsx

@@ -0,0 +1,26 @@
+import ActionItem from './action';
+import './index.less';
+import { ProFormList } from '@ant-design/pro-form';
+
+export default () => {
+  return (
+    <div className={'actions-items'}>
+      <ProFormList
+        name={'actions'}
+        creatorButtonProps={{
+          creatorButtonText: '新增',
+        }}
+      >
+        {(meta, index, action) => {
+          return (
+            <ActionItem
+              onRemove={() => {
+                action.remove?.(index);
+              }}
+            />
+          );
+        }}
+      </ProFormList>
+    </div>
+  );
+};

+ 60 - 0
src/pages/rule-engine/Scene/Save/action/messageContent.tsx

@@ -0,0 +1,60 @@
+import { Col, Form, Input, Row, Select, TimePicker } from 'antd';
+import { ItemGroup } from '@/pages/rule-engine/Scene/Save/components';
+
+interface MessageContentProps {
+  name: number;
+  template?: any;
+}
+
+const rowGutter = 12;
+
+export default (props: MessageContentProps) => {
+  const inputNodeByType = (data: any) => {
+    switch (data.type) {
+      case 'enum':
+        return <Select placeholder={`请选择${data.name}`} style={{ width: '100%' }} />;
+      case 'timmer':
+        return <TimePicker style={{ width: '100%' }} />;
+      case 'number':
+        return <Input placeholder={`请输入${data.name}`} style={{ width: '100%' }} />;
+      default:
+        return <Input placeholder={`请输入${data.name}`} />;
+    }
+  };
+
+  return (
+    <>
+      {props.template && (
+        <div className={'template-variable'}>
+          {props.template.variableDefinitions ? (
+            <Row gutter={rowGutter}>
+              {props.template.variableDefinitions.map((item: any) => {
+                // const rules = !item.required ? [{ required: true, message: '请输入'+ item.name }] : undefined
+                return (
+                  <Col span={12} key={item.id}>
+                    <Form.Item
+                      name={[props.name, 'notify', 'variables', item.id]}
+                      label={item.name}
+                    >
+                      <ItemGroup>
+                        <Select
+                          defaultValue={'1'}
+                          options={[
+                            { label: '手动输入', value: '1' },
+                            { label: '内置参数', value: '2' },
+                          ]}
+                          style={{ width: 120 }}
+                        />
+                        {inputNodeByType(item)}
+                      </ItemGroup>
+                    </Form.Item>
+                  </Col>
+                );
+              })}
+            </Row>
+          ) : null}
+        </div>
+      )}
+    </>
+  );
+};

+ 28 - 0
src/pages/rule-engine/Scene/Save/action/service.ts

@@ -0,0 +1,28 @@
+import { request } from '@@/plugin-request/request';
+import SystemConst from '@/utils/const';
+
+export const queryMessageType = () =>
+  request(`${SystemConst.API_BASE}/notifier/config/types`, { method: 'GET' });
+
+// 通知配置
+export const queryMessageConfig = (data: any) =>
+  request(`${SystemConst.API_BASE}/notifier/config/_query/no-paging?paging=false`, {
+    method: 'POST',
+    data,
+  });
+
+// 通知模板
+export const queryMessageTemplate = (data: any) =>
+  request(`${SystemConst.API_BASE}/notifier/template/_query/no-paging?paging=false`, {
+    method: 'POST',
+    data,
+  });
+
+export const queryProductList = (data?: any) =>
+  request(`${SystemConst.API_BASE}/device-product/_query/no-paging?paging=false`, {
+    method: 'POST',
+    data,
+  });
+
+export const queryDeviceSelector = () =>
+  request(`${SystemConst.API_BASE}/scene/device-selectors`, { method: 'GET' });

+ 7 - 0
src/pages/rule-engine/Scene/Save/components/ItemGroup/index.less

@@ -0,0 +1,7 @@
+.group-item-compact {
+  display: flex;
+
+  &:nth-last-child(1) {
+    flex: 1;
+  }
+}

+ 10 - 0
src/pages/rule-engine/Scene/Save/components/ItemGroup/index.tsx

@@ -0,0 +1,10 @@
+import React from 'react';
+import './index.less';
+
+interface ItemGroupProps {
+  children?: React.ReactNode;
+}
+
+export default (props: ItemGroupProps) => {
+  return <div className={'group-item-compact'}>{props.children}</div>;
+};

+ 49 - 0
src/pages/rule-engine/Scene/Save/components/TimeSelect/index.tsx

@@ -0,0 +1,49 @@
+import { TreeSelect } from 'antd';
+import React, { useCallback, useEffect, useState } from 'react';
+
+type OptionsItemType = {
+  label: string;
+  value: string | number;
+};
+
+interface TimeSelectProps {
+  options?: OptionsItemType[];
+  value?: string;
+  onChange?: (value: string[]) => void;
+  style?: React.CSSProperties;
+}
+
+export default (props: TimeSelectProps) => {
+  const [checkedKeys, setCheckedKeys] = useState<any[]>([]);
+
+  const onChange = useCallback(
+    (keys: string[], _, extra) => {
+      if (extra.triggerValue === 'all') {
+        const newKeys = extra.checked ? ['all', ...props.options!.map((item) => item.value)] : [];
+        setCheckedKeys(newKeys);
+      } else {
+        const noAllKeys = keys.filter((key) => key !== 'all');
+        const newKeys = noAllKeys.length === props.options!.length ? ['all', ...keys] : noAllKeys;
+
+        setCheckedKeys(newKeys);
+      }
+    },
+    [checkedKeys, props.options],
+  );
+
+  useEffect(() => {}, [checkedKeys]);
+
+  return (
+    <TreeSelect
+      treeCheckable
+      value={checkedKeys}
+      onChange={onChange}
+      style={props.style}
+      treeData={
+        props.options && props.options.length
+          ? [{ label: '全部', value: 'all' }, ...props.options]
+          : []
+      }
+    />
+  );
+};

+ 89 - 0
src/pages/rule-engine/Scene/Save/components/TimingTrigger/index.tsx

@@ -0,0 +1,89 @@
+import { Input, InputNumber, Select, TimePicker } from 'antd';
+import { TimeSelect } from '@/pages/rule-engine/Scene/Save/components';
+import { useState } from 'react';
+
+export default () => {
+  const [type1, setType1] = useState(1);
+  const [type2, setType2] = useState(1);
+
+  const type1Select = async (key: number) => {
+    setType1(key);
+    if (key !== 3) {
+      // TODO 请求后端返回天数
+    }
+  };
+
+  const type2Select = (key: number) => {
+    setType2(key);
+  };
+
+  const TimeTypeAfter = (
+    <Select
+      defaultValue={'second'}
+      options={[
+        { label: '秒', value: 'second' },
+        { label: '分', value: 'minute' },
+        { label: '小时', value: 'hour' },
+      ]}
+    />
+  );
+
+  const implementNode =
+    type2 === 1 ? (
+      <>
+        <TimePicker.RangePicker />
+        <span> 每 </span>
+        <InputNumber addonAfter={TimeTypeAfter} style={{ width: 150 }} min={0} max={9999} />
+        <span> 执行一次 </span>
+      </>
+    ) : (
+      <>
+        <TimePicker />
+        <InputNumber addonAfter={TimeTypeAfter} style={{ width: 150 }} min={0} max={9999} />
+        <span> 执行一次 </span>
+      </>
+    );
+
+  return (
+    <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
+      <Select
+        options={[
+          { label: '按周', value: 1 },
+          { label: '按月', value: 2 },
+          { label: 'cron表达式', value: 3 },
+        ]}
+        value={type1}
+        onSelect={type1Select}
+        style={{ width: 120 }}
+      />
+      {type1 !== 3 ? (
+        <>
+          <TimeSelect
+            options={[
+              { label: '周一', value: 1 },
+              { label: '周二', value: 2 },
+              { label: '周三', value: 3 },
+              { label: '周四', value: 4 },
+              { label: '周五', value: 5 },
+              { label: '周六', value: 6 },
+              { label: '周末', value: 7 },
+            ]}
+            style={{ width: 150 }}
+          />
+          <Select
+            options={[
+              { label: '周期执行', value: 1 },
+              { label: '执行一次', value: 2 },
+            ]}
+            value={type2}
+            style={{ width: 150 }}
+            onSelect={type2Select}
+          />
+          {implementNode}
+        </>
+      ) : (
+        <Input placeholder={'请输入cron表达式'} style={{ width: 400 }} />
+      )}
+    </div>
+  );
+};

+ 24 - 0
src/pages/rule-engine/Scene/Save/components/TriggerWay/index.less

@@ -0,0 +1,24 @@
+@import '~antd/es/style/themes/default.less';
+
+.trigger-way-warp {
+  display: flex;
+  gap: 20px;
+  width: 100%;
+
+  .trigger-way-item {
+    padding: 20px 16px;
+    border: 1px solid #e0e4e8;
+    border-radius: 2px;
+    cursor: pointer;
+    transition: all 0.3s;
+
+    &:hover {
+      color: @primary-color-hover;
+    }
+
+    &.active {
+      color: @primary-color-active;
+      border-color: @primary-color-active;
+    }
+  }
+}

+ 65 - 0
src/pages/rule-engine/Scene/Save/components/TriggerWay/index.tsx

@@ -0,0 +1,65 @@
+import { useEffect, useState } from 'react';
+import classNames from 'classnames';
+import './index.less';
+
+interface TriggerWayProps {
+  value?: string;
+  onChange?: (type: string) => void;
+}
+
+enum TriggerWayType {
+  manual = 'manual',
+  timing = 'timer',
+  device = 'device',
+}
+
+export default (props: TriggerWayProps) => {
+  const [type, setType] = useState(props.value || '');
+
+  useEffect(() => {
+    setType(props.value || '');
+  }, [props.value]);
+
+  const onSelect = (_type: string) => {
+    setType(_type);
+
+    if (props.onChange) {
+      props.onChange(_type);
+    }
+  };
+
+  return (
+    <div className={'trigger-way-warp'}>
+      <div
+        className={classNames('trigger-way-item', {
+          active: type === TriggerWayType.manual,
+        })}
+        onClick={() => {
+          onSelect(TriggerWayType.manual);
+        }}
+      >
+        手动触发
+      </div>
+      <div
+        className={classNames('trigger-way-item', {
+          active: type === TriggerWayType.timing,
+        })}
+        onClick={() => {
+          onSelect(TriggerWayType.timing);
+        }}
+      >
+        定时触发
+      </div>
+      <div
+        className={classNames('trigger-way-item', {
+          active: type === TriggerWayType.device,
+        })}
+        onClick={() => {
+          onSelect(TriggerWayType.device);
+        }}
+      >
+        设备触发
+      </div>
+    </div>
+  );
+};

+ 4 - 0
src/pages/rule-engine/Scene/Save/components/index.ts

@@ -0,0 +1,4 @@
+export { default as TimeSelect } from './TimeSelect';
+export { default as TimingTrigger } from './TimingTrigger';
+export { default as TriggerWay } from './TriggerWay';
+export { default as ItemGroup } from './ItemGroup';

+ 67 - 1
src/pages/rule-engine/Scene/Save/index.tsx

@@ -1 +1,67 @@
-export default () => {};
+import { PageContainer } from '@ant-design/pro-layout';
+import { Button, Card, Form } from 'antd';
+import { useLocation } from 'umi';
+import { useEffect } from 'react';
+import { PermissionButton } from '@/components';
+import ActionItems from './action/action';
+import { PlusOutlined } from '@ant-design/icons';
+
+export default () => {
+  const location = useLocation();
+  const [form] = Form.useForm();
+
+  const { getOtherPermission } = PermissionButton.usePermission('rule-engine/Scene');
+
+  const getDetail = async () => {
+    // TODO 回显数据
+  };
+
+  useEffect(() => {
+    const params = new URLSearchParams(location.search);
+    const id = params.get('id');
+    if (id) {
+      getDetail();
+    }
+  }, [location]);
+
+  const saveData = async () => {
+    const formData = await form.validateFields();
+    console.log(formData);
+  };
+
+  return (
+    <PageContainer>
+      <Card>
+        <Form form={form} autoComplete="off">
+          <Form.List name="users">
+            {(fields, { add, remove }) => (
+              <>
+                {fields.map(({ key, name, ...restField }) => (
+                  <ActionItems
+                    key={key}
+                    restField={restField}
+                    onRemove={() => remove(name)}
+                    name={name}
+                  />
+                ))}
+                <Form.Item>
+                  <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
+                    新增
+                  </Button>
+                </Form.Item>
+              </>
+            )}
+          </Form.List>
+          <Form.Item>
+            <Button type="primary" htmlType="submit">
+              Submit
+            </Button>
+          </Form.Item>
+        </Form>
+        <PermissionButton isPermission={getOtherPermission(['add', 'update'])} onClick={saveData}>
+          保存
+        </PermissionButton>
+      </Card>
+    </PageContainer>
+  );
+};

+ 10 - 0
src/pages/rule-engine/Scene/Save/trigger/index.tsx

@@ -0,0 +1,10 @@
+import { Form } from 'antd';
+import TriggerWay from '../components/TriggerWay';
+
+export default () => {
+  return (
+    <Form.Item>
+      <TriggerWay />
+    </Form.Item>
+  );
+};

+ 360 - 0
src/pages/rule-engine/Scene/Save2/index.tsx

@@ -0,0 +1,360 @@
+import {
+  ArrayItems,
+  DatePicker,
+  Editable,
+  FormButtonGroup,
+  FormGrid,
+  FormItem,
+  FormLayout,
+  Input,
+  NumberPicker,
+  Radio,
+  Select,
+  Space,
+  Submit,
+} from '@formily/antd';
+import type { Field } from '@formily/core';
+import { createForm, FieldDataSource, onFieldReact, onFieldValueChange } from '@formily/core';
+import { createSchemaField, FormProvider } from '@formily/react';
+import { action } from '@formily/reactive';
+import {
+  queryMessageConfig,
+  queryMessageTemplate,
+  queryMessageType,
+  queryProductList,
+} from '@/pages/rule-engine/Scene/Save/action/service';
+import { Card } from 'antd';
+import { useMemo } from 'react';
+import type { ISchema } from '@formily/json-schema';
+
+export default () => {
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Editable,
+      DatePicker,
+      Space,
+      Radio,
+      Input,
+      Select,
+      ArrayItems,
+      FormLayout,
+      FormGrid,
+      NumberPicker,
+    },
+  });
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      layout: {
+        type: 'void',
+        'x-component': 'FormLayout',
+        'x-component-props': {
+          layout: 'vertical',
+        },
+        properties: {
+          actions: {
+            type: 'array',
+            'x-component': 'ArrayItems',
+            'x-decorator': 'FormItem',
+            title: '执行动作',
+            items: {
+              type: 'object',
+              title: '执行动作',
+              properties: {
+                space: {
+                  type: 'void',
+                  'x-component': 'Space',
+                  properties: {
+                    executor: {
+                      type: 'string',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'Select',
+                      'x-component-props': {
+                        style: {
+                          width: 160,
+                        },
+                      },
+                      enum: [
+                        { label: '消息通知', value: 'message' },
+                        { label: '设备输出', value: 'device' },
+                        { label: '延迟执行', value: 'delay' },
+                      ],
+                    },
+                    notify: {
+                      type: 'object',
+                      'x-decorator': 'FormItem',
+                      'x-reactions': [
+                        {
+                          dependencies: ['.executor'],
+                          fulfill: {
+                            state: {
+                              visible: "{{$deps[0] === 'message'}}",
+                            },
+                          },
+                        },
+                      ],
+                      properties: {
+                        grid: {
+                          type: 'void',
+                          'x-component': 'FormGrid',
+                          'x-component-props': {
+                            minColumns: [4, 6, 10],
+                          },
+                          properties: {
+                            messageType: {
+                              type: 'string',
+                              'x-decorator': 'FormItem',
+                              'x-component': 'Select',
+                              'x-component-props': {
+                                style: { width: 160 },
+                                fieldNames: { label: 'name', value: 'id' },
+                              },
+                              'x-reactions': ['{{useAsyncDataSource(getMessageType)}}'],
+                            },
+                            notifierId: {
+                              type: 'string',
+                              'x-decorator': 'FormItem',
+                              'x-component': 'Select',
+                              'x-component-props': {
+                                style: { width: 160 },
+                                fieldNames: { label: 'name', value: 'id' },
+                              },
+                            },
+                            templateId: {
+                              type: 'string',
+                              'x-decorator': 'FormItem',
+                              'x-component': 'Select',
+                              'x-component-props': {
+                                style: { width: 160 },
+                              },
+                            },
+                          },
+                        },
+                      },
+                    },
+                    variables: {
+                      type: 'object',
+                      'x-decorator': 'FormItem',
+                      properties: {},
+                      'x-reactions': [
+                        {
+                          dependencies: ['.executor'],
+                          fulfill: {
+                            state: {
+                              visible: "{{$deps[0] === 'message'}}",
+                            },
+                          },
+                        },
+                      ],
+                    },
+                    device: {
+                      type: 'object',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'Space',
+                      'x-reactions': [
+                        {
+                          dependencies: ['.executor'],
+                          fulfill: {
+                            state: {
+                              visible: "{{$deps[0] === 'device'}}",
+                            },
+                          },
+                        },
+                      ],
+                      properties: {
+                        productId: {
+                          type: 'string',
+                          'x-decorator': 'FormItem',
+                          'x-component': 'Select',
+                          'x-component-props': {
+                            style: { width: 200 },
+                            fieldNames: { label: 'name', value: 'id' },
+                          },
+                          'x-reactions': ['{{useAsyncDataSource(getProductList)}}'],
+                        },
+                        selector: {
+                          type: 'string',
+                          'x-decorator': 'FormItem',
+                          'x-component': 'Select',
+                          'x-component-props': {
+                            style: { width: 200 },
+                          },
+                          enum: [
+                            { label: '固定设备', value: 'device' },
+                            { label: '按标签', value: 'tag' },
+                            { label: '按关系', value: 'relation' },
+                          ],
+                        },
+                        'message.messageType': {
+                          type: 'string',
+                          'x-decorator': 'FormItem',
+                          'x-component': 'Select',
+                          'x-component-props': {
+                            style: { width: 160 },
+                          },
+                          enum: [
+                            { label: '功能调用', value: 'INVOKE_FUNCTION' },
+                            { label: '读取属性', value: 'READ_PROPERTY' },
+                            { label: '设置属性', value: 'WRITE_PROPERTY' },
+                          ],
+                        },
+                        value: {
+                          type: 'object',
+                          'x-decorator': 'FormItem',
+                        },
+                      },
+                    },
+                    delay: {
+                      type: 'object',
+                      'x-decorator': 'FormItem',
+                      'x-reactions': [
+                        {
+                          dependencies: ['.executor'],
+                          fulfill: {
+                            state: {
+                              visible: "{{$deps[0] === 'delay'}}",
+                            },
+                          },
+                        },
+                      ],
+                      properties: {
+                        time: {
+                          type: 'number',
+                          'x-decorator': 'FormItem',
+                          'x-component': 'NumberPicker',
+                          'x-component-props': {
+                            style: {
+                              width: 240,
+                            },
+                          },
+                        },
+                        unit: {
+                          type: 'string',
+                          'x-decorator': 'FormItem',
+                          'x-component': 'Select',
+                          'x-component-props': {
+                            style: {
+                              width: 160,
+                            },
+                          },
+                          enum: [
+                            { label: '秒', value: 'seconds' },
+                            { label: '分', value: 'minutes' },
+                            { label: '小时', value: 'hours' },
+                          ],
+                        },
+                      },
+                    },
+                  },
+                },
+                remove: {
+                  type: 'void',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'ArrayItems.Remove',
+                },
+              },
+            },
+            properties: {
+              add: {
+                type: 'void',
+                title: '添加条目',
+                'x-component': 'ArrayItems.Addition',
+              },
+            },
+          },
+        },
+      },
+    },
+  };
+
+  const form = useMemo(
+    () =>
+      createForm({
+        effects: () => {
+          onFieldReact('actions.*.notify.notifierId', async (field, f) => {
+            const key = field.query('.messageType').get('value');
+            f.clearFormGraph('.variables');
+            (field as Field).value = undefined;
+            if (key) {
+              (field as Field).loading = true;
+              const resp = await queryMessageConfig({ terms: [{ column: 'type$IN', value: key }] });
+              (field as Field).loading = false;
+              if (resp.status === 200) {
+                (field as Field).dataSource = resp.result;
+              }
+            }
+          });
+          onFieldReact('actions.*.notify.templateId', async (field) => {
+            const key = field.query('.notifierId').get('value');
+            (field as Field).value = undefined;
+            if (key) {
+              (field as Field).loading = true;
+              const resp = await queryMessageTemplate({
+                terms: [{ column: 'configId', value: key }],
+              });
+              (field as Field).loading = false;
+              if (resp.status === 200) {
+                (field as Field).dataSource = resp.result.map((item: any) => ({
+                  label: item.name,
+                  value: item.id,
+                  data: item,
+                }));
+              }
+            }
+          });
+          onFieldValueChange('actions.*.notify.templateId', async (field) => {
+            console.log(field);
+
+            const templateData = field.dataSource.find((item) => item.value === field.value);
+            if (templateData) {
+              const data = templateData.data;
+              if (data.variableDefinitions) {
+                const obj = {};
+                data.variableDefinitions.forEach((item: any) => {
+                  obj[item.id] = {
+                    title: item.name,
+                    type: 'string',
+                    'x-decorator': 'FormItem',
+                    'x-component': 'Input',
+                  };
+                });
+              }
+            }
+          });
+        },
+      }),
+    [],
+  );
+
+  const getMessageType = async () => await queryMessageType();
+
+  const getProductList = async () =>
+    await queryProductList({ sorts: [{ name: 'createTime', order: 'desc' }] });
+
+  const useAsyncDataSource =
+    (services: (arg0: Field) => Promise<FieldDataSource>) => (field: Field) => {
+      field.loading = true;
+      services(field).then(
+        action.bound!((resp: any) => {
+          field.dataSource = resp.result;
+          field.loading = false;
+        }),
+      );
+    };
+
+  return (
+    <Card>
+      <FormProvider form={form}>
+        <SchemaField
+          schema={schema}
+          scope={{ useAsyncDataSource, getMessageType, getProductList }}
+        />
+        <FormButtonGroup>
+          <Submit onSubmit={console.log}>提交</Submit>
+        </FormButtonGroup>
+      </FormProvider>
+    </Card>
+  );
+};

+ 12 - 4
src/pages/rule-engine/Scene/index.tsx

@@ -1,5 +1,5 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { useRef, useState } from 'react';
+import React, { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import type { SceneItem } from '@/pages/rule-engine/Scene/typings';
 import { Badge, message } from 'antd';
@@ -8,7 +8,10 @@ import { useIntl } from '@@/plugin-locale/localeExports';
 import { PermissionButton, ProTableCard } from '@/components';
 import { statusMap } from '@/pages/device/Instance';
 import SearchComponent from '@/components/SearchComponent';
+import SceneCard from '@/components/ProTableCard/CardItems/scene';
 import Service from './service';
+import { useHistory } from 'umi';
+import { getMenuPathByCode } from '@/utils/menu';
 
 export const service = new Service('rule-engine/scene');
 
@@ -17,8 +20,9 @@ const Scene = () => {
   const actionRef = useRef<ActionType>();
   const { permission } = PermissionButton.usePermission('rule-engine/Scene');
   const [searchParams, setSearchParams] = useState<any>({});
+  const history = useHistory();
 
-  const Tools = (record: any, type: 'card' | 'table') => {
+  const Tools = (record: any, type: 'card' | 'table'): React.ReactNode[] => {
     return [
       <PermissionButton
         key={'update'}
@@ -189,7 +193,7 @@ const Scene = () => {
           setSearchParams(data);
         }}
       />
-      <ProTableCard
+      <ProTableCard<SceneItem>
         columns={columns}
         actionRef={actionRef}
         params={searchParams}
@@ -213,7 +217,10 @@ const Scene = () => {
             icon={<PlusOutlined />}
             type="primary"
             isPermission={permission.add}
-            onClick={() => {}}
+            onClick={() => {
+              const url = getMenuPathByCode('rule-engine/Scene/Save');
+              history.push(url);
+            }}
           >
             {intl.formatMessage({
               id: 'pages.data.option.add',
@@ -221,6 +228,7 @@ const Scene = () => {
             })}
           </PermissionButton>,
         ]}
+        cardRender={(record) => <SceneCard {...record} tools={Tools(record, 'card')} />}
       />
     </PageContainer>
   );

+ 6 - 3
src/pages/rule-engine/Scene/typings.d.ts

@@ -1,4 +1,4 @@
-import type { BaseItem, State } from '@/utils/typings';
+import type { State } from '@/utils/typings';
 
 type Action = {
   executor: string;
@@ -10,9 +10,12 @@ type Trigger = {
   device: Record<string, unknown>;
 };
 
-type SceneItem = {
+interface SceneItem {
   parallel: boolean;
   state: State;
   actions: Action[];
   triggers: Trigger[];
-} & BaseItem;
+  id: string;
+  name: string;
+  describe: string;
+}

+ 4 - 1
src/pages/system/Menu/Detail/edit.tsx

@@ -159,7 +159,10 @@ export default (props: EditProps) => {
                       defaultMessage: '名称',
                     })}
                     required={true}
-                    rules={[{ required: true, message: '请输入名称' }]}
+                    rules={[
+                      { required: true, message: '请输入名称' },
+                      { max: 64, message: '最多可输入64个字符' },
+                    ]}
                   >
                     <Input disabled={disabled} placeholder={'请输入名称'} />
                   </Form.Item>

+ 0 - 251
src/pages/system/Relationship/Save/index.tsx

@@ -1,251 +0,0 @@
-import { useIntl } from 'umi';
-import type { Field } from '@formily/core';
-import { createForm } from '@formily/core';
-import { createSchemaField } from '@formily/react';
-import React from 'react';
-import * as ICONS from '@ant-design/icons';
-import { Form, FormGrid, FormItem, Input, Select } from '@formily/antd';
-import type { ISchema } from '@formily/json-schema';
-import { action } from '@formily/reactive';
-import type { Response } from '@/utils/typings';
-import { service } from '@/pages/system/Relationship';
-import { Modal } from '@/components';
-import { message } from 'antd';
-
-interface Props {
-  data: Partial<ReationItem>;
-  close: () => void;
-}
-
-const Save = (props: Props) => {
-  const intl = useIntl();
-
-  const getTypes = () => service.getTypes();
-
-  const useAsyncDataSource = (api: any) => (field: Field) => {
-    field.loading = true;
-    api(field).then(
-      action.bound!((resp: Response<any>) => {
-        field.dataSource = resp.result?.map((item: Record<string, unknown>) => ({
-          ...item,
-          label: item.name,
-          value: JSON.stringify({
-            objectType: item.id,
-            objectTypeName: item.name,
-          }),
-        }));
-        field.loading = false;
-      }),
-    );
-  };
-
-  const form = createForm({
-    validateFirst: true,
-    initialValues: props.data,
-  });
-
-  const SchemaField = createSchemaField({
-    components: {
-      FormItem,
-      Input,
-      Select,
-      FormGrid,
-    },
-    scope: {
-      icon(name: any) {
-        return React.createElement(ICONS[name]);
-      },
-    },
-  });
-
-  const schema: ISchema = {
-    type: 'object',
-    properties: {
-      layout: {
-        type: 'void',
-        'x-decorator': 'FormGrid',
-        'x-decorator-props': {
-          maxColumns: 2,
-          minColumns: 2,
-          columnGap: 24,
-        },
-        properties: {
-          name: {
-            title: '名称',
-            type: 'string',
-            'x-decorator': 'FormItem',
-            'x-component': 'Input',
-            'x-decorator-props': {
-              gridSpan: 2,
-            },
-            'x-component-props': {
-              placeholder: '请输入名称',
-            },
-            name: 'name',
-            'x-validator': [
-              {
-                max: 64,
-                message: '最多可输入64个字符',
-              },
-              {
-                required: true,
-                message: '请输入名称',
-              },
-            ],
-          },
-          relation: {
-            title: '标识',
-            'x-decorator-props': {
-              gridSpan: 2,
-            },
-            type: 'string',
-            'x-disabled': !!props.data?.id,
-            'x-decorator': 'FormItem',
-            'x-component': 'Input',
-            'x-component-props': {
-              placeholder: '请输入标识',
-            },
-            'x-validator': [
-              {
-                max: 64,
-                message: '最多可输入64个字符',
-              },
-              {
-                required: true,
-                message: '请输入标识',
-              },
-              // {
-              //   triggerType: 'onBlur',
-              //   // validator: (value: string) => {
-              //   //   return new Promise((resolve) => {
-              //   //     service
-              //   //       .validateField('username', value)
-              //   //       .then((resp) => {
-              //   //         if (resp.status === 200) {
-              //   //           if (resp.result.passed) {
-              //   //             resolve('');
-              //   //           } else {
-              //   //             resolve(model === 'edit' ? '' : resp.result.reason);
-              //   //           }
-              //   //         }
-              //   //         resolve('');
-              //   //       })
-              //   //       .catch(() => {
-              //   //         return '验证失败!';
-              //   //       });
-              //   //   });
-              //   // },
-              // },
-            ],
-            name: 'relation',
-            required: true,
-          },
-          object: {
-            title: '关联方',
-            'x-decorator-props': {
-              gridSpan: 1,
-            },
-            type: 'string',
-            'x-decorator': 'FormItem',
-            'x-disabled': !!props.data?.id,
-            'x-component': 'Select',
-            'x-component-props': {
-              placeholder: '请选择关联方',
-              showArrow: true,
-              filterOption: (input: string, option: any) =>
-                option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
-            },
-            required: true,
-            'x-reactions': ['{{useAsyncDataSource(getTypes)}}'],
-          },
-          target: {
-            title: '被关联方',
-            'x-decorator-props': {
-              gridSpan: 1,
-            },
-            type: 'string',
-            'x-decorator': 'FormItem',
-            'x-disabled': !!props.data?.id,
-            'x-component': 'Select',
-            'x-component-props': {
-              placeholder: '请选择被关联方',
-            },
-            'x-reactions': {
-              dependencies: ['..object'],
-              fulfill: {
-                state: {
-                  dataSource:
-                    '{{JSON.parse($deps[0] || "{}").objectType==="device"?[{label: "用户", value: JSON.stringify({"targetType":"user", "targetTypeName": "用户"})}] : []}}',
-                },
-              },
-            },
-            required: true,
-          },
-          description: {
-            title: '说明',
-            'x-decorator': 'FormItem',
-            'x-component': 'Input.TextArea',
-            'x-component-props': {
-              rows: 5,
-              placeholder: '请输入说明',
-            },
-            'x-decorator-props': {
-              gridSpan: 2,
-            },
-            'x-validator': [
-              {
-                max: 200,
-                message: '最多可输入200个字符',
-              },
-            ],
-          },
-        },
-      },
-    },
-  };
-
-  const save = async () => {
-    const value = await form.submit<any>();
-    const temp: any = {
-      ...props.data,
-      ...value,
-      ...JSON.parse(value?.object || '{}'),
-      ...JSON.parse(value?.target || '{}'),
-    };
-    delete temp.object;
-    delete temp.target;
-    const response: any = await service[!props.data?.id ? 'save' : 'update']({ ...temp });
-    if (response.status === 200) {
-      message.success(
-        intl.formatMessage({
-          id: 'pages.data.option.success',
-          defaultMessage: '操作成功',
-        }),
-      );
-      props.close();
-    } else {
-      message.error('操作失败!');
-    }
-  };
-
-  return (
-    <Modal
-      title={intl.formatMessage({
-        id: `pages.data.option.${props.data.id ? 'edit' : 'add'}`,
-        defaultMessage: '编辑',
-      })}
-      maskClosable={false}
-      visible
-      onCancel={props.close}
-      onOk={save}
-      width="35vw"
-      permissionCode={'system/Relationship'}
-      permission={['add', 'edit']}
-    >
-      <Form form={form} layout="vertical">
-        <SchemaField schema={schema} scope={{ useAsyncDataSource, getTypes }} />
-      </Form>
-    </Modal>
-  );
-};
-export default Save;

+ 0 - 146
src/pages/system/Relationship/index.tsx

@@ -1,146 +0,0 @@
-import SearchComponent from '@/components/SearchComponent';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import ProTable from '@jetlinks/pro-table';
-import { useRef, useState } from 'react';
-import Service from '@/pages/system/Relationship/service';
-import { PageContainer } from '@ant-design/pro-layout';
-import { PermissionButton } from '@/components';
-import { useIntl } from 'umi';
-import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
-import { message } from 'antd';
-import Save from './Save';
-
-export const service = new Service('relation');
-
-const Relationship = () => {
-  const intl = useIntl();
-  const [param, setParam] = useState<any>({});
-  const [current, setCurrent] = useState<Partial<ReationItem>>({});
-  const [visible, setVisible] = useState<boolean>(false);
-  const actionRef = useRef<ActionType>();
-  const { permission } = PermissionButton.usePermission('system/Relationship');
-
-  const columns: ProColumns<ReationItem>[] = [
-    {
-      dataIndex: 'name',
-      title: '名称',
-      ellipsis: true,
-    },
-    {
-      dataIndex: 'objectTypeName',
-      title: '关联方',
-      ellipsis: true,
-    },
-    {
-      dataIndex: 'targetTypeName',
-      title: '被关联方',
-      ellipsis: true,
-    },
-    {
-      dataIndex: 'description',
-      title: '说明',
-      ellipsis: true,
-    },
-    {
-      title: '操作',
-      valueType: 'option',
-      align: 'center',
-      width: 200,
-      render: (text, record) => [
-        <PermissionButton
-          isPermission={permission.update}
-          key="warning"
-          onClick={() => {
-            setVisible(true);
-            setCurrent(record);
-          }}
-          type={'link'}
-          style={{ padding: 0 }}
-          tooltip={{
-            title: intl.formatMessage({
-              id: 'pages.data.option.edit',
-              defaultMessage: '编辑',
-            }),
-          }}
-        >
-          <EditOutlined />
-        </PermissionButton>,
-        <PermissionButton
-          isPermission={permission.delete}
-          style={{ padding: 0 }}
-          popConfirm={{
-            title: '确认删除',
-            onConfirm: async () => {
-              const resp: any = await service.remove(record.id);
-              if (resp.status === 200) {
-                message.success(
-                  intl.formatMessage({
-                    id: 'pages.data.option.success',
-                    defaultMessage: '操作成功!',
-                  }),
-                );
-                actionRef.current?.reload();
-              }
-            },
-          }}
-          key="button"
-          type="link"
-        >
-          <DeleteOutlined />
-        </PermissionButton>,
-      ],
-    },
-  ];
-
-  return (
-    <PageContainer>
-      <SearchComponent<ReationItem>
-        field={columns}
-        target="relationship"
-        onSearch={(data) => {
-          actionRef.current?.reload();
-          setParam(data);
-        }}
-      />
-      <ProTable<ReationItem>
-        actionRef={actionRef}
-        params={param}
-        columns={columns}
-        search={false}
-        rowKey="id"
-        request={async (params) => {
-          return service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] });
-        }}
-        headerTitle={[
-          <PermissionButton
-            isPermission={permission.add}
-            key="add"
-            onClick={() => {
-              setVisible(true);
-              setCurrent({});
-            }}
-            type="primary"
-            tooltip={{
-              title: intl.formatMessage({
-                id: 'pages.data.option.add',
-                defaultMessage: '新增',
-              }),
-            }}
-          >
-            新增
-          </PermissionButton>,
-        ]}
-      />
-      {visible && (
-        <Save
-          data={current}
-          close={() => {
-            setVisible(false);
-            actionRef.current?.reload();
-          }}
-        />
-      )}
-    </PageContainer>
-  );
-};
-export default Relationship;

+ 0 - 12
src/pages/system/Relationship/service.ts

@@ -1,12 +0,0 @@
-import BaseService from '@/utils/BaseService';
-import { request } from 'umi';
-import SystemConst from '@/utils/const';
-
-class Service extends BaseService<ReationItem> {
-  getTypes = () =>
-    request(`/${SystemConst.API_BASE}/relation/types`, {
-      method: 'GET',
-    });
-}
-
-export default Service;

+ 0 - 12
src/pages/system/Relationship/typings.d.ts

@@ -1,12 +0,0 @@
-type ReationItem = {
-  id: string;
-  name: string;
-  objectType: string;
-  objectTypeName: string;
-  relation: string;
-  targetType: string;
-  targetTypeName: string;
-  createTime: number;
-  description?: string;
-  expands?: Record<string, any>;
-};

+ 3 - 1
src/pages/system/User/ResetPassword/index.tsx

@@ -108,7 +108,7 @@ const ResetPassword = (props: Props) => {
     },
   };
 
-  const form = useMemo(() => createForm({}), [props.visible]);
+  const form = useMemo(() => createForm({}), []);
   return (
     <Modal
       title="重置密码"
@@ -122,6 +122,8 @@ const ResetPassword = (props: Props) => {
             message.success('操作成功');
             props.close();
           }
+        } else {
+          props.close();
         }
       }}
     >

+ 9 - 2
src/utils/menu/index.ts

@@ -1,7 +1,8 @@
 // 路由components映射
 import type { IRouteProps } from 'umi';
 import type { MenuItem } from '@/pages/system/Menu/typing';
-import { BUTTON_PERMISSION, getDetailNameByCode, MENUS_CODE, MENUS_CODE_TYPE } from './router';
+import type { BUTTON_PERMISSION, MENUS_CODE_TYPE } from './router';
+import { getDetailNameByCode, MENUS_CODE } from './router';
 
 /** localStorage key */
 export const MENUS_DATA_CACHE = 'MENUS_DATA_CACHE';
@@ -29,6 +30,12 @@ const extraRouteObj = {
       { code: 'Playback', name: '回放' },
     ],
   },
+  'rule-engine/Scene': {
+    children: [
+      { code: 'Save', name: '详情' },
+      { code: 'Save2', name: '测试详情' },
+    ],
+  },
 };
 
 /**
@@ -257,7 +264,7 @@ export const getButtonPermission = (
  * 通过缓存的数据取出相应的路由url
  * @param code
  */
-export const getMenuPathByCode = (code: string): string => {
+export const getMenuPathByCode = (code: MENUS_CODE_TYPE): string => {
   const menusStr = localStorage.getItem(MENUS_DATA_CACHE) || '{}';
   const menusData = JSON.parse(menusStr);
   return menusData[code];

+ 2 - 0
src/utils/menu/router.ts

@@ -63,6 +63,8 @@ export enum MENUS_CODE {
   'rule-engine/Alarm/Log' = 'rule-engine/Alarm/Log',
   'rule-engine/Alarm/Log/Detail' = 'rule-engine/Alarm/Log/Detail',
   'rule-engine/Alarm/Config' = 'rule-engine/Alarm/Config',
+  'rule-engine/Scene/Save' = 'rule-engine/Scene/Save',
+  'rule-engine/Scene/Save2' = 'rule-engine/Scene/Save2',
   'rule-engine/Alarm/Configuration' = 'rule-engine/Alarm/Configuration',
   'simulator/Device' = 'simulator/Device',
   'system/DataSource' = 'system/DataSource',

+ 74 - 73
yarn.lock

@@ -2778,85 +2778,86 @@
   resolved "https://registry.yarnpkg.com/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz#2dc8c57044de0340eb53a7ba602e59abf80dc799"
   integrity sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==
 
-"@formily/antd@2.0.19":
-  version "2.0.19"
-  resolved "https://registry.yarnpkg.com/@formily/antd/-/antd-2.0.19.tgz#7419807965d5d1f39324b46e0be4f6aae04ca267"
-  integrity sha512-pxybyq2zWS4Ki56oY7227yjonVN7mnFiaIXSy/NVRD5wXxUBzOvrFA+4LiJuFGv0vzUkmSBbFCBkcDb/8TRZXQ==
-  dependencies:
-    "@formily/core" "2.0.19"
-    "@formily/grid" "2.0.19"
-    "@formily/json-schema" "2.0.19"
-    "@formily/react" "2.0.19"
-    "@formily/reactive" "2.0.19"
-    "@formily/reactive-react" "2.0.19"
-    "@formily/shared" "2.0.19"
+"@formily/antd@2.0.0-rc.17":
+  version "2.0.0-rc.17"
+  resolved "https://registry.yarnpkg.com/@formily/antd/-/antd-2.0.0-rc.17.tgz#fe41ded1c387a2018c292cd37c17e13350ee1dfe"
+  integrity sha512-FLUnq56b43+va3NiH2bhLPSO2BhmAMMxRWvdMX2qfV7s6SG4TQ35hEQkxDPu3YUUKKDv/75kFdgAJIaIZwHcNA==
+  dependencies:
+    "@ant-design/icons" "^4.0.0"
+    "@formily/core" "2.0.0-rc.17"
+    "@formily/grid" "2.0.0-rc.17"
+    "@formily/json-schema" "2.0.0-rc.17"
+    "@formily/react" "2.0.0-rc.17"
+    "@formily/reactive" "2.0.0-rc.17"
+    "@formily/reactive-react" "2.0.0-rc.17"
+    "@formily/shared" "2.0.0-rc.17"
+    "@juggle/resize-observer" "^3.3.1"
     classnames "^2.2.6"
     react-sortable-hoc "^1.11.0"
     react-sticky-box "^0.9.3"
 
-"@formily/core@2.0.19":
-  version "2.0.19"
-  resolved "https://registry.yarnpkg.com/@formily/core/-/core-2.0.19.tgz#359bef69964b623d8468934e4cc396de4eb03173"
-  integrity sha512-VsqWJKc2jhjzPgu4SKN5EVJeRrEwu+mAvsSo5bdDeKDTQ3b9+L9TTpUF8Q4t9NvZshK+gMAfvdCYNnb5hUqSnw==
-  dependencies:
-    "@formily/reactive" "2.0.19"
-    "@formily/shared" "2.0.19"
-    "@formily/validator" "2.0.19"
-
-"@formily/grid@2.0.19":
-  version "2.0.19"
-  resolved "https://registry.yarnpkg.com/@formily/grid/-/grid-2.0.19.tgz#b0b3aa45f027fd23c5918f1490597aeea7b2b90d"
-  integrity sha512-x2s1EVAkiGx6rdFr333gsNJjpwS9yLHIECvvStqWcTfBHlszrFxtPyAa1rYf0RCvjBMWq0EE6p2o6VIVqKVOtw==
-  dependencies:
-    "@formily/reactive" "2.0.19"
-    "@juggle/resize-observer" "^3.3.1"
-
-"@formily/json-schema@2.0.19":
-  version "2.0.19"
-  resolved "https://registry.yarnpkg.com/@formily/json-schema/-/json-schema-2.0.19.tgz#e14167060a07abd54759bb5ee17c6679156b866f"
-  integrity sha512-BTcEZwcGM/up6VKEVZ4wulD4hI5fYBb8n5SgRnaezSJbHECK23p8Yh13Qj4h1GFbQbnCWr6FVYFvqBSAc8tyOQ==
-  dependencies:
-    "@formily/core" "2.0.19"
-    "@formily/reactive" "2.0.19"
-    "@formily/shared" "2.0.19"
-
-"@formily/path@2.0.19":
-  version "2.0.19"
-  resolved "https://registry.yarnpkg.com/@formily/path/-/path-2.0.19.tgz#391abd170fd68048a4f59568b7b150c68fd36785"
-  integrity sha512-uiNyq0Vrls7ie8/odP7ZVybNBOFgwJVQ68XXIzq4ZPrki0uSyoVAn5CrCkNP94PdqOjN8/gjP4sQo6eSXvPnvQ==
-
-"@formily/react@2.0.19":
-  version "2.0.19"
-  resolved "https://registry.yarnpkg.com/@formily/react/-/react-2.0.19.tgz#44d0afeb4eff2b62f555664aa620cb9ecff1bec2"
-  integrity sha512-R6FE/pX1u06nORiWX7hNgb8idMcZdd+ozvZu1iupgDqAespWz6axl24OOKWH56+JU/uXDRXG8dvGKds5rjctvQ==
-  dependencies:
-    "@formily/core" "2.0.19"
-    "@formily/json-schema" "2.0.19"
-    "@formily/reactive" "2.0.19"
-    "@formily/reactive-react" "2.0.19"
-    "@formily/shared" "2.0.19"
-    "@formily/validator" "2.0.19"
+"@formily/core@2.0.0-rc.17":
+  version "2.0.0-rc.17"
+  resolved "https://registry.yarnpkg.com/@formily/core/-/core-2.0.0-rc.17.tgz#06880aa6be6f6f822050998662654748bce1627b"
+  integrity sha512-O+iahZipqv1iwqQW9KDLTSo0USVwFrwjUs3v6ToHWgaJHbOFY3rHtUjUBB00QN4cQsc7tANErx8+MawoV/fH8Q==
+  dependencies:
+    "@formily/reactive" "2.0.0-rc.17"
+    "@formily/shared" "2.0.0-rc.17"
+    "@formily/validator" "2.0.0-rc.17"
+
+"@formily/grid@2.0.0-rc.17":
+  version "2.0.0-rc.17"
+  resolved "https://registry.yarnpkg.com/@formily/grid/-/grid-2.0.0-rc.17.tgz#46603919f435cbf71b65f039a4f7a4c98db8cf77"
+  integrity sha512-1boauZbcyKFC+0Pom3xeOzbH5DvCBXs2u9xHklDoaQrxZZLPggtZLlo7Qxyx7zXfyKQRn910WkbzlWFIt2VaDg==
+  dependencies:
+    "@formily/reactive" "2.0.0-rc.17"
+
+"@formily/json-schema@2.0.0-rc.17":
+  version "2.0.0-rc.17"
+  resolved "https://registry.yarnpkg.com/@formily/json-schema/-/json-schema-2.0.0-rc.17.tgz#5638ad56c7c44da89ae33b60b8b1f78f548ed9e4"
+  integrity sha512-x7kqGGdXXS40a3xf7LvbjPvMsCEhUZCshoEfHbQaxKOq4Y+mtUCpFYwJMBr3xsJG6+Yid3IDqflL0yBHn9/SDA==
+  dependencies:
+    "@formily/core" "2.0.0-rc.17"
+    "@formily/reactive" "2.0.0-rc.17"
+    "@formily/shared" "2.0.0-rc.17"
+
+"@formily/path@2.0.0-rc.17":
+  version "2.0.0-rc.17"
+  resolved "https://registry.yarnpkg.com/@formily/path/-/path-2.0.0-rc.17.tgz#40ea317fc8f46fa9908c4407720d8bcfd7e5fae6"
+  integrity sha512-BOFI38udFlYC/q9DYHehwu9FfKOdW1KgIjXp0t/wFlwfiVGQ+B/KyKVSkFPzEocdK5Q3fkujs8kyGLoyJLfSHQ==
+
+"@formily/react@2.0.0-rc.17":
+  version "2.0.0-rc.17"
+  resolved "https://registry.yarnpkg.com/@formily/react/-/react-2.0.0-rc.17.tgz#a88d9fa4b30d08a5ac19fda7ae09841fab64d493"
+  integrity sha512-PLBZYzKHNAb8PSGJrIFBEHA8kB3+j3WN0ls6weo89RXqbcnmkLCmjs6Xa7Cx0KYFLSLkmUZySmrI+Y51w0ASJA==
+  dependencies:
+    "@formily/core" "2.0.0-rc.17"
+    "@formily/json-schema" "2.0.0-rc.17"
+    "@formily/reactive" "2.0.0-rc.17"
+    "@formily/reactive-react" "2.0.0-rc.17"
+    "@formily/shared" "2.0.0-rc.17"
+    "@formily/validator" "2.0.0-rc.17"
     hoist-non-react-statics "^3.3.2"
 
-"@formily/reactive-react@2.0.19":
-  version "2.0.19"
-  resolved "https://registry.yarnpkg.com/@formily/reactive-react/-/reactive-react-2.0.19.tgz#0526cc22346d62c1809eefbdbac988a1845e581e"
-  integrity sha512-Laz3O/oSCIA4qKQ4fIMsyUQjS4XtD00nUvXSXIZhGdTkZW09Spq8zv7wd+0V6REEKIH6urTtC8htpBQN8W3fww==
+"@formily/reactive-react@2.0.0-rc.17":
+  version "2.0.0-rc.17"
+  resolved "https://registry.yarnpkg.com/@formily/reactive-react/-/reactive-react-2.0.0-rc.17.tgz#fa7d86c83170f183c8180bc6a798797609e8839b"
+  integrity sha512-7rHZ1Az0cpqjLccmrwASJ68b6QxPzJ2mpTLYKf5jbmIINPB5mG0zziFPJLymY15ljAQ6jIyX15viOkDBSkedJA==
   dependencies:
-    "@formily/reactive" "2.0.19"
+    "@formily/reactive" "2.0.0-rc.17"
     hoist-non-react-statics "^3.3.2"
 
-"@formily/reactive@2.0.19":
-  version "2.0.19"
-  resolved "https://registry.yarnpkg.com/@formily/reactive/-/reactive-2.0.19.tgz#4498b4e70c466bfee9b9dda8639ffe6f10d5d7ca"
-  integrity sha512-gEpiEITdrRHGc+cf/0lalw4gTcES+8axdAxC0mZRMHfJ8iSZnFs369AGxiWdElUK9NNVLfEmSuU60op6XCQhrg==
+"@formily/reactive@2.0.0-rc.17":
+  version "2.0.0-rc.17"
+  resolved "https://registry.yarnpkg.com/@formily/reactive/-/reactive-2.0.0-rc.17.tgz#fcf752d2c6c14459580d08305efc0d56d9741278"
+  integrity sha512-xFLOFnd+O5t1TRmunlFJHpTTKObSjh7rxJW7IvO42OkrV1o2dUJ7TdDcsaZIHsHg9H/3tMFHzAtfGcprpIcYAA==
 
-"@formily/shared@2.0.19":
-  version "2.0.19"
-  resolved "https://registry.yarnpkg.com/@formily/shared/-/shared-2.0.19.tgz#602ce0738fe39fb0773accc3345329ba3b0cbbac"
-  integrity sha512-1zKNZLKoEEH31Y9+rBXdByHVsUModWyshkPj7fsZv0KkaObn/wV2WUCKLQW4c4Hn1y+yojPH//8SD2oOZ4wZXw==
+"@formily/shared@2.0.0-rc.17":
+  version "2.0.0-rc.17"
+  resolved "https://registry.yarnpkg.com/@formily/shared/-/shared-2.0.0-rc.17.tgz#c6e88df5652f376783130be908e428d3533093b5"
+  integrity sha512-+09L5mOP0MwOk5AOOiphNlZ1PPNYxPI/7pMthulyF3BIDSAJF9Odx3IGvGz+YthzD0fYpi3QX7ly2OuwAA3FhA==
   dependencies:
-    "@formily/path" "2.0.19"
+    "@formily/path" "2.0.0-rc.17"
     camel-case "^4.1.1"
     lower-case "^2.0.1"
     no-case "^3.0.4"
@@ -2864,12 +2865,12 @@
     pascal-case "^3.1.1"
     upper-case "^2.0.1"
 
-"@formily/validator@2.0.19":
-  version "2.0.19"
-  resolved "https://registry.yarnpkg.com/@formily/validator/-/validator-2.0.19.tgz#4d14191b6ab92b0298a59b42964cab008ef7f551"
-  integrity sha512-KS9g0WXKR77ET+3blKGxDL2w4e8gp0z5kkd5BDm7bIUmfNb67rTuSaacs+8MbOuckt09B7qU1nzOekXkskaRNw==
+"@formily/validator@2.0.0-rc.17":
+  version "2.0.0-rc.17"
+  resolved "https://registry.yarnpkg.com/@formily/validator/-/validator-2.0.0-rc.17.tgz#e11bf27a5f5b14bed92dcdc8e5dbf2430965018d"
+  integrity sha512-srjQrfY8ubKaFjldb75lcHhBVgXKNY6Q1R6BvFr2Xogslbkriv2ct752Bix0YC+cFZ4elFwWyiOknSaupnzZRg==
   dependencies:
-    "@formily/shared" "2.0.19"
+    "@formily/shared" "2.0.0-rc.17"
 
 "@hapi/address@^2.1.2":
   version "2.1.4"