Pārlūkot izejas kodu

fix(merge): merge sc

lind 4 gadi atpakaļ
vecāks
revīzija
8bab125b36
29 mainītis faili ar 490 papildinājumiem un 692 dzēšanām
  1. BIN
      public/images/bind/Rectangle.png
  2. BIN
      public/images/bind/Vector.png
  3. BIN
      public/images/bind/bindPage.png
  4. BIN
      public/images/bind/jetlinksLogo.png
  5. 18 3
      src/components/ProTableCard/CardItems/AlarmConfig.tsx
  6. 56 8
      src/pages/account/Center/bind/index.less
  7. 74 49
      src/pages/account/Center/bind/index.tsx
  8. 1 1
      src/pages/demo/AMap/index.tsx
  9. 5 4
      src/pages/device/Instance/Detail/MetadataLog/Property/index.tsx
  10. 15 10
      src/pages/device/Instance/Detail/Opcua/index.tsx
  11. 3 3
      src/pages/device/Instance/Detail/Running/Property/EditProperty.tsx
  12. 1 1
      src/pages/device/Instance/Detail/Running/Property/FileComponent/index.less
  13. 19 9
      src/pages/device/Instance/Detail/Running/Property/PropertyCard.tsx
  14. 34 0
      src/pages/device/Instance/Detail/Running/Property/PropertyTable.tsx
  15. 102 95
      src/pages/device/Instance/Detail/Running/Property/index.tsx
  16. 15 10
      src/pages/device/Instance/Detail/index.tsx
  17. 10 17
      src/pages/link/Channel/Modbus/Access/addPoint/index.tsx
  18. 2 2
      src/pages/link/Channel/Modbus/index.tsx
  19. 12 8
      src/pages/link/Channel/Opcua/Save/index.tsx
  20. 1 1
      src/pages/link/Channel/Opcua/index.tsx
  21. 6 0
      src/pages/media/Device/Channel/Live/index.less
  22. 4 3
      src/pages/media/Device/Channel/Live/index.tsx
  23. 17 4
      src/pages/media/Device/Channel/Save.tsx
  24. 16 0
      src/pages/rule-engine/Alarm/Configuration/index.tsx
  25. 0 412
      src/pages/rule-engine/Scene/Save/trigger/device.tsx
  26. 30 2
      src/pages/system/Platforms/Api/base.tsx
  27. 12 18
      src/pages/system/Platforms/Api/basePage.tsx
  28. 37 20
      src/pages/system/Platforms/Api/leftTree.tsx
  29. 0 12
      src/utils/menu/index.ts

BIN
public/images/bind/Rectangle.png


BIN
public/images/bind/Vector.png


BIN
public/images/bind/bindPage.png


BIN
public/images/bind/jetlinksLogo.png


+ 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>

+ 56 - 8
src/pages/account/Center/bind/index.less

@@ -1,12 +1,60 @@
-.col {
-  display: flex;
-  justify-content: center;
+.cards {
+  position: absolute;
+  top: 150px;
+  left: 17%;
+  box-sizing: border-box;
+  width: 66%;
+  background: #fff;
+  border: 1px solid #e0e4e8;
+  border-radius: 2px;
 
-  .item {
+  .title {
+    margin: 50px 0;
+    color: #0f1222;
+    font-weight: 400;
+    font-size: 24px;
+    font-family: 'PingFang SC';
+    font-style: normal;
+    line-height: 25px;
+    text-align: center;
+  }
+
+  .info {
+    display: flex;
+    justify-content: center;
+
+    .infotitle {
+      width: 64px;
+      height: 24px;
+      margin-left: 10px;
+      color: rgba(0, 0, 0, 0.85);
+      font-weight: 400;
+      font-size: 16px;
+      font-family: 'PingFang SC';
+      font-style: normal;
+      line-height: 24px;
+    }
+
+    .item {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      width: 300px;
+
+      .fonts {
+        font-weight: 400;
+        font-size: 16px;
+        font-family: 'PingFang SC';
+        font-style: normal;
+        line-height: 16px;
+        opacity: 0.75;
+        mix-blend-mode: normal;
+      }
+    }
+  }
+
+  .btn {
     display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: space-around;
-    width: 300px;
+    justify-content: center;
   }
 }

+ 74 - 49
src/pages/account/Center/bind/index.tsx

@@ -1,4 +1,4 @@
-import { Avatar, Button, Card, Col, message, Row } from 'antd';
+import { Button, Card, message } from 'antd';
 import { useEffect, useState } from 'react';
 import Service from '@/pages/account/Center/service';
 import styles from './index.less';
@@ -10,14 +10,15 @@ const Bind = () => {
   const [user, setUser] = useState<any>();
   const [code, setCode] = useState<string>('');
 
+  const bindPage = require('/public/images/bind/bindPage.png');
+  const Vector = require('/public/images/bind/Vector.png');
+  const Rectangle = require('/public/images/bind/Rectangle.png');
+  const logo = require('/public/images/bind/jetlinksLogo.png');
+
   const iconMap = new Map();
   iconMap.set('dingtalk', require('/public/images/notice/dingtalk.png'));
   iconMap.set('wechat-webapp', require('/public/images/notice/wechat.png'));
 
-  const bGroundMap = new Map();
-  bGroundMap.set('dingtalk', require('/public/images/notice/dingtalk-background.png'));
-  bGroundMap.set('wechat-webapp', require('/public/images/notice/wechat-background.png'));
-
   const bindUserInfo = (params: string) => {
     service.bindUserInfo(params).then((res) => {
       if (res.status === 200) {
@@ -40,55 +41,79 @@ const Bind = () => {
   }, []);
   return (
     <>
-      <Card>
-        <div style={{ margin: '0 auto', width: 800 }}>
-          <Row>
-            <Col span={12} className={styles.col}>
-              <Card title="个人信息">
-                <div className={styles.item}>
-                  <div style={{ height: 100 }}>
-                    <Avatar size={90} src={user?.avatar} />
+      <div
+        style={{
+          width: '100%',
+          height: '100%',
+          background: `url(${bindPage}) no-repeat`,
+          backgroundSize: '100% 100%',
+        }}
+      >
+        <div className={styles.cards}>
+          <div className={styles.title}>第三方账户绑定</div>
+          <div className={styles.info}>
+            <Card
+              title={
+                <div style={{ display: 'flex', alignItems: 'center' }}>
+                  <div>
+                    <img src={Rectangle} />
                   </div>
-                  <p>登录账号:{user?.username}</p>
-                  <p>姓名:{user?.name}</p>
+                  <div className={styles.infotitle}>个人信息</div>
                 </div>
-              </Card>
-            </Col>
-            <Col span={12} className={styles.col}>
-              <Card title="三方账号信息">
-                <div className={styles.item}>
-                  <div style={{ height: 100 }}>
-                    <img style={{ height: 80 }} src={iconMap.get(bindUser?.type)} />
+              }
+            >
+              <div className={styles.item}>
+                <div style={{ height: 100, marginTop: 10, marginBottom: 10 }}>
+                  <img src={logo} style={{ width: 90, height: 90 }} />
+                </div>
+                <p className={styles.fonts}>账号:{user?.username}</p>
+                <p className={styles.fonts}>用户名:{user?.name}</p>
+              </div>
+            </Card>
+            <div style={{ position: 'relative', top: '135px', margin: '0 20px' }}>
+              <img src={Vector} />
+            </div>
+            <Card
+              title={
+                <div style={{ display: 'flex', alignItems: 'center' }}>
+                  <div>
+                    <img src={Rectangle} />
                   </div>
-                  <p>组织:{bindUser?.providerName}</p>
-                  <p>名字:{bindUser?.result.others.name}</p>
+                  <div className={styles.infotitle}>三方账户信息</div>
+                </div>
+              }
+            >
+              <div className={styles.item}>
+                <div style={{ height: 100, marginTop: 10, marginBottom: 10 }}>
+                  <img style={{ height: 80 }} src={iconMap.get(bindUser?.type)} />
                 </div>
-              </Card>
-            </Col>
-          </Row>
-          <Row>
-            <Col span={24} style={{ textAlign: 'center', marginTop: 20 }}>
-              <Button
-                type="primary"
-                onClick={() => {
-                  // window.close()
-                  service.bind(code).then((res) => {
-                    if (res.status === 200) {
-                      message.success('绑定成功');
-                      localStorage.setItem('onBind', 'true');
-                      setTimeout(() => window.close(), 300);
-                    } else {
-                      message.error('绑定失败');
-                    }
-                  });
-                }}
-              >
-                立即绑定
-              </Button>
-            </Col>
-          </Row>
+                <p className={styles.fonts}>账户:{bindUser?.providerName}</p>
+                <p className={styles.fonts}>用户名:{bindUser?.result.others.name}</p>
+              </div>
+            </Card>
+          </div>
+          <div className={styles.btn}>
+            <Button
+              style={{ marginTop: 30, marginBottom: 30 }}
+              type="primary"
+              onClick={() => {
+                // window.close()
+                service.bind(code).then((res) => {
+                  if (res.status === 200) {
+                    message.success('绑定成功');
+                    localStorage.setItem('onBind', 'true');
+                    setTimeout(() => window.close(), 1000);
+                  } else {
+                    message.error('绑定失败');
+                  }
+                });
+              }}
+            >
+              立即绑定
+            </Button>
+          </div>
         </div>
-      </Card>
+      </div>
     </>
   );
 };

+ 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,

+ 15 - 10
src/pages/device/Instance/Detail/Opcua/index.tsx

@@ -1,6 +1,6 @@
 import PermissionButton from '@/components/PermissionButton';
 import { Badge, Card, Empty, message, Tabs, Tooltip } from 'antd';
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useMemo, useRef, useState } from 'react';
 import { useIntl } from 'umi';
 import styles from '@/pages/link/Channel/Opcua/Access/index.less';
 import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
@@ -181,6 +181,19 @@ const Opcua = () => {
       });
   };
 
+  const edit = useMemo(
+    () => (
+      <Save
+        data={channel}
+        close={() => {
+          setVisible(false);
+        }}
+        device={InstanceModel.detail}
+      />
+    ),
+    [channel.id],
+  );
+
   useEffect(() => {
     const { id } = InstanceModel.detail;
     setDeviceId(id);
@@ -342,15 +355,7 @@ const Opcua = () => {
       ) : (
         <Empty />
       )}
-      {visible && (
-        <Save
-          data={channel}
-          close={() => {
-            setVisible(false);
-          }}
-          device={InstanceModel.detail}
-        />
-      )}
+      {visible && edit}
       {pointVisiable && (
         <AddPoint
           deviceId={deviceId}

+ 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>
   );

+ 15 - 10
src/pages/device/Instance/Detail/index.tsx

@@ -149,24 +149,29 @@ const InstanceDetail = observer(() => {
       tab: '物模型映射',
       component: <MetadataMap type="device" />,
     },
-    {
-      key: 'opcua',
-      tab: 'OPC UA',
-      component: <Opcua />,
-    },
-    {
-      key: 'modbus',
-      tab: 'Modbus',
-      component: <Modbus />,
-    },
   ];
   const [list, setList] =
     useState<{ key: string; tab: string | ReactNode; component: ReactNode }[]>(baseList);
 
   const getDetail = (id: string) => {
     service.detail(id).then((response) => {
+      console.log(response.result);
       InstanceModel.detail = response?.result;
       const datalist = [...baseList];
+      if (response.result.protocol === 'modbus-tcp') {
+        datalist.push({
+          key: 'modbus',
+          tab: 'Modbus',
+          component: <Modbus />,
+        });
+      }
+      if (response.result.protocol === 'opc-ua') {
+        datalist.push({
+          key: 'opcua',
+          tab: 'OPC UA',
+          component: <Opcua />,
+        });
+      }
       if (response.result.deviceType?.value === 'gateway') {
         // 产品类型为网关的情况下才显示此模块
         datalist.push({

+ 10 - 17
src/pages/link/Channel/Modbus/Access/addPoint/index.tsx

@@ -57,8 +57,10 @@ const AddPoint = (props: Props) => {
         initialValues={{
           ...props.data,
           codecConfig: {
+            ...props.data?.codecConfig,
             readIndex: props.data?.codecConfig?.readIndex || 0,
             scaleFactor: props.data?.codecConfig?.scaleFactor || 1,
+            revertBytes: props.data?.codecConfig?.revertBytes || false,
           },
         }}
       >
@@ -138,23 +140,14 @@ const AddPoint = (props: Props) => {
             </Form.Item>
           </Col>
           <Col span={12}>
-            <Form.Item
-              label="读取长度"
-              name={['codecConfig', 'readLength']}
-              required
-              rules={[
-                { required: true, message: '请输入读取长度' },
-                ({}) => ({
-                  validator(_, value) {
-                    if (value !== 0 || /(^[1-9]\d*$)/.test(value)) {
-                      return Promise.resolve();
-                    }
-                    return Promise.reject(new Error('请输入正整数'));
-                  },
-                }),
-              ]}
-            >
-              <InputNumber style={{ width: '100%' }} placeholder="请输入" min={1} />
+            <Form.Item label="读取长度" name={['codecConfig', 'readLength']} required>
+              <Select placeholder="请选择">
+                <Select.Option value={1}>1</Select.Option>
+                <Select.Option value={2}>2</Select.Option>
+                <Select.Option value={3}>3</Select.Option>
+                <Select.Option value={4}>4</Select.Option>
+                <Select.Option value={8}>8</Select.Option>
+              </Select>
             </Form.Item>
           </Col>
         </Row>

+ 2 - 2
src/pages/link/Channel/Modbus/index.tsx

@@ -93,12 +93,12 @@ const Modbus = () => {
             onConfirm: async () => {
               if (record.state.value === 'disabled') {
                 await service.edit({
-                  id: record.id,
+                  ...record,
                   state: 'enabled',
                 });
               } else {
                 await service.edit({
-                  id: record.id,
+                  ...record,
                   state: 'disabled',
                 });
               }

+ 12 - 8
src/pages/link/Channel/Opcua/Save/index.tsx

@@ -6,7 +6,7 @@ import type { ISchema } from '@formily/json-schema';
 import { service } from '@/pages/link/Channel/Opcua';
 import { Modal } from '@/components';
 import { message } from 'antd';
-import { useEffect, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
 
 interface Props {
   data: Partial<OpaUa>;
@@ -19,13 +19,17 @@ const Save = (props: Props) => {
   const [policies, setPolicies] = useState<any>([]);
   const [modes, setModes] = useState<any>([]);
 
-  const form = createForm({
-    validateFirst: true,
-    initialValues: {
-      ...props.data,
-      clientConfigs: props.data?.clientConfigs?.[0],
-    },
-  });
+  const form = useMemo(
+    () =>
+      createForm({
+        validateFirst: true,
+        initialValues: {
+          ...props.data,
+          clientConfigs: props.data?.clientConfigs?.[0],
+        },
+      }),
+    [props.data.id],
+  );
 
   const SchemaField = createSchemaField({
     components: {

+ 1 - 1
src/pages/link/Channel/Opcua/index.tsx

@@ -136,7 +136,7 @@ const Opcua = () => {
           disabled={record.state.value === 'enabled'}
           popConfirm={{
             title: '确认删除',
-            disabled: record.state.value === 'enable',
+            disabled: record.state.value === 'enabled',
             onConfirm: async () => {
               const resp: any = await service.remove(record.id);
               if (resp.status === 200) {

+ 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>
-  );
-};

+ 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();

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

@@ -85,12 +85,6 @@ export const extraRouteArr = [
         url: '/account/center',
       },
       {
-        code: 'account/Center/bind',
-        name: '第三方页面',
-        url: '/account/center/bind',
-        hideInMenu: true,
-      },
-      {
         code: 'account/NotificationSubscription',
         name: '通知订阅',
         url: '/account/NotificationSubscription',
@@ -100,12 +94,6 @@ export const extraRouteArr = [
         name: '通知记录',
         url: '/account/NotificationRecord',
       },
-      // {
-      //   code: 'account/Center/bind',
-      //   name: '第三方页面',
-      //   url: '/account/center/bind',
-      //   hideInMenu: true,
-      // },
     ],
   },
 ];