wzyyy 3 лет назад
Родитель
Сommit
c0e7ff51a2

+ 1 - 1
src/components/FTermArrayCards/index.tsx

@@ -101,7 +101,7 @@ export const FTermArrayCards: ComposedArrayCards = observer((props) => {
                     {
                       type: 'object',
                       properties: {
-                        termType: {
+                        type: {
                           'x-decorator': 'FormItem',
                           'x-component': 'Select',
                           'x-component-props': {

+ 0 - 1
src/components/FTermTypeSelect/index.tsx

@@ -21,7 +21,6 @@ const FTermTypeSelect = (props: Props) => {
         onChange={(value) => props.onChange(value)}
         value={props.value}
         style={{ width: '200px' }}
-        defaultValue={'and'}
         options={[
           { label: '并且', value: 'and' },
           { label: '或者', value: 'or' },

+ 18 - 3
src/components/ProTableCard/CardItems/AlarmConfig.tsx

@@ -1,10 +1,12 @@
 import React from 'react';
-import { TableCard } from '@/components';
+import { PermissionButton, TableCard } from '@/components';
 import '@/style/common.less';
 import '../index.less';
 import { StatusColorEnum } from '@/components/BadgeStatus';
 import { Tooltip } from 'antd';
 import { Store } from 'jetlinks-store';
+import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
+import { useHistory } from 'umi';
 
 export interface AlarmConfigProps extends ConfigurationItem {
   detail?: React.ReactNode;
@@ -15,6 +17,7 @@ export interface AlarmConfigProps extends ConfigurationItem {
 export const aliyunSms = require('/public/images/alarm/alarm-config.png');
 
 export default (props: AlarmConfigProps) => {
+  const history = useHistory();
   return (
     <TableCard
       actions={props.actions}
@@ -38,8 +41,20 @@ export default (props: AlarmConfigProps) => {
           <div className={'card-item-content'}>
             <div>
               <label>关联场景联动</label>
-              <div className={'ellipsis'}>
-                <Tooltip title={props?.sceneName || ''}>{props?.sceneName || ''}</Tooltip>
+              <div>
+                <PermissionButton
+                  type={'link'}
+                  isPermission={!!getMenuPathByCode(MENUS_CODE['rule-engine/Scene'])}
+                  style={{ padding: 0, height: 'auto' }}
+                  onClick={() => {
+                    const url = getMenuPathByCode('rule-engine/Scene/Save');
+                    history.push(`${url}?id=${props.sceneId}`);
+                  }}
+                >
+                  <div className={'ellipsis'}>
+                    <Tooltip title={props?.sceneName || ''}>{props?.sceneName || ''}</Tooltip>
+                  </div>
+                </PermissionButton>
               </div>
             </div>
             <div>

+ 1 - 0
src/pages/account/Center/bind/index.less

@@ -49,6 +49,7 @@
       }
     }
   }
+
   .btn {
     display: flex;
     justify-content: center;

+ 1 - 1
src/pages/demo/AMap/index.tsx

@@ -53,7 +53,7 @@ export default () => {
           height: 500,
           width: '100%',
         }}
-        onInstanceCreated={(_map) => {
+        onInstanceCreated={(_map: any) => {
           setMap(_map);
         }}
         events={{

+ 5 - 4
src/pages/device/Instance/Detail/MetadataLog/Property/index.tsx

@@ -56,7 +56,7 @@ const PropertyLog = (props: Props) => {
       render: (text: any) => <span>{text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : ''}</span>,
     },
     {
-      title: <span>{data.valueType?.type !== 'file' ? '自定义属性' : '文件内容'}</span>,
+      title: <span>{data.name}</span>,
       dataIndex: 'value',
       key: 'value',
       ellipsis: true,
@@ -110,7 +110,7 @@ const PropertyLog = (props: Props) => {
       render: (text: any) => <span>{text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : ''}</span>,
     },
     {
-      title: '位置',
+      title: <span>{data.name}</span>,
       dataIndex: 'value',
       key: 'value',
       render: (text: any, record: any) => (
@@ -252,6 +252,7 @@ const PropertyLog = (props: Props) => {
               pageSize: dataSource?.pageSize || 10,
               showSizeChanger: true,
               total: dataSource?.total || 0,
+              pageSizeOptions: [5, 10, 20, 50],
             }}
           />
         );
@@ -344,7 +345,7 @@ const PropertyLog = (props: Props) => {
     if (tab === 'table') {
       handleSearch(
         {
-          pageSize: 10,
+          pageSize: data.valueType?.type === 'file' ? 5 : 10,
           pageIndex: 0,
         },
         start,
@@ -496,7 +497,7 @@ const PropertyLog = (props: Props) => {
             if (key === 'table') {
               handleSearch(
                 {
-                  pageSize: 10,
+                  pageSize: data.valueType?.type === 'file' ? 5 : 10,
                   pageIndex: 0,
                 },
                 start,

+ 3 - 3
src/pages/device/Instance/Detail/Running/Property/EditProperty.tsx

@@ -7,12 +7,12 @@ import { useParams } from 'umi';
 import type { PropertyMetadata } from '@/pages/device/Product/typings';
 
 interface Props {
-  visible: boolean;
   data: Partial<PropertyMetadata>;
   onCancel: () => void;
 }
+
 const EditProperty = (props: Props) => {
-  const { visible, data } = props;
+  const { data } = props;
   const params = useParams<{ id: string }>();
 
   const SchemaField = createSchemaField({
@@ -47,7 +47,7 @@ const EditProperty = (props: Props) => {
     <Modal
       maskClosable={false}
       title="编辑"
-      visible={visible}
+      visible
       onOk={async () => {
         const values: any = await form.submit();
         if (!!values) {

+ 1 - 1
src/pages/device/Instance/Detail/Running/Property/FileComponent/index.less

@@ -2,7 +2,7 @@
   display: flex;
   align-items: center;
   width: 100%;
-  height: 60px;
+  max-height: 60px;
 
   .other {
     width: 100%;

+ 19 - 9
src/pages/device/Instance/Detail/Running/Property/PropertyCard.tsx

@@ -6,7 +6,7 @@ import {
 } from '@ant-design/icons';
 import { Card, message, Space, Spin, Tooltip } from 'antd';
 import type { PropertyMetadata } from '@/pages/device/Product/typings';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
 import { service } from '@/pages/device/Instance';
 import { useParams } from 'umi';
 import PropertyLog from '@/pages/device/Instance/Detail/MetadataLog/Property';
@@ -40,6 +40,7 @@ const Property = (props: Props) => {
   const [visible, setVisible] = useState<boolean>(false);
   const [editVisible, setEditVisible] = useState<boolean>(false);
   const [indicatorVisible, setIndicatorVisible] = useState<boolean>(false);
+  const [dataValue, setDataValue] = useState<any>(null);
 
   const renderTitle = (title: string) => {
     return (
@@ -84,12 +85,20 @@ const Property = (props: Props) => {
     );
   };
 
+  useEffect(() => {
+    if (!dataValue?.timestamp) {
+      setDataValue(value);
+    } else if (dataValue?.timestamp && dataValue?.timestamp <= value?.timestamp) {
+      setDataValue(value);
+    }
+  }, [value]);
+
   return (
     <Card bordered hoverable style={{ backgroundColor: 'rgba(0, 0, 0, .02)' }}>
       <Spin spinning={loading}>
         <div>
           <div>{renderTitle(data?.name || '')}</div>
-          <FileComponent type="card" value={value} data={data} />
+          <FileComponent type="card" value={dataValue} data={data} />
           <div style={{ marginTop: 10 }}>
             <div style={{ color: 'rgba(0, 0, 0, .65)', fontSize: 12 }}>更新时间</div>
             <div style={{ marginTop: 5, fontSize: 16, color: 'black' }} className="value">
@@ -98,13 +107,14 @@ const Property = (props: Props) => {
           </div>
         </div>
       </Spin>
-      <EditProperty
-        visible={editVisible}
-        onCancel={() => {
-          setEditVisible(false);
-        }}
-        data={data}
-      />
+      {editVisible && (
+        <EditProperty
+          onCancel={() => {
+            setEditVisible(false);
+          }}
+          data={data}
+        />
+      )}
       {visible && <PropertyLog data={data} close={() => setVisible(false)} />}
       {indicatorVisible && (
         <Indicators

+ 34 - 0
src/pages/device/Instance/Detail/Running/Property/PropertyTable.tsx

@@ -0,0 +1,34 @@
+import moment from 'moment';
+import { useEffect, useState } from 'react';
+import FileComponent from './FileComponent';
+
+interface Props {
+  type: 'time' | 'value';
+  data: any;
+  value: any;
+}
+
+const PropertyTable = (props: Props) => {
+  const { type, data, value } = props;
+  const [dataValue, setDataValue] = useState<any>(null);
+
+  useEffect(() => {
+    if (!dataValue?.timestamp) {
+      setDataValue(value);
+    } else if (dataValue?.timestamp && dataValue?.timestamp <= value?.timestamp) {
+      setDataValue(value);
+    }
+  }, [value]);
+
+  return (
+    <div>
+      {type === 'time' ? (
+        <span>{moment(dataValue?.timestamp).format('YYYY-MM-DD HH:mm:ss')}</span>
+      ) : (
+        <FileComponent type="table" value={dataValue} data={data} />
+      )}
+    </div>
+  );
+};
+
+export default PropertyTable;

+ 102 - 95
src/pages/device/Instance/Detail/Running/Property/index.tsx

@@ -1,4 +1,4 @@
-import { Col, Input, message, Pagination, Row, Space, Table } from 'antd';
+import { Col, Input, message, Pagination, Row, Space, Spin, Table } from 'antd';
 import CheckButton from '@/components/CheckButton';
 import { useCallback, useEffect, useRef, useState } from 'react';
 import type { PropertyMetadata } from '@/pages/device/Product/typings';
@@ -10,10 +10,9 @@ import { map } from 'rxjs/operators';
 import EditProperty from './EditProperty';
 import { useParams } from 'umi';
 import PropertyLog from '../../MetadataLog/Property';
-import moment from 'moment';
 import styles from './index.less';
-import FileComponent from './FileComponent';
 import { throttle } from 'lodash';
+import PropertyTable from './PropertyTable';
 
 interface Props {
   data: Partial<PropertyMetadata>[];
@@ -44,6 +43,7 @@ const Property = (props: Props) => {
     pageSize: 8,
     currentPage: 0,
   });
+  const [loading, setLoading] = useState<boolean>(true);
 
   const [check, setCheck] = useState<boolean>(true);
 
@@ -65,17 +65,17 @@ const Property = (props: Props) => {
       title: '值',
       dataIndex: 'value',
       key: 'value',
-      render: (text: any, record: any) => (
-        <FileComponent type="table" value={propertyValue[record.id]} data={record} />
-      ),
+      render: (text: any, record: any) => {
+        return <PropertyTable type="value" value={propertyValue[record.id]} data={record} />;
+      },
     },
     {
       title: '更新时间',
       dataIndex: 'time',
       key: 'time',
-      render: (text: any, record: any) => (
-        <span>{moment(propertyValue[record.id]?.timestamp).format('YYYY-MM-DD HH:mm:ss')}</span>
-      ),
+      render: (text: any, record: any) => {
+        return <PropertyTable type="time" value={propertyValue[record.id]} data={record} />;
+      },
     },
     {
       title: '操作',
@@ -116,10 +116,12 @@ const Property = (props: Props) => {
   const subRef = useRef<any>(null);
 
   const valueChange = (payload: any) => {
-    payload.forEach((item: any) => {
-      const { value } = item;
-      propertyValue[value?.property] = { ...item, ...value };
-    });
+    (payload || [])
+      .sort((a: any, b: any) => a.timestamp - b.timestamp)
+      .forEach((item: any) => {
+        const { value } = item;
+        propertyValue[value?.property] = { ...item, ...value };
+      });
     setPropertyValue({ ...propertyValue });
     list.current = [];
   };
@@ -161,9 +163,10 @@ const Property = (props: Props) => {
         },
       },
     ];
-
+    setLoading(true);
     service.propertyRealTime(param).subscribe({
       next: (resp) => {
+        setLoading(false);
         propertyValue[resp.property] = resp.list[0];
         setPropertyValue({ ...propertyValue });
       },
@@ -186,91 +189,95 @@ const Property = (props: Props) => {
 
   return (
     <div>
-      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
-        <Space>
-          <Input.Search
-            allowClear
-            placeholder="请输入名称"
-            onSearch={(value: string) => {
-              if (!!value) {
-                const li = data.filter((item) => {
-                  return (
-                    item.name && item.name.toLowerCase().indexOf(value.toLocaleLowerCase()) !== -1
-                  );
-                });
-                setPropertyList(li);
-                setDataSource({
-                  total: li.length,
-                  data: (li || []).slice(0, 8),
-                  pageSize: 8,
-                  currentPage: 0,
-                });
-              } else {
-                setPropertyList(data);
-                setDataSource({
-                  total: data.length,
-                  data: (data || []).slice(0, 8),
-                  pageSize: 8,
-                  currentPage: 0,
-                });
-              }
+      <Spin spinning={loading}>
+        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+          <Space>
+            <Input.Search
+              allowClear
+              placeholder="请输入名称"
+              onSearch={(value: string) => {
+                if (!!value) {
+                  const li = data.filter((item) => {
+                    return (
+                      item.name && item.name.toLowerCase().indexOf(value.toLocaleLowerCase()) !== -1
+                    );
+                  });
+                  setPropertyList(li);
+                  setDataSource({
+                    total: li.length,
+                    data: (li || []).slice(0, 8),
+                    pageSize: 8,
+                    currentPage: 0,
+                  });
+                } else {
+                  setPropertyList(data);
+                  setDataSource({
+                    total: data.length,
+                    data: (data || []).slice(0, 8),
+                    pageSize: 8,
+                    currentPage: 0,
+                  });
+                }
+              }}
+              style={{ width: 317 }}
+            />
+          </Space>
+          <CheckButton
+            value={check}
+            change={(value: boolean) => {
+              setCheck(value);
             }}
-            style={{ width: 317 }}
           />
-        </Space>
-        <CheckButton
-          value={check}
-          change={(value: boolean) => {
-            setCheck(value);
+        </div>
+        <div style={{ marginTop: '20px' }}>
+          {check ? (
+            <Row gutter={[16, 16]}>
+              {dataSource.data.map((item: any) => (
+                <Col {...ColResponsiveProps} key={item.id}>
+                  <PropertyCard data={item} value={item?.id ? propertyValue[item?.id] : '--'} />
+                </Col>
+              ))}
+            </Row>
+          ) : (
+            <Table pagination={false} columns={columns} dataSource={dataSource.data} rowKey="id" />
+          )}
+          {dataSource.data.length > 0 && (
+            <div
+              style={{
+                marginTop: '20px',
+                width: '100%',
+                display: 'flex',
+                justifyContent: 'flex-end',
+              }}
+            >
+              <Pagination
+                className={styles.page}
+                defaultCurrent={1}
+                total={dataSource.total}
+                showSizeChanger
+                pageSize={dataSource.pageSize}
+                pageSizeOptions={[8, 16, 32, 48]}
+                onChange={(page: number, size: number) => {
+                  setDataSource({
+                    total: propertyList.length,
+                    data: (propertyList || []).slice((page - 1) * size, page * size),
+                    pageSize: size,
+                    currentPage: page - 1,
+                  });
+                }}
+              />
+            </div>
+          )}
+        </div>
+      </Spin>
+      {visible && (
+        <EditProperty
+          data={currentInfo}
+          onCancel={() => {
+            setVisible(false);
           }}
         />
-      </div>
-      <div style={{ marginTop: '20px' }}>
-        {check ? (
-          <Row gutter={[16, 16]}>
-            {dataSource.data.map((item: any) => (
-              <Col {...ColResponsiveProps} key={item.id}>
-                <PropertyCard data={item} value={item?.id ? propertyValue[item?.id] : '--'} />
-              </Col>
-            ))}
-          </Row>
-        ) : (
-          <Table pagination={false} columns={columns} dataSource={dataSource.data} rowKey="id" />
-        )}
-        {dataSource.data.length > 0 && (
-          <div
-            style={{
-              marginTop: '20px',
-              width: '100%',
-              display: 'flex',
-              justifyContent: 'flex-end',
-            }}
-          >
-            <Pagination
-              className={styles.page}
-              defaultCurrent={1}
-              total={dataSource.total}
-              showSizeChanger
-              pageSize={dataSource.pageSize}
-              onChange={(page: number, size: number) => {
-                setDataSource({
-                  total: propertyList.length,
-                  data: (propertyList || []).slice((page - 1) * size, page * size),
-                  pageSize: size,
-                  currentPage: page - 1,
-                });
-              }}
-            />
-          </div>
-        )}
-      </div>
-      <EditProperty
-        data={currentInfo}
-        visible={visible}
-        onCancel={() => {
-          setVisible(false);
-        }}
-      />
+      )}
       {infoVisible && <PropertyLog data={currentInfo} close={() => setInfoVisible(false)} />}
     </div>
   );

+ 8 - 0
src/pages/device/components/Metadata/Base/Edit/index.tsx

@@ -705,6 +705,14 @@ const Edit = observer((props: Props) => {
                   type: 'void',
                   'x-component': 'Editable.Popover',
                   title: '指标数据',
+                  'x-reactions': {
+                    dependencies: ['.edit.name'],
+                    fulfill: {
+                      state: {
+                        title: '{{$deps[0]}}',
+                      },
+                    },
+                  },
                   properties: {
                     id: {
                       // 标识

+ 6 - 0
src/pages/media/Device/Channel/Live/index.less

@@ -24,6 +24,12 @@
       right: 0;
       z-index: 2;
       display: flex;
+      opacity: 0;
+      transition: opacity 0.3s;
+
+      &:hover {
+        opacity: 1;
+      }
 
       .tool-item {
         margin-right: 6px;

+ 4 - 3
src/pages/media/Device/Channel/Live/index.tsx

@@ -1,6 +1,6 @@
 // 通道直播
 import { useCallback, useEffect, useState } from 'react';
-import { Radio, Modal } from 'antd';
+import { Modal, Radio } from 'antd';
 import LivePlayer from '@/components/Player';
 import MediaTool from '@/components/Player/mediaTool';
 import { service } from '../index';
@@ -102,8 +102,9 @@ const LiveFC = (props: LiveProps) => {
           buttonStyle={'solid'}
           value={mediaType}
           onChange={(e) => {
-            setMediaType(e.target.value);
-            mediaStart(e.target.value);
+            const _type = e.target.value;
+            setMediaType(_type);
+            mediaStart(_type === 'hls' ? 'm3u8' : _type);
           }}
           options={[
             { label: 'MP4', value: 'mp4' },

+ 17 - 4
src/pages/media/Device/Channel/Save.tsx

@@ -1,7 +1,7 @@
 // Modal 弹窗,用于新增、修改数据
 import { createForm } from '@formily/core';
 import { createSchemaField } from '@formily/react';
-import { Form, FormItem, FormTab, Input, Select, FormGrid } from '@formily/antd';
+import { Form, FormGrid, FormItem, FormTab, Input, Select } from '@formily/antd';
 import { message, Modal } from 'antd';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import type { ISchema } from '@formily/json-schema';
@@ -99,12 +99,25 @@ const Save = (props: SaveModalProps) => {
             max: 64,
             message: '最多可输入64个字符',
           },
+          {
+            triggerType: 'onBlur',
+            validator: (value: string) => {
+              const reg = /(http|https|rtsp|rtmp):\/\/([\w.]+\/?)\S*/;
+              return new Promise((resolve) => {
+                if (reg.test(value)) {
+                  resolve('');
+                } else {
+                  resolve('请输入正确的视频地址');
+                }
+              });
+            },
+          },
         ],
         'x-decorator-props': {
           gridSpan: 24,
         },
       },
-      media_url: {
+      'others.media_url': {
         type: 'string',
         title: '视频地址',
         'x-visible': props.type === ProviderValue.FIXED,
@@ -112,8 +125,8 @@ const Save = (props: SaveModalProps) => {
         'x-component': 'Input',
         'x-validator': [
           {
-            max: 64,
-            message: '最多可输入64个字符',
+            max: 128,
+            message: '最多可输入128个字符',
           },
         ],
       },

+ 16 - 0
src/pages/rule-engine/Alarm/Configuration/index.tsx

@@ -18,11 +18,14 @@ import Save from './Save';
 import Service from '@/pages/rule-engine/Alarm/Configuration/service';
 import AlarmConfig from '@/components/ProTableCard/CardItems/AlarmConfig';
 import { Store } from 'jetlinks-store';
+import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
+import { useHistory } from 'umi';
 
 const service = new Service('alarm/config');
 
 const Configuration = () => {
   const intl = useIntl();
+  const history = useHistory();
   const [visible, setVisible] = useState<boolean>(false);
   const actionRef = useRef<ActionType>();
   const { permission } = PermissionButton.usePermission('rule-engine/Alarm/Configuration');
@@ -59,6 +62,19 @@ const Configuration = () => {
     {
       title: '关联场景联动',
       dataIndex: 'sceneName',
+      render: (text: any, record: any) => (
+        <PermissionButton
+          type={'link'}
+          isPermission={!!getMenuPathByCode(MENUS_CODE['rule-engine/Scene'])}
+          style={{ padding: 0, height: 'auto' }}
+          onClick={() => {
+            const url = getMenuPathByCode('rule-engine/Scene/Save');
+            history.push(`${url}?id=${record.sceneId}`);
+          }}
+        >
+          {text}
+        </PermissionButton>
+      ),
     },
     {
       title: '状态',

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

@@ -1,412 +0,0 @@
-import { useCallback, useEffect, useState } from 'react';
-import { Col, Form, Row, Select, TreeSelect } from 'antd';
-import { ItemGroup, TimingTrigger } from '@/pages/rule-engine/Scene/Save/components';
-import { getProductList } from '@/pages/rule-engine/Scene/Save/action/device/service';
-import { queryOrgTree } from '@/pages/rule-engine/Scene/Save/trigger/service';
-import Device from '@/pages/rule-engine/Scene/Save/action/device/deviceModal';
-import FunctionCall from '@/pages/rule-engine/Scene/Save/action/device/functionCall';
-import Operation from './operation';
-import classNames from 'classnames';
-import { FormModel } from '../index';
-import AllDevice from '@/pages/rule-engine/Scene/Save/action/device/AllDevice';
-
-interface DeviceData {
-  productId?: string;
-  source?: string;
-  selector?: any;
-  selectorValues?: any[];
-  relation?: any;
-  upperKey?: string;
-  operation?: {
-    operator?: string;
-    timer?: any;
-    eventId?: string;
-    readProperties?: string[];
-    writeProperties?: any;
-    functionId?: string;
-    functionParameters?: { name: string; value: any }[];
-  };
-}
-
-interface TriggerProps {
-  value?: DeviceData;
-  onChange?: (value: DeviceData) => void;
-  className?: string;
-}
-
-enum OperatorEnum {
-  'online' = 'online',
-  'offline' = 'offline',
-  'reportEvent' = 'reportEvent',
-  'reportProperty' = 'reportProperty',
-  'readProperty' = 'readProperty',
-  'writeProperty' = 'writeProperty',
-  'invokeFunction' = 'invokeFunction',
-}
-
-export default (props: TriggerProps) => {
-  const [productList, setProductList] = useState<any[]>([]);
-  const [productId, setProductId] = useState('');
-  // const [selector, setSelector] = useState('fixed');
-
-  // const [selectorOptions, setSelectorOptions] = useState<any[]>([]);
-  // const [operation, setOperation] = useState<string | undefined>(undefined);
-  const [operatorOptions, setOperatorOptions] = useState<any[]>([]);
-
-  const [properties, setProperties] = useState<any[]>([]); // 属性
-  const [events, setEvents] = useState([]); // 事件
-  const [functions, setFunctions] = useState([]); // 功能列表
-
-  const [functionItem, setFunctionItem] = useState<any[]>([]); // 单个功能-属性列表
-  const [orgTree, setOrgTree] = useState<any>([]);
-
-  const onChange = (value: DeviceData) => {
-    if (props.onChange) {
-      props.onChange(value);
-    }
-  };
-
-  // const getSelector = () => {
-  //   querySelector().then((resp) => {
-  //     if (resp && resp.status === 200) {
-  //       setSelectorOptions(resp.result);
-  //     }
-  //   });
-  // };
-
-  const handleMetadata = (metadata?: string) => {
-    try {
-      const metadataObj = JSON.parse(metadata || '{}');
-      let newOperator: any[] = [
-        { label: '设备上线', value: OperatorEnum.online },
-        { label: '设备离线', value: OperatorEnum.offline },
-      ];
-      if (metadataObj.properties && metadataObj.properties.length) {
-        newOperator = [
-          ...newOperator,
-          { label: '属性上报', value: OperatorEnum.reportProperty },
-          { label: '属性读取', value: OperatorEnum.readProperty },
-          { label: '修改属性', value: OperatorEnum.writeProperty },
-        ];
-        setProperties(metadataObj.properties);
-      }
-      if (metadataObj.events && metadataObj.events.length) {
-        newOperator = [...newOperator, { label: '事件上报', value: OperatorEnum.reportEvent }];
-        setEvents(metadataObj.events);
-      }
-      if (metadataObj.functions && metadataObj.functions.length) {
-        newOperator = [...newOperator, { label: '功能调用', value: OperatorEnum.invokeFunction }];
-        setFunctions(metadataObj.functions);
-      }
-      setOperatorOptions(newOperator);
-    } catch (err) {
-      console.warn('handleMetadata === ', err);
-    }
-  };
-
-  const productIdChange = useCallback(
-    (id: string, metadata: any) => {
-      setProductId(id);
-      handleMetadata(metadata);
-    },
-    [props.value],
-  );
-
-  const getProducts = async () => {
-    const resp = await getProductList({ paging: false });
-    if (resp && resp.status === 200) {
-      setProductList(resp.result);
-      if (FormModel.trigger && FormModel.trigger.device) {
-        const productItem = resp.result.find(
-          (item: any) => item.id === FormModel.trigger!.device.productId,
-        );
-
-        if (productItem) {
-          productIdChange(FormModel.trigger!.device.productId, productItem.metadata);
-        }
-      }
-    }
-  };
-
-  useEffect(() => {
-    getProducts();
-    // getSelector();
-  }, []);
-
-  useEffect(() => {
-    const triggerData: any = props.value;
-    console.log('trigger', triggerData);
-    if (triggerData) {
-      if (triggerData.selector) {
-        if (triggerData.selector === 'org') {
-          queryOrgTree(triggerData.productId).then((resp) => {
-            if (resp && resp.status === 200) {
-              setOrgTree(resp.result);
-            }
-          });
-        }
-      }
-    }
-  }, [props.value]);
-
-  return (
-    <div className={classNames(props.className)}>
-      <Row gutter={24}>
-        <Col span={6}>
-          <Form.Item
-            noStyle
-            rules={[
-              {
-                validator: async (_: any, value: any) => {
-                  console.log('productId', value);
-                  return Promise.resolve();
-                },
-              },
-            ]}
-          >
-            <Select
-              showSearch
-              value={props.value?.productId}
-              options={productList}
-              placeholder={'请选择产品'}
-              style={{ width: '100%' }}
-              listHeight={220}
-              onChange={(key: any, node: any) => {
-                productIdChange(key, node.metadata);
-                onChange({
-                  productId: key,
-                  selectorValues: undefined,
-                  selector: 'fixed',
-                  operation: {
-                    operator: undefined,
-                  },
-                });
-              }}
-              fieldNames={{ label: 'name', value: 'id' }}
-              filterOption={(input: string, option: any) =>
-                option.name.toLowerCase().indexOf(input.toLowerCase()) >= 0
-              }
-            />
-          </Form.Item>
-        </Col>
-        {props.value?.productId ? (
-          <Col span={12}>
-            <ItemGroup compact>
-              <Select
-                value={props.value?.selector}
-                options={[
-                  { label: '全部设备', value: 'all' },
-                  { label: '固定设备', value: 'fixed' },
-                  { label: '按部门', value: 'org' },
-                ]}
-                style={{ width: 120 }}
-                onSelect={(value: string) => {
-                  const _value = { ...props.value };
-                  _value.selector = value;
-                  _value.selectorValues = undefined;
-                  onChange(_value);
-                }}
-              />
-              {props.value?.selector === 'all' && (
-                <AllDevice
-                  value={props.value?.selectorValues}
-                  productId={productId}
-                  onChange={(value) => {
-                    const _value = { ...props.value };
-                    _value.selectorValues = value;
-                    onChange(_value);
-                  }}
-                />
-              )}
-              {props.value?.selector === 'fixed' && (
-                <Device
-                  value={props.value?.selectorValues}
-                  productId={productId}
-                  onChange={(value) => {
-                    const _value = { ...props.value };
-                    _value.selectorValues = value;
-                    onChange(_value);
-                  }}
-                />
-              )}
-              {props.value?.selector === 'org' && (
-                <TreeSelect
-                  value={
-                    props.value?.selectorValues && props.value?.selectorValues.length
-                      ? props.value?.selectorValues[0].id
-                      : undefined
-                  }
-                  treeData={orgTree}
-                  fieldNames={{ label: 'name', value: 'id' }}
-                  placeholder={'请选择部门'}
-                  style={{ width: '100%' }}
-                  onChange={(value, label) => {
-                    const _value = { ...props.value };
-                    _value.selectorValues = [{ id: value, name: label[0] }];
-                    onChange(_value);
-                  }}
-                />
-              )}
-            </ItemGroup>
-          </Col>
-        ) : null}
-        <Col span={6}>
-          {functions.length || events.length || properties.length ? (
-            <Select
-              value={props.value?.operation?.operator}
-              placeholder={'请选择触发类型'}
-              options={operatorOptions}
-              style={{ width: '100%' }}
-              onChange={(value) => {
-                const _value = { ...props.value };
-                _value.operation!.operator = value;
-                onChange(_value);
-              }}
-            />
-          ) : null}
-        </Col>
-      </Row>
-      {props.value?.operation?.operator === OperatorEnum.invokeFunction ||
-      props.value?.operation?.operator === OperatorEnum.readProperty ||
-      props.value?.operation?.operator === OperatorEnum.writeProperty ? (
-        <TimingTrigger
-          value={props.value?.operation?.timer}
-          onChange={(value) => {
-            const _value = { ...props.value };
-            _value.operation!.timer = value;
-            onChange(_value);
-          }}
-        />
-      ) : null}
-      {props.value?.operation?.operator === OperatorEnum.invokeFunction && (
-        <>
-          <Row gutter={24}>
-            <Col span={6}>
-              <Select
-                value={props.value?.operation?.functionId}
-                options={functions}
-                fieldNames={{
-                  label: 'name',
-                  value: 'id',
-                }}
-                style={{ width: '100%' }}
-                placeholder={'请选择功能'}
-                onSelect={(key: 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);
-                  const _value = { ...props.value };
-                  _value.operation!.functionId = key;
-                  onChange(_value);
-                }}
-              />
-            </Col>
-            <Col span={18}>
-              <span style={{ lineHeight: '32px' }}>定时调用所选功能</span>
-            </Col>
-          </Row>
-          <Row>
-            <Col span={24}>
-              <FunctionCall
-                value={props.value?.operation?.functionParameters}
-                functionData={functionItem}
-                onChange={(value) => {
-                  const _value = { ...props.value };
-                  _value.operation!.functionParameters = value;
-                  onChange(_value);
-                }}
-              />
-            </Col>
-          </Row>
-        </>
-      )}
-      {props.value?.operation?.operator === OperatorEnum.writeProperty && (
-        <Row>
-          <Col span={24}>
-            <Operation
-              value={props.value?.operation?.writeProperties}
-              propertiesList={properties.filter((item) => {
-                if (item.expands) {
-                  return item.expands.type ? item.expands.type.includes('write') : false;
-                }
-                return false;
-              })}
-              onChange={(value) => {
-                const _value = { ...props.value };
-                _value.operation!.writeProperties = value;
-                onChange(_value);
-              }}
-            />
-          </Col>
-        </Row>
-      )}
-      {props.value?.operation?.operator === OperatorEnum.readProperty && (
-        <Row gutter={24}>
-          <Col span={6}>
-            <Select
-              value={props.value?.operation?.readProperties}
-              mode={'multiple'}
-              options={properties.filter((item) => {
-                if (item.expands) {
-                  return item.expands.type ? item.expands.type.includes('read') : false;
-                }
-                return false;
-              })}
-              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' }}
-              onChange={(value) => {
-                if (props.value) {
-                  const _value = { ...props.value };
-                  _value!.operation!.readProperties = value;
-                  onChange(_value);
-                }
-              }}
-            />
-          </Col>
-          <Col span={18}>
-            <span style={{ lineHeight: '32px' }}>定时读取所选属性值</span>
-          </Col>
-        </Row>
-      )}
-      {props.value?.operation?.operator === OperatorEnum.reportEvent && (
-        <Row gutter={24}>
-          <Col span={6}>
-            <Select
-              value={props.value?.operation?.eventId}
-              options={events}
-              placeholder={'请选择事件'}
-              style={{ width: '100%' }}
-              fieldNames={{ label: 'name', value: 'id' }}
-              onChange={(value) => {
-                if (props.value) {
-                  const _value = { ...props.value };
-                  _value!.operation!.eventId = value;
-                  onChange(_value);
-                }
-              }}
-            />
-          </Col>
-        </Row>
-      )}
-    </div>
-  );
-};

+ 5 - 2
src/pages/rule-engine/Scene/TriggerTerm/index.tsx

@@ -231,7 +231,9 @@ const TriggerTerm = (props: Props, ref: any) => {
   useImperativeHandle(ref, () => ({
     getTriggerForm: async () => {
       await form.validate();
+
       const data: any = await form.submit().then((_data: any) => {
+        if (!Array.isArray(_data.trigger)) return;
         _data.trigger?.map((item: { terms: any[] }) =>
           item.terms.map((j) => {
             if (j.value.value.length === 1) {
@@ -242,7 +244,7 @@ const TriggerTerm = (props: Props, ref: any) => {
         );
         return _data;
       });
-      return data;
+      return Array.isArray(data?.trigger) ? data : undefined;
     },
   }));
   const SchemaField = createSchemaField({
@@ -269,7 +271,7 @@ const TriggerTerm = (props: Props, ref: any) => {
         'x-component': 'FTermArrayCards',
         'x-decorator': 'FormItem',
         'x-value': {
-          termType: 'and',
+          type: 'and',
         },
         'x-component-props': {
           title: '分组',
@@ -293,6 +295,7 @@ const TriggerTerm = (props: Props, ref: any) => {
                     type: 'string',
                     // "x-decorator": 'FormItem',
                     'x-component': 'FTermTypeSelect',
+                    'x-value': 'and',
                   },
                   layout: {
                     type: 'void',

+ 30 - 2
src/pages/system/Platforms/Api/base.tsx

@@ -1,11 +1,12 @@
 import Tree from '@/pages/system/Platforms/Api/leftTree';
 import Table from '@/pages/system/Platforms/Api/basePage';
 import SwaggerUI from '@/pages/system/Platforms/Api/swagger-ui';
-import { useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
 import { service } from '@/pages/system/Platforms';
 import { model } from '@formily/reactive';
 import { observer } from '@formily/react';
 import './index.less';
+import { useLocation } from 'umi';
 
 export const ApiModel = model<{
   data: any[];
@@ -29,7 +30,9 @@ interface ApiPageProps {
 }
 
 export default observer((props: ApiPageProps) => {
+  const location = useLocation();
   const [operations, setOperations] = useState<string[]>([]);
+  const [GrantKeys, setGrantKeys] = useState<string[]>([]);
 
   const initModel = () => {
     ApiModel.data = [];
@@ -39,6 +42,9 @@ export default observer((props: ApiPageProps) => {
     ApiModel.debugger = {};
   };
 
+  /**
+   *  获取能授权的接口ID
+   */
   const getOperations = () => {
     service.apiOperations().then((resp: any) => {
       if (resp.status === 200) {
@@ -47,15 +53,32 @@ export default observer((props: ApiPageProps) => {
     });
   };
 
+  /**
+   * 获取已授权的接口ID
+   */
+  const getApiGrant = useCallback(() => {
+    const param = new URLSearchParams(location.search);
+    const code = param.get('code');
+
+    service.getApiGranted(code!).then((resp: any) => {
+      if (resp.status === 200) {
+        setGrantKeys(resp.result);
+      }
+    });
+  }, [location]);
+
   useEffect(() => {
     initModel();
     getOperations();
+    getApiGrant();
   }, []);
 
   return (
     <div className={'platforms-api'}>
       <div className={'platforms-api-tree'}>
         <Tree
+          isShowGranted={props.isShowGranted}
+          grantKeys={GrantKeys}
           onSelect={(data) => {
             ApiModel.data = data;
             ApiModel.showTable = true;
@@ -63,7 +86,12 @@ export default observer((props: ApiPageProps) => {
         />
       </div>
       {ApiModel.showTable ? (
-        <Table data={ApiModel.data} operations={operations} isShowGranted={props.isShowGranted} />
+        <Table
+          data={ApiModel.data}
+          operations={operations}
+          isShowGranted={props.isShowGranted}
+          grantKeys={GrantKeys}
+        />
       ) : (
         <SwaggerUI showDebugger={props.showDebugger} />
       )}

+ 12 - 18
src/pages/system/Platforms/Api/basePage.tsx

@@ -9,6 +9,8 @@ interface TableProps {
   operations: string[];
   // 是否只暂时已授权的接口
   isShowGranted?: boolean;
+  //
+  grantKeys: string[];
 }
 
 export default (props: TableProps) => {
@@ -20,18 +22,6 @@ export default (props: TableProps) => {
 
   const location = useLocation();
 
-  const getApiGrant = useCallback(() => {
-    const param = new URLSearchParams(location.search);
-    const code = param.get('code');
-
-    service.getApiGranted(code!).then((resp: any) => {
-      if (resp.status === 200) {
-        grantCache.current = resp.result;
-        setSelectKeys(resp.result);
-      }
-    });
-  }, [location]);
-
   const getOperations = async (apiData: any[], operations: string[]) => {
     // 过滤只能授权的接口,当isShowGranted为true时,过滤为已赋权的接口
     setDataSource(
@@ -39,6 +29,14 @@ export default (props: TableProps) => {
     );
   };
 
+  /**
+   * 获取已授权的接口ID
+   */
+  useEffect(() => {
+    grantCache.current = props.grantKeys;
+    setSelectKeys(props.grantKeys);
+  }, [props.grantKeys]);
+
   useEffect(() => {
     if (props.isShowGranted) {
       if (props.data && selectKeys) {
@@ -59,10 +57,6 @@ export default (props: TableProps) => {
     }
   }, [props.data, props.operations, props.isShowGranted]);
 
-  useEffect(() => {
-    getApiGrant();
-  }, []);
-
   const save = useCallback(async () => {
     const param = new URLSearchParams(location.search);
     const code = param.get('code');
@@ -86,7 +80,7 @@ export default (props: TableProps) => {
       const item = dataSource.find((b) => b.operationId === a);
       return {
         id: a,
-        permissions: item.security,
+        permissions: item?.security,
       };
     });
 
@@ -94,7 +88,7 @@ export default (props: TableProps) => {
       const item = dataSource.find((b) => b.operationId === a);
       return {
         id: a,
-        permissions: item.security,
+        permissions: item?.security,
       };
     });
 

+ 37 - 20
src/pages/system/Platforms/Api/leftTree.tsx

@@ -1,10 +1,15 @@
 import { Tree } from 'antd';
-import React, { useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
 import { service } from '@/pages/system/Platforms';
 import { ApiModel } from '@/pages/system/Platforms/Api/base';
 
 type LeftTreeType = {
   onSelect: (data: any) => void;
+  /**
+   * 是否只展示已授权的接口
+   */
+  isShowGranted?: boolean;
+  grantKeys?: string[];
 };
 
 interface DataNode {
@@ -25,24 +30,38 @@ export default (props: LeftTreeType) => {
     }
   };
 
-  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,
-        };
-      }
+  const updateTreeData = useCallback(
+    (list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] => {
+      return list.map((node) => {
+        if (node.id === key) {
+          const newChildren = node.children ? [...node.children, ...children] : children;
+          // api详情时,过滤掉没有授权的接口
+          const filterChildren = props.isShowGranted
+            ? newChildren.filter(
+                (item: any) =>
+                  item.extraData &&
+                  item.extraData.some((extraItem: any) =>
+                    props.grantKeys?.includes(extraItem.operationId),
+                  ),
+              )
+            : newChildren;
+          return {
+            ...node,
+            children: filterChildren,
+          };
+        }
 
-      if (node.children) {
-        return {
-          ...node,
-          children: updateTreeData(node.children, key, children),
-        };
-      }
-      return node;
-    });
-  };
+        if (node.children) {
+          return {
+            ...node,
+            children: updateTreeData(node.children, key, children),
+          };
+        }
+        return node;
+      });
+    },
+    [props.isShowGranted, props.grantKeys],
+  );
 
   const handleTreeData = (data: any) => {
     const newArr = data.tags.map((item: any) => ({ id: item.name, name: item.name, isLeaf: true }));
@@ -62,7 +81,6 @@ export default (props: LeftTreeType) => {
         }
       });
     });
-    console.log(newArr);
     return newArr;
   };
 
@@ -83,7 +101,6 @@ export default (props: LeftTreeType) => {
   };
 
   const onLoadData = (node: any): Promise<void> => {
-    console.log(node);
     return new Promise(async (resolve) => {
       if (node.children) {
         resolve();