lind 3 лет назад
Родитель
Сommit
9c7a62c463
30 измененных файлов с 545 добавлено и 212 удалено
  1. 1 1
      src/components/ProTableCard/CardItems/duerOs.tsx
  2. 1 1
      src/pages/Northbound/AliCloud/Detail/index.tsx
  3. 113 66
      src/pages/Northbound/DuerOS/Detail/index.tsx
  4. 22 3
      src/pages/Northbound/DuerOS/index.tsx
  5. 4 1
      src/pages/Northbound/DuerOS/types.d.ts
  6. 4 1
      src/pages/cloud/DuerOS/typings.d.ts
  7. 5 5
      src/pages/rule-engine/Scene/Save/action/VariableItems/builtIn.tsx
  8. 29 10
      src/pages/rule-engine/Scene/Save/action/VariableItems/user.tsx
  9. 2 0
      src/pages/rule-engine/Scene/Save/action/action.tsx
  10. 0 1
      src/pages/rule-engine/Scene/Save/action/device/AllDevice.tsx
  11. 6 1
      src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx
  12. 14 6
      src/pages/rule-engine/Scene/Save/action/device/deviceModal.tsx
  13. 1 5
      src/pages/rule-engine/Scene/Save/action/device/functionCall.tsx
  14. 6 2
      src/pages/rule-engine/Scene/Save/action/device/index.tsx
  15. 6 1
      src/pages/rule-engine/Scene/Save/action/device/readProperty.tsx
  16. 1 1
      src/pages/rule-engine/Scene/Save/action/device/tagModal.tsx
  17. 6 3
      src/pages/rule-engine/Scene/Save/action/messageContent.tsx
  18. 8 2
      src/pages/rule-engine/Scene/Save/action/service.ts
  19. 4 4
      src/pages/rule-engine/Scene/Save/components/TimingTrigger/index.tsx
  20. 48 33
      src/pages/rule-engine/Scene/Save/index.tsx
  21. 8 8
      src/pages/rule-engine/Scene/Save/trigger/device.tsx
  22. 32 32
      src/pages/rule-engine/Scene/Save/trigger/index.tsx
  23. 1 0
      src/pages/rule-engine/Scene/Save/trigger/operation.tsx
  24. 57 8
      src/pages/system/Platforms/Api/basePage.tsx
  25. 20 0
      src/pages/system/Platforms/Api/index.less
  26. 15 5
      src/pages/system/Platforms/Api/index.tsx
  27. 119 0
      src/pages/system/Platforms/Api/leftTree.tsx
  28. 3 3
      src/pages/system/Platforms/index.tsx
  29. 6 9
      src/pages/system/Platforms/save.tsx
  30. 3 0
      src/utils/menu/index.ts

+ 1 - 1
src/components/ProTableCard/CardItems/duerOs.tsx

@@ -45,7 +45,7 @@ export default (props: DuerOSProps) => {
             <div>
               <label>设备类型</label>
               <div className={'ellipsis'}>
-                <Tooltip title={props.applianceType}>{props.applianceType}</Tooltip>
+                <Tooltip title={props.applianceType?.text}>{props.applianceType?.text}</Tooltip>
               </div>
             </div>
           </div>

+ 1 - 1
src/pages/Northbound/AliCloud/Detail/index.tsx

@@ -58,7 +58,7 @@ const Detail = observer(() => {
   );
 
   useEffect(() => {
-    if (params.id) {
+    if (params.id && params.id !== ':id') {
       service.detail(params.id).then((resp) => {
         if (resp.status === 200) {
           form.setValues(resp.result);

+ 113 - 66
src/pages/Northbound/DuerOS/Detail/index.tsx

@@ -21,6 +21,7 @@ import { service } from '..';
 import { Store } from 'jetlinks-store';
 import { useParams } from 'umi';
 import Doc from '@/pages/Northbound/DuerOS/Detail/Doc';
+import _ from 'lodash';
 
 const Save = () => {
   const SchemaField = createSchemaField({
@@ -37,6 +38,20 @@ const Save = () => {
 
   const { id } = useParams<{ id: string }>();
 
+  const findProductMetadata = (_id: string) => {
+    if (!_id) return;
+    const _productList = Store.get('product-list');
+    const _product = _productList?.find((item: any) => item.id === _id);
+    return _product?.metadata && JSON.parse(_product.metadata || '{}');
+  };
+
+  const findApplianceType = (_id: string) => {
+    if (!_id) return;
+    const _productTypes = Store.get('product-types');
+    console.log(_productTypes, 'tt');
+    return _productTypes?.find((item: any) => item.id === _id);
+  };
+
   const getProduct = () =>
     service.getProduct().then((resp) => {
       Store.set('product-list', resp.result);
@@ -49,6 +64,92 @@ const Save = () => {
       return resp.result;
     });
 
+  const form = useMemo(
+    () =>
+      createForm({
+        validateFirst: true,
+        effects() {
+          onFormInit(async (form1) => {
+            await getTypes();
+            await getProduct();
+            if (id === ':id') return;
+            const resp = await service.detail(id);
+            /// 单独处理一下applianceType
+            const _data = resp.result;
+            if (_data) {
+              _data.applianceType = _data?.applianceType?.value;
+            }
+            form1.setInitialValues(_data);
+          });
+          onFieldReact('actionMappings.*.layout.action', (field) => {
+            const productType = field.query('applianceType').value();
+            (field as Field).setDataSource(findApplianceType(productType)?.actions);
+          });
+          onFieldReact('actionMappings.*.layout.command.message.properties', (field) => {
+            const product = field.query('id').value();
+            (field as Field).setDataSource(findProductMetadata(product)?.properties);
+          });
+          onFieldReact('actionMappings.*.layout.command.message.functionId', (field) => {
+            const product = field.query('id').value();
+            (field as Field).setDataSource(findProductMetadata(product)?.functions);
+          });
+          onFieldValueChange(
+            'actionMappings.*.layout.command.message.functionId',
+            (field, form1) => {
+              const functionId = field.value;
+              if (!functionId) return;
+              const product = field.query('id').value();
+              const _functionList = findProductMetadata(product)?.functions;
+              const _function =
+                _functionList && _functionList.find((item: any) => item.id === functionId);
+              form1.setFieldState(field.query('.function'), (state) => {
+                state.value = _function?.inputs.map((item: any) => ({
+                  ...item,
+                  valueType: item?.valueType?.type,
+                }));
+              });
+            },
+          );
+          onFieldReact('propertyMappings.*.layout.source', (field) => {
+            const productType = field.query('applianceType').value();
+            (field as Field).setDataSource(findApplianceType(productType)?.properties);
+          });
+          onFieldReact('propertyMappings.*.layout.target', (field) => {
+            const product = field.query('id').value();
+            (field as Field).setDataSource(findProductMetadata(product)?.properties);
+          });
+        },
+      }),
+    [],
+  );
+
+  const getProductProperties = (f: Field) => {
+    const items =
+      form
+        .getValuesIn('propertyMappings')
+        ?.map((i: { target: string[] }) => i.target?.map((j) => j)) || [];
+    const checked = _.flatMap(items);
+    const index = checked.findIndex((i) => i === f.value);
+    checked.splice(index, 1);
+    const _id = form.getValuesIn('id');
+    const sourceList = findProductMetadata(_id)?.properties;
+    const list = sourceList?.filter((i: { id: string }) => !checked.includes(i.id));
+    return new Promise((resolve) => resolve(list));
+  };
+
+  const getDuerOSProperties = (f: Field) => {
+    const items =
+      form.getValuesIn('propertyMappings')?.map((i: { source: string }) => i.source) || [];
+    const checked = [...items];
+    const index = checked.findIndex((i) => i === f.value);
+    checked.splice(index, 1);
+    const _productType = form.getValuesIn('applianceType');
+    const targetList = findApplianceType(_productType?.value)?.properties;
+    console.log(targetList, 'list', _productType);
+    const list = targetList?.filter((i: { id: string }) => !checked.includes(i.id));
+    return new Promise((resolve) => resolve(list));
+  };
+
   const schema: ISchema = {
     type: 'object',
     properties: {
@@ -398,6 +499,7 @@ const Save = () => {
                       value: 'id',
                     },
                   },
+                  'x-reactions': ['{{useAsyncDataSource(getDuerOSProperties)}}'],
                 },
                 target: {
                   title: '平台属性',
@@ -414,6 +516,7 @@ const Save = () => {
                     },
                     mode: 'tags',
                   },
+                  'x-reactions': ['{{useAsyncDataSource(getProductProperties)}}'],
                 },
               },
             },
@@ -434,71 +537,6 @@ const Save = () => {
     },
   };
 
-  const findProductMetadata = (_id: string) => {
-    if (!_id) return;
-    const _productList = Store.get('product-list');
-    const _product = _productList?.find((item: any) => item.id === _id);
-    return _product?.metadata && JSON.parse(_product.metadata || '{}');
-  };
-
-  const findapplianceType = (_id: string) => {
-    if (!_id) return;
-    const _productTypes = Store.get('product-types');
-    return _productTypes?.find((item: any) => item.id === _id);
-  };
-  const form = useMemo(
-    () =>
-      createForm({
-        validateFirst: true,
-        effects() {
-          onFormInit(async (form1) => {
-            await getTypes();
-            await getProduct();
-            const resp = await service.detail(id);
-            form1.setInitialValues(resp.result);
-          });
-          onFieldReact('actionMappings.*.layout.action', (field) => {
-            const productType = field.query('applianceType').value();
-            (field as Field).setDataSource(findapplianceType(productType)?.actions);
-          });
-          onFieldReact('actionMappings.*.layout.command.message.properties', (field) => {
-            const product = field.query('id').value();
-            (field as Field).setDataSource(findProductMetadata(product)?.properties);
-          });
-          onFieldReact('actionMappings.*.layout.command.message.functionId', (field) => {
-            const product = field.query('id').value();
-            (field as Field).setDataSource(findProductMetadata(product)?.functions);
-          });
-          onFieldValueChange(
-            'actionMappings.*.layout.command.message.functionId',
-            (field, form1) => {
-              const functionId = field.value;
-              if (!functionId) return;
-              const product = field.query('id').value();
-              const _functionList = findProductMetadata(product)?.functions;
-              const _function =
-                _functionList && _functionList.find((item: any) => item.id === functionId);
-              form1.setFieldState(field.query('.function'), (state) => {
-                state.value = _function?.inputs.map((item: any) => ({
-                  ...item,
-                  valueType: item?.valueType?.type,
-                }));
-              });
-            },
-          );
-          onFieldReact('propertyMappings.*.layout.source', (field) => {
-            const productType = field.query('applianceType').value();
-            (field as Field).setDataSource(findapplianceType(productType)?.properties);
-          });
-          onFieldReact('propertyMappings.*.layout.target', (field) => {
-            const product = field.query('id').value();
-            (field as Field).setDataSource(findProductMetadata(product)?.properties);
-          });
-        },
-      }),
-    [],
-  );
-
   const handleSave = async () => {
     const data: any = await form.submit();
     const productName = Store.get('product-list')?.find((item: any) => item.id === data.id)?.name;
@@ -512,7 +550,16 @@ const Save = () => {
         <Row>
           <Col span={12}>
             <Form layout="vertical" form={form}>
-              <SchemaField schema={schema} scope={{ useAsyncDataSource, getTypes, getProduct }} />
+              <SchemaField
+                schema={schema}
+                scope={{
+                  useAsyncDataSource,
+                  getTypes,
+                  getProduct,
+                  getProductProperties,
+                  getDuerOSProperties,
+                }}
+              />
               <FormButtonGroup.Sticky>
                 <FormButtonGroup.FormItem>
                   <PermissionButton isPermission={true} type="primary" onClick={handleSave}>

+ 22 - 3
src/pages/Northbound/DuerOS/index.tsx

@@ -3,13 +3,18 @@ import SearchComponent from '@/components/SearchComponent';
 import { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { PermissionButton, ProTableCard } from '@/components';
-import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
+import {
+  DeleteOutlined,
+  EditOutlined,
+  ExclamationCircleFilled,
+  PlusOutlined,
+} from '@ant-design/icons';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { message, Space } from 'antd';
 import { DuerOSItem } from '@/pages/Northbound/DuerOS/types';
 import DuerOSCard from '@/components/ProTableCard/CardItems/duerOs';
 import { history } from '@@/core/history';
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import { getMenuPathByCode, getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import Service from './service';
 
 export const service = new Service('dueros/product');
@@ -90,6 +95,7 @@ export default () => {
         defaultMessage: '设备类型',
       }),
       dataIndex: 'applianceType',
+      renderText: (data) => data.text,
     },
     {
       title: intl.formatMessage({
@@ -127,6 +133,19 @@ export default () => {
           setSearchParams(data);
         }}
       />
+      <div style={{ backgroundColor: 'white', width: '100%', height: 60, padding: 20 }}>
+        <div
+          style={{
+            padding: 10,
+            width: '100%',
+            color: 'rgba(0, 0, 0, 0.55)',
+            backgroundColor: '#f6f6f6',
+          }}
+        >
+          <ExclamationCircleFilled style={{ marginRight: 10 }} />
+          将平台产品通过API的方式同步DuerOS平台
+        </div>
+      </div>
       <ProTableCard<DuerOSItem>
         rowKey="id"
         search={false}
@@ -143,7 +162,7 @@ export default () => {
             <PermissionButton
               isPermission={true}
               onClick={() => {
-                history.push(getMenuPathByParams(MENUS_CODE['Northbound/DuerOS/Detail']));
+                history.push(getMenuPathByCode(MENUS_CODE['Northbound/DuerOS/Detail']));
               }}
               key="button"
               icon={<PlusOutlined />}

+ 4 - 1
src/pages/Northbound/DuerOS/types.d.ts

@@ -17,7 +17,10 @@ type DuerOSItem = {
   version: number;
   manufacturerName: string;
   autoReportProperty: boolean;
-  applianceType: string;
+  applianceType: {
+    text: string;
+    value: string;
+  };
   actionMappings: ActionMapping[];
   propertyMappings: PropertyMapping[];
 } & BaseItem;

+ 4 - 1
src/pages/cloud/DuerOS/typings.d.ts

@@ -31,7 +31,10 @@ type DuerOSItem = {
   version: number;
   manufacturerName: string;
   autoReportProperty: boolean;
-  applianceType: string;
+  applianceType: {
+    text: string;
+    value: string;
+  };
   actionMappings: ActionMapping[];
   propertyMappings: PropertyMapping[];
 } & BaseItem;

+ 5 - 5
src/pages/rule-engine/Scene/Save/action/VariableItems/builtIn.tsx

@@ -17,6 +17,7 @@ interface BuiltInProps {
   type?: string;
   notifyType?: string;
   onChange?: (value: ChangeType) => void;
+  trigger?: any;
 }
 
 export default (props: BuiltInProps) => {
@@ -35,12 +36,11 @@ export default (props: BuiltInProps) => {
   });
 
   useEffect(() => {
-    if (source === 'upper') {
-      getBuiltInList({
-        trigger: { type: props.type },
-      });
+    console.log(props.trigger);
+    if (source === 'upper' && props.trigger) {
+      getBuiltInList({ ...props.trigger });
     }
-  }, [source, props.type]);
+  }, [source, props.trigger]);
 
   useEffect(() => {
     setSource(props.value?.source);

+ 29 - 10
src/pages/rule-engine/Scene/Save/action/VariableItems/user.tsx

@@ -54,7 +54,11 @@ export default (props: UserProps) => {
     };
     const resp1 = await queryPlatformUsers();
     if (resp1.status === 200) {
-      _userList.platform = resp1.result.map((item: any) => ({ label: item.name, value: item.id }));
+      _userList.platform = resp1.result.map((item: any) => ({
+        label: item.name,
+        value: item.id,
+        username: item.username,
+      }));
     }
 
     const resp2 = await queryRelationUsers();
@@ -62,6 +66,7 @@ export default (props: UserProps) => {
       _userList.relation = resp2.result.map((item: any) => ({
         label: item.name,
         value: item.relation,
+        username: '',
       }));
     }
 
@@ -172,7 +177,7 @@ export default (props: UserProps) => {
   };
 
   const filterOption = (input: string, option: any) => {
-    return option.children ? option.children.toLowerCase().includes(input.toLowerCase()) : false;
+    return option.label ? option.label.toLowerCase().includes(input.toLowerCase()) : false;
   };
 
   const userSelect =
@@ -187,12 +192,16 @@ export default (props: UserProps) => {
         placeholder={'请选择收信人'}
         listHeight={200}
         filterOption={filterOption}
+        optionLabelProp="label"
       >
         {userList.platform.length ? (
           <Select.OptGroup label={'平台用户'}>
             {userList.platform.map((item: any) => (
-              <Select.Option value={item.value} isRelation={false}>
-                {item.label}
+              <Select.Option value={item.value} isRelation={false} label={item.label}>
+                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                  <span>{item.label}</span>
+                  <span style={{ color: '#cfcfcf' }}>{item.username}</span>
+                </div>
               </Select.Option>
             ))}
           </Select.OptGroup>
@@ -200,8 +209,11 @@ export default (props: UserProps) => {
         {userList.relation.length ? (
           <Select.OptGroup label={'关系用户'}>
             {userList.relation.map((item: any) => (
-              <Select.Option value={item.value} isRelation={true}>
-                {item.label}
+              <Select.Option value={item.value} isRelation={false} label={item.label}>
+                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                  <span>{item.label}</span>
+                  <span style={{ color: '#cfcfcf' }}>{item.username}</span>
+                </div>
               </Select.Option>
             ))}
           </Select.OptGroup>
@@ -237,12 +249,16 @@ export default (props: UserProps) => {
         placeholder={'请选择收信人'}
         listHeight={200}
         filterOption={filterOption}
+        optionLabelProp="label"
       >
         {userList.platform.length ? (
           <Select.OptGroup label={'平台用户'}>
             {userList.platform.map((item: any) => (
-              <Select.Option value={item.value} isRelation={false}>
-                {item.label}
+              <Select.Option value={item.value} isRelation={false} label={item.label}>
+                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                  <span>{item.label}</span>
+                  <span style={{ color: '#cfcfcf' }}>{item.username}</span>
+                </div>
               </Select.Option>
             ))}
           </Select.OptGroup>
@@ -250,8 +266,11 @@ export default (props: UserProps) => {
         {userList.relation.length ? (
           <Select.OptGroup label={'关系用户'}>
             {userList.relation.map((item: any) => (
-              <Select.Option value={item.value} isRelation={true}>
-                {item.label}
+              <Select.Option value={item.value} isRelation={true} label={item.label}>
+                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                  <span>{item.label}</span>
+                  <span style={{ color: '#cfcfcf' }}>{item.username}</span>
+                </div>
               </Select.Option>
             ))}
           </Select.OptGroup>

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

@@ -25,6 +25,7 @@ interface ActionProps {
   triggerType: string;
   onRemove: () => void;
   actionItemData?: any;
+  trigger?: any;
 }
 
 export default observer((props: ActionProps) => {
@@ -265,6 +266,7 @@ export default observer((props: ActionProps) => {
           form={props.form}
           template={templateData}
           name={props.name}
+          trigger={props.trigger}
           notifyType={notifyType}
           triggerType={props.triggerType}
           configId={configId}

+ 0 - 1
src/pages/rule-engine/Scene/Save/action/device/AllDevice.tsx

@@ -10,7 +10,6 @@ interface AllDeviceProps {
 
 export default (props: AllDeviceProps) => {
   useEffect(() => {
-    console.log(props.productId);
     queryAllDevice({
       terms: [{ column: 'productId', value: props.productId }],
       paging: false,

+ 6 - 1
src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx

@@ -124,7 +124,12 @@ export default (props: WritePropertyProps) => {
       <Col span={4}>
         <Select
           value={propertiesKey}
-          options={props.properties}
+          options={props.properties.filter((item) => {
+            if (item.expands && item.expands.type) {
+              return item.expands.type.includes('write');
+            }
+            return false;
+          })}
           fieldNames={{ label: 'name', value: 'id' }}
           style={{ width: '100%' }}
           onSelect={(key: any) => {

+ 14 - 6
src/pages/rule-engine/Scene/Save/action/device/deviceModal.tsx

@@ -1,4 +1,4 @@
-import { Badge, Input, message, Modal } from 'antd';
+import { Badge, Button, Input, message, Modal } from 'antd';
 import { useEffect, useRef, useState } from 'react';
 import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { DeviceItem } from '@/pages/system/Department/typings';
@@ -38,7 +38,6 @@ export default (props: DeviceModelProps) => {
   const [selectKeys, setSelectKeys] = useState<ChangeValueType[]>(props.value || []);
   const [searchParam, setSearchParam] = useState({});
   const [value, setValue] = useState<ChangeValueType[]>(props.value || []);
-  const oldAllSelect = useRef<any[]>([]);
 
   useEffect(() => {
     setValue(props.value || []);
@@ -159,21 +158,30 @@ export default (props: DeviceModelProps) => {
                 }
                 setSelectKeys(newSelectKeys);
               },
-              onSelectAll: (selected, selectedRows) => {
+              onSelectAll: (selected, _, changeRows) => {
                 let newSelectKeys = [...selectKeys];
                 if (selected) {
-                  oldAllSelect.current = selectedRows;
-                  selectedRows.forEach((item) => {
+                  changeRows.forEach((item) => {
                     newSelectKeys.push({ name: item.name, value: item.id });
                   });
                 } else {
                   newSelectKeys = newSelectKeys.filter((a) => {
-                    return !oldAllSelect.current.some((b) => b.id === a.value);
+                    return !changeRows.some((b) => b.id === a.value);
                   });
                 }
                 setSelectKeys(newSelectKeys);
               },
             }}
+            tableAlertOptionRender={() => (
+              <Button
+                type={'link'}
+                onClick={() => {
+                  setSelectKeys([]);
+                }}
+              >
+                取消选择
+              </Button>
+            )}
             request={(params) => queryDevice(params)}
             params={searchParam}
           ></ProTable>

+ 1 - 5
src/pages/rule-engine/Scene/Save/action/device/functionCall.tsx

@@ -24,10 +24,6 @@ export default (props: FunctionCallProps) => {
 
   useEffect(() => {
     setEditableRowKeys(props.functionData.map((d) => d.id));
-    console.log('functionData', props.functionData);
-    formRef.current?.setFieldsValue({
-      table: props.functionData,
-    });
   }, [props.functionData]);
 
   useEffect(() => {
@@ -45,7 +41,7 @@ export default (props: FunctionCallProps) => {
         }),
       });
     }
-  }, []);
+  }, [props.value, props.functionData]);
 
   const getItemNode = (record: any) => {
     const type = record.type;

+ 6 - 2
src/pages/rule-engine/Scene/Save/action/device/index.tsx

@@ -185,7 +185,11 @@ export default (props: DeviceProps) => {
             initialValue={props.value ? props.value.selector : SourceEnum.fixed}
             {...props.restField}
           >
-            <Select options={sourceList} style={{ width: 120 }} />
+            <Select
+              options={sourceList}
+              style={{ width: 120 }}
+              onSelect={(key: string) => setSelector(key)}
+            />
           </Form.Item>
           {selector === SourceEnum.fixed && (
             <Form.Item
@@ -211,7 +215,7 @@ export default (props: DeviceProps) => {
               {...props.restField}
               rules={[{ required: true, message: '请选择关系人' }]}
             >
-              <Select style={{ width: 300 }} />
+              <Select style={{ width: '100%' }} placeholder={'请选择关系'} />
             </Form.Item>
           )}
         </ItemGroup>

+ 6 - 1
src/pages/rule-engine/Scene/Save/action/device/readProperty.tsx

@@ -10,7 +10,12 @@ export default (props: ReadPropertyProps) => {
   return (
     <Select
       value={props.value ? props.value[0] : undefined}
-      options={props.properties}
+      options={props.properties.filter((item) => {
+        if (item.expands && item.expands.type) {
+          return item.expands.type.includes('read');
+        }
+        return false;
+      })}
       fieldNames={{ label: 'name', value: 'id' }}
       style={{ width: '100%' }}
       onSelect={(key: any) => {

+ 1 - 1
src/pages/rule-engine/Scene/Save/action/device/tagModal.tsx

@@ -151,7 +151,7 @@ export default (props: TagModalProps) => {
     <>
       <Modal
         visible={visible}
-        title={'设备'}
+        title={'标签'}
         width={660}
         onOk={() => {
           const newValue = tagList

+ 6 - 3
src/pages/rule-engine/Scene/Save/action/messageContent.tsx

@@ -16,6 +16,7 @@ interface MessageContentProps {
   notifyType: string;
   triggerType: string;
   configId: string;
+  trigger?: any;
 }
 
 const rowGutter = 12;
@@ -44,8 +45,10 @@ export default (props: MessageContentProps) => {
         } else {
           rules.push({
             validator: async (_: any, value: any) => {
-              if (type === 'file' && !value) {
-                return Promise.reject(new Error('请输入' + item.name));
+              if (type === 'file' || type === 'link') {
+                if (!value) {
+                  return Promise.reject(new Error('请输入' + item.name));
+                }
               } else {
                 if (!value || !value.value) {
                   if (['date', 'org'].includes(type)) {
@@ -146,7 +149,7 @@ export default (props: MessageContentProps) => {
                       ) : type === 'link' ? (
                         <Input placeholder={'请输入' + item.name} />
                       ) : (
-                        <BuiltIn type={props.triggerType} data={item} />
+                        <BuiltIn type={props.triggerType} trigger={props.trigger} data={item} />
                       )}
                     </Form.Item>
                   </Col>

+ 8 - 2
src/pages/rule-engine/Scene/Save/action/service.ts

@@ -50,7 +50,10 @@ export const queryRelationUsers = () =>
 
 // 钉钉用户
 export const queryDingTalkUsers = (id: string) =>
-  request(`${SystemConst.API_BASE}/notifier/dingtalk/corp/${id}/users`, { method: 'GET' });
+  request(
+    `${SystemConst.API_BASE}/notifier/dingtalk/corp/${id}/users?sorts[0].name='name'&sorts[0].order=asc`,
+    { method: 'GET' },
+  );
 
 // 钉钉部门
 export const queryDingTalkDepartments = (id: string) =>
@@ -60,7 +63,10 @@ export const queryDingTalkDepartments = (id: string) =>
 
 // 微信用户
 export const queryWechatUsers = (id: string) =>
-  request(`${SystemConst.API_BASE}/notifier/wechat/corp/${id}/users`, { method: 'GET' });
+  request(
+    `${SystemConst.API_BASE}/notifier/wechat/corp/${id}/users?sorts[0].name='name'&sorts[0].order=asc`,
+    { method: 'GET' },
+  );
 
 // 微信部门
 export const queryWechatDepartments = (id: string) =>

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

@@ -193,7 +193,7 @@ export default (props: TimingTrigger) => {
                   value={
                     data.period?.from
                       ? [moment(data.period?.from, 'HH:mm:ss'), moment(data.period?.to, 'hh:mm:ss')]
-                      : undefined
+                      : [moment(new Date(), 'HH:mm:ss'), moment(new Date(), 'HH:mm:ss')]
                   }
                   onChange={(_, dateString) => {
                     onChange({
@@ -208,8 +208,8 @@ export default (props: TimingTrigger) => {
                 />
               ) : (
                 <TimePicker
-                  format={'hh:mm:ss'}
-                  value={data.once?.time ? moment(data.once?.time, 'hh:mm:ss') : undefined}
+                  format={'HH:mm:ss'}
+                  value={moment(data.once?.time || new Date(), 'HH:mm:ss')}
                   onChange={(_, dateString) => {
                     onChange({
                       ...data,
@@ -233,7 +233,7 @@ export default (props: TimingTrigger) => {
                     addonAfter={TimeTypeAfter}
                     style={{ flex: 1 }}
                     min={0}
-                    max={9999}
+                    max={59}
                     onChange={(e) => {
                       onChange({
                         ...data,

+ 48 - 33
src/pages/rule-engine/Scene/Save/index.tsx

@@ -35,7 +35,7 @@ type ShakeLimitType = {
 
 const DefaultShakeLimit = {
   enabled: false,
-  alarmFirst: true,
+  alarmFirst: false,
 };
 
 export let FormModel = model<FormModelType>({});
@@ -59,6 +59,7 @@ export default () => {
 
   const [requestParams, setRequestParams] = useState<any>(undefined);
   const [triggerValue, setTriggerValue] = useState<any>([]);
+  const [actionParams, setActionParams] = useState<any>(undefined);
 
   const [actionsData, setActionsData] = useState<any[]>([]);
   const [isEdit, setIsEdit] = useState(false);
@@ -72,10 +73,11 @@ export default () => {
         FormModel = _data;
         form.setFieldsValue(_data);
         setParallel(_data.parallel);
-        setShakeLimit(_data.shakeLimit || DefaultShakeLimit);
 
         setTriggerValue({ trigger: _data.terms || [] });
-
+        if (_data.trigger?.shakeLimit) {
+          setShakeLimit(_data.trigger?.shakeLimit || DefaultShakeLimit);
+        }
         if (_data.trigger?.device) {
           setRequestParams({ trigger: _data.trigger });
         }
@@ -95,7 +97,7 @@ export default () => {
     }
   }, [location]);
 
-  const saveData = async () => {
+  const saveData = useCallback(async () => {
     const formData = await form.validateFields();
     let triggerData = undefined;
     // 获取触发条件数据
@@ -107,6 +109,12 @@ export default () => {
     }
     console.log('save', formData);
     if (formData) {
+      if (shakeLimit.enabled) {
+        formData.trigger = {
+          ...formData.trigger,
+          shakeLimit: shakeLimit,
+        };
+      }
       setLoading(true);
       const resp = formData.id ? await service.updateScene(formData) : await service.save(formData);
 
@@ -126,7 +134,7 @@ export default () => {
         message.error(resp.message);
       }
     }
-  };
+  }, [shakeLimit]);
 
   const AntiShake = (
     <Space>
@@ -136,34 +144,36 @@ export default () => {
         checkedChildren="开启防抖"
         unCheckedChildren="关闭防抖"
         onChange={(e) => {
-          setShakeLimit({
+          const newShake = {
             ...shakeLimit,
             enabled: e,
-          });
-          form.setFieldsValue({ shakeLimit });
+          };
+          setShakeLimit(newShake);
         }}
       />
       {shakeLimit.enabled && (
         <>
           <InputNumber
             value={shakeLimit.time}
+            min={0}
             onChange={(e: number) => {
-              setShakeLimit({
+              const newShake = {
                 ...shakeLimit,
                 time: e,
-              });
-              form.setFieldsValue({ shakeLimit });
+              };
+              setShakeLimit(newShake);
             }}
           />
           <span> 秒内发生 </span>
           <InputNumber
             value={shakeLimit.threshold}
+            min={0}
             onChange={(e: number) => {
-              setShakeLimit({
+              const newShake = {
                 ...shakeLimit,
                 threshold: e,
-              });
-              form.setFieldsValue({ shakeLimit });
+              };
+              setShakeLimit(newShake);
             }}
           />
           <span>次及以上时,处理</span>
@@ -175,12 +185,11 @@ export default () => {
             ]}
             optionType="button"
             onChange={(e) => {
-              console.log(e);
-              setShakeLimit({
+              const newShake = {
                 ...shakeLimit,
                 alarmFirst: e.target.value,
-              });
-              form.setFieldsValue({ shakeLimit });
+              };
+              setShakeLimit(newShake);
             }}
           ></Radio.Group>
         </>
@@ -199,16 +208,21 @@ export default () => {
           preserve={false}
           className={'scene-save'}
           onValuesChange={(changeValue, allValues) => {
-            if (changeValue.trigger && changeValue.trigger.device) {
-              if (
-                changeValue.trigger.device.selectorValues ||
-                (changeValue.trigger.device.operation &&
-                  changeValue.trigger.device.operation.operator)
-              ) {
-                setTriggerValue([]);
-                setRequestParams({ trigger: allValues.trigger });
+            if (changeValue.trigger) {
+              if (changeValue.trigger.device) {
+                if (
+                  changeValue.trigger.device.selectorValues ||
+                  (changeValue.trigger.device.operation &&
+                    changeValue.trigger.device.operation.operator)
+                ) {
+                  setTriggerValue([]);
+                  setRequestParams({ trigger: allValues.trigger });
+                }
+              } else if (['timer', 'manual'].includes(changeValue.trigger.type)) {
+                setActionParams({ trigger: allValues.trigger });
               }
             }
+
             if (allValues.actions) {
               setActionsData(allValues.actions);
             }
@@ -380,6 +394,7 @@ export default () => {
                         form={form}
                         restField={restField}
                         name={name}
+                        trigger={actionParams}
                         triggerType={triggerType}
                         onRemove={() => remove(name)}
                         actionItemData={actionsData.length && actionsData[name]}
@@ -402,13 +417,13 @@ export default () => {
           >
             <Input.TextArea showCount maxLength={200} placeholder={'请输入说明'} rows={4} />
           </Form.Item>
-          {triggerType === TriggerWayType.device &&
-          requestParams &&
-          requestParams.trigger?.device?.productId ? (
-            <Form.Item hidden name={'shakeLimit'} initialValue={DefaultShakeLimit}>
-              <Input />
-            </Form.Item>
-          ) : null}
+          {/*{triggerType === TriggerWayType.device &&*/}
+          {/*requestParams &&*/}
+          {/*requestParams.trigger?.device?.productId ? (*/}
+          {/*  <Form.Item hidden name={['trigger','shakeLimit']}>*/}
+          {/*    <Input />*/}
+          {/*  </Form.Item>*/}
+          {/*) : null}*/}
           <Form.Item hidden name={'id'}>
             <Input />
           </Form.Item>

+ 8 - 8
src/pages/rule-engine/Scene/Save/trigger/device.tsx

@@ -362,14 +362,14 @@ export default (props: TriggerProps) => {
                 }
                 return false;
               })}
-              maxTagCount={0}
-              maxTagPlaceholder={(values) => {
-                return (
-                  <div style={{ maxWidth: 'calc(100% - 8px)' }}>
-                    {values.map((item) => item.label).toString()}
-                  </div>
-                );
-              }}
+              maxTagCount={'responsive'}
+              // maxTagPlaceholder={(values) => {
+              //   return (
+              //     <div style={{ maxWidth: 'calc(100% - 8px)' }}>
+              //       {values.map((item) => item.label).toString()}
+              //     </div>
+              //   );
+              // }}
               placeholder={'请选择属性'}
               style={{ width: '100%' }}
               fieldNames={{ label: 'name', value: 'id' }}

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

@@ -98,6 +98,29 @@ export default observer((props: TriggerProps) => {
     [selector],
   );
 
+  useEffect(() => {
+    if (FormModel.trigger?.device?.operation?.functionId && functions.length) {
+      const fcItem: any = functions.find(
+        (_fcItem: any) => _fcItem.id === FormModel.trigger?.device?.operation?.functionId,
+      );
+      if (fcItem) {
+        const _properties = fcItem.valueType ? fcItem.valueType.properties : fcItem.inputs;
+        const array = [];
+        for (const datum of _properties) {
+          array.push({
+            id: datum.id,
+            name: datum.name,
+            type: datum.valueType ? datum.valueType.type : '-',
+            format: datum.valueType ? datum.valueType.format : undefined,
+            options: datum.valueType ? datum.valueType.elements : undefined,
+            value: undefined,
+          });
+        }
+        setFunctionItem(array);
+      }
+    }
+  }, [functions, FormModel]);
+
   const getProducts = async () => {
     const resp = await getProductList({ paging: false });
     if (resp && resp.status === 200) {
@@ -108,7 +131,7 @@ export default observer((props: TriggerProps) => {
         );
 
         if (productItem) {
-          productIdChange(FormModel.trigger!.device.productId, productItem.metadata);
+          await productIdChange(FormModel.trigger!.device.productId, productItem.metadata);
         }
       }
     }
@@ -133,10 +156,6 @@ export default observer((props: TriggerProps) => {
     }
   }, [props.value]);
 
-  useEffect(() => {
-    console.log('FormModel-device', FormModel);
-  }, [FormModel]);
-
   return (
     <div className={classNames(props.className)}>
       <Row gutter={24}>
@@ -186,6 +205,9 @@ export default observer((props: TriggerProps) => {
                       { label: '按部门', value: 'org' },
                     ]}
                     // fieldNames={{ label: 'name', value: 'id' }}
+                    onSelect={(key: string) => {
+                      setSelector(key);
+                    }}
                     style={{ width: 120 }}
                   />
                 </Form.Item>
@@ -306,21 +328,6 @@ export default observer((props: TriggerProps) => {
                   }}
                   style={{ width: '100%' }}
                   placeholder={'请选择功能'}
-                  onSelect={(_: any, data: any) => {
-                    const _properties = data.valueType ? data.valueType.properties : data.inputs;
-                    const array = [];
-                    for (const datum of _properties) {
-                      array.push({
-                        id: datum.id,
-                        name: datum.name,
-                        type: datum.valueType ? datum.valueType.type : '-',
-                        format: datum.valueType ? datum.valueType.format : undefined,
-                        options: datum.valueType ? datum.valueType.elements : undefined,
-                        value: undefined,
-                      });
-                    }
-                    setFunctionItem(array);
-                  }}
                   filterOption={(input: string, option: any) =>
                     option.name.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }
@@ -347,8 +354,8 @@ export default observer((props: TriggerProps) => {
             >
               <Operation
                 propertiesList={properties.filter((item) => {
-                  if (item.expands) {
-                    return item.expands.type ? item.expands.type.includes('write') : false;
+                  if (item.expands && item.expands.type) {
+                    return item.expands.type.includes('write');
                   }
                   return false;
                 })}
@@ -368,19 +375,12 @@ export default observer((props: TriggerProps) => {
               <Select
                 mode={'multiple'}
                 options={properties.filter((item) => {
-                  if (item.expands) {
-                    return item.expands.type ? item.expands.type.includes('read') : false;
+                  if (item.expands && item.expands.type) {
+                    return item.expands.type.includes('read');
                   }
                   return false;
                 })}
-                maxTagCount={0}
-                maxTagPlaceholder={(values) => {
-                  return (
-                    <div style={{ maxWidth: 'calc(100% - 8px)' }}>
-                      {values.map((item) => item.label).toString()}
-                    </div>
-                  );
-                }}
+                maxTagCount={'responsive'}
                 placeholder={'请选择属性'}
                 style={{ width: '100%' }}
                 fieldNames={{ label: 'name', value: 'id' }}

+ 1 - 0
src/pages/rule-engine/Scene/Save/trigger/operation.tsx

@@ -76,6 +76,7 @@ export default (props: OperatorProps) => {
             label: 'name',
             value: 'id',
           }}
+          maxTagCount={'responsive'}
           style={{ width: '100%' }}
           placeholder={'请选择属性'}
           onSelect={(id: any) => {

+ 57 - 8
src/pages/system/Platforms/Api/basePage.tsx

@@ -1,27 +1,76 @@
 import { Button, Table } from 'antd';
+import { useCallback, useEffect, useState } from 'react';
+import { service } from '../index';
 
-export default () => {
-  // const [selectKeys, setSelectKeys] = useState<string[]>([])
+interface TableProps {
+  parentId: string;
+  onJump: (id: string) => void;
+}
 
-  const save = () => {};
+export default (props: TableProps) => {
+  const [selectKeys, setSelectKeys] = useState<string[]>([]);
+  const [dataSource, setDataSource] = useState([]);
+
+  const queryData = async (pId: string) => {
+    const resp: any = service.queryRoleList(pId);
+    if (resp.status === 200) {
+      setDataSource(resp.result);
+    }
+  };
+
+  useEffect(() => {
+    queryData(props.parentId);
+  }, [props.parentId]);
+
+  const save = useCallback(async () => {}, [selectKeys]);
 
   return (
-    <div>
-      <Table
+    <div className={'platforms-api-table'}>
+      <Table<any>
         columns={[
           {
             title: 'API',
             dataIndex: 'name',
+            render: (text: string, record) => {
+              return (
+                <Button
+                  type={'link'}
+                  style={{ padding: 0 }}
+                  onClick={() => {
+                    props.onJump(record.id);
+                  }}
+                >
+                  {text}
+                </Button>
+              );
+            },
           },
           {
             title: '说明',
             dataIndex: '',
           },
         ]}
+        dataSource={dataSource}
+        rowSelection={{
+          selectedRowKeys: selectKeys,
+          onSelect: (record, selected) => {
+            if (selected) {
+              const newArr = [...selectKeys, record];
+              setSelectKeys(newArr);
+            } else {
+              setSelectKeys([...selectKeys.filter((key) => key !== record)]);
+            }
+          },
+          onSelectAll: (_, selectedRows) => {
+            setSelectKeys(selectedRows);
+          },
+        }}
       />
-      <Button type={'primary'} onClick={save}>
-        保存
-      </Button>
+      <div className={'platforms-api-save'}>
+        <Button type={'primary'} onClick={save}>
+          保存
+        </Button>
+      </div>
     </div>
   );
 };

+ 20 - 0
src/pages/system/Platforms/Api/index.less

@@ -0,0 +1,20 @@
+.platforms-api {
+  display: flex;
+  padding: 24px;
+  background-color: #fff;
+
+  .platforms-api-tree {
+    width: 320px;
+  }
+
+  .platforms-api-table {
+    display: flex;
+    flex-direction: column;
+    flex-grow: 1;
+    width: 0;
+
+    .platforms-api-save {
+      margin-top: 12px;
+    }
+  }
+}

+ 15 - 5
src/pages/system/Platforms/Api/index.tsx

@@ -1,15 +1,25 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { Tree } from 'antd';
 import Table from './basePage';
+import Tree from './leftTree';
+import './index.less';
+import { useState } from 'react';
 
 export default () => {
+  const [jumpId, setJumpId] = useState('');
+  const [parentId, setParentId] = useState('');
+
   return (
     <PageContainer>
-      <div>
-        <div>
-          <Tree />
+      <div className={'platforms-api'}>
+        <div className={'platforms-api-tree'}>
+          <Tree
+            onSelect={(id) => {
+              setJumpId('');
+              setParentId(id);
+            }}
+          />
         </div>
-        <Table />
+        {!jumpId ? <Table parentId={parentId} onJump={setJumpId} /> : <></>}
       </div>
     </PageContainer>
   );

+ 119 - 0
src/pages/system/Platforms/Api/leftTree.tsx

@@ -0,0 +1,119 @@
+import { Tree } from 'antd';
+import React, { useState } from 'react';
+import { queryChannel } from '@/pages/media/SplitScreen/service';
+
+type LeftTreeType = {
+  onSelect: (id: string) => void;
+};
+
+interface DataNode {
+  name: string;
+  id: string;
+  isLeaf?: boolean;
+  icon?: React.ReactNode;
+  children?: DataNode[];
+}
+
+export default (props: LeftTreeType) => {
+  const [treeData, setTreeData] = useState<DataNode[]>([]);
+
+  /**
+   * 是否为子节点
+   * @param node
+   */
+  const isLeaf = (node: DataNode): boolean => {
+    if (node.children) {
+      return false;
+    }
+    return true;
+  };
+
+  const updateTreeData = (list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] => {
+    return list.map((node) => {
+      if (node.id === key) {
+        return {
+          ...node,
+          children: node.children ? [...node.children, ...children] : children,
+        };
+      }
+
+      if (node.children) {
+        return {
+          ...node,
+          children: updateTreeData(node.children, key, children),
+        };
+      }
+      return node;
+    });
+  };
+
+  const getChildren = (key: React.Key, params: any): Promise<any> => {
+    return new Promise(async (resolve) => {
+      const resp = await queryChannel(params);
+      if (resp.status === 200) {
+        const { total, pageIndex, pageSize } = resp.result;
+        setTreeData((origin) => {
+          const data = updateTreeData(
+            origin,
+            key,
+            resp.result.data.map((item: DataNode) => ({
+              ...item,
+              isLeaf: isLeaf(item),
+            })),
+          );
+
+          if (total > (pageIndex + 1) * pageSize) {
+            setTimeout(() => {
+              getChildren(key, {
+                ...params,
+                pageIndex: params.pageIndex + 1,
+              });
+            }, 50);
+          }
+
+          return data;
+        });
+        resolve(resp.result);
+      }
+    });
+  };
+
+  const onLoadData = ({ key, children }: any): Promise<void> => {
+    return new Promise(async (resolve) => {
+      if (children) {
+        resolve();
+        return;
+      }
+      await getChildren(key, {
+        pageIndex: 0,
+        pageSize: 100,
+        terms: [
+          {
+            column: 'deviceId',
+            value: key,
+          },
+        ],
+      });
+      resolve();
+    });
+  };
+
+  return (
+    <Tree
+      showIcon
+      showLine={{ showLeafIcon: false }}
+      height={550}
+      fieldNames={{
+        title: 'name',
+        key: 'id',
+      }}
+      onSelect={(_, { node }: any) => {
+        if (props.onSelect && node.isLeaf) {
+          props.onSelect(node.id);
+        }
+      }}
+      loadData={onLoadData}
+      treeData={treeData}
+    />
+  );
+};

+ 3 - 3
src/pages/system/Platforms/index.tsx

@@ -12,7 +12,7 @@ import PasswordModal from './password';
 import Service from './service';
 import { message } from 'antd';
 
-export const service = new Service('platforms');
+export const service = new Service('api-client');
 
 export default () => {
   const actionRef = useRef<ActionType>();
@@ -37,11 +37,11 @@ export default () => {
       title: '名称',
     },
     {
-      dataIndex: 'accessName',
+      dataIndex: 'username',
       title: '用户名',
     },
     {
-      dataIndex: 'role',
+      dataIndex: 'roleIdList',
       title: '角色',
     },
     {

+ 6 - 9
src/pages/system/Platforms/save.tsx

@@ -23,6 +23,7 @@ import usePermissions from '@/hooks/permission';
 import { action } from '@formily/reactive';
 import { Response } from '@/utils/typings';
 import { service } from '@/pages/system/Platforms/index';
+import { randomString } from '@/utils/util';
 
 interface SaveProps {
   visible: boolean;
@@ -76,7 +77,7 @@ export default (props: SaveProps) => {
     () =>
       createForm({
         validateFirst: true,
-        initialValues: props.data || { oath2: true },
+        initialValues: props.data || { oath2: true, id: randomString() },
       }),
     [props.data],
   );
@@ -116,7 +117,7 @@ export default (props: SaveProps) => {
               },
             ],
           },
-          clientId: {
+          id: {
             type: 'string',
             title: 'clientId',
             'x-decorator': 'FormItem',
@@ -152,7 +153,7 @@ export default (props: SaveProps) => {
               },
             ],
           },
-          accessName: {
+          username: {
             type: 'string',
             title: '用户名',
             'x-decorator': 'FormItem',
@@ -291,7 +292,7 @@ export default (props: SaveProps) => {
               },
             ],
           },
-          oath2: {
+          enableOAuth2: {
             type: 'boolean',
             title: '开启OAth2',
             required: true,
@@ -334,7 +335,7 @@ export default (props: SaveProps) => {
               },
             ],
           },
-          ipAddress: {
+          ipWhiteList: {
             type: 'string',
             title: 'IP白名单',
             'x-decorator': 'FormItem',
@@ -362,10 +363,6 @@ export default (props: SaveProps) => {
               maxLength: 200,
             },
           },
-          id: {
-            type: 'string',
-            'x-hidden': true,
-          },
         },
       },
     },

+ 3 - 0
src/utils/menu/index.ts

@@ -53,6 +53,9 @@ const extraRouteObj = {
   demo: {
     children: [{ code: 'AMap', name: '地图' }],
   },
+  'system/Platforms': {
+    children: [{ code: 'Api', name: '赋权' }],
+  },
 };
 //额外路由
 export const extraRouteArr = [