wzyyy 3 år sedan
förälder
incheckning
eee0c6adaa
32 ändrade filer med 338 tillägg och 217 borttagningar
  1. 1 1
      src/components/AMapComponent/hooks/Distance.tsx
  2. 10 5
      src/components/ProTableCard/CardItems/AlarmConfig.tsx
  3. 11 2
      src/components/ProTableCard/CardItems/noticeConfig.tsx
  4. 7 2
      src/components/ProTableCard/CardItems/noticeTemplate.tsx
  5. 12 0
      src/pages/Northbound/AliCloud/Detail/index.tsx
  6. 1 0
      src/pages/device/Instance/Detail/MetadataLog/Property/AMap.tsx
  7. 10 1
      src/pages/device/Instance/Detail/MetadataLog/Property/Charts.tsx
  8. 2 3
      src/pages/device/Instance/Detail/MetadataLog/Property/index.tsx
  9. 10 10
      src/pages/device/Instance/Detail/Running/Property/FileComponent/index.less
  10. 8 8
      src/pages/device/Instance/Detail/Running/Property/FileComponent/index.tsx
  11. 22 3
      src/pages/device/Instance/Detail/Running/Property/index.tsx
  12. 2 2
      src/pages/device/Instance/Detail/Tags/location/AMap.tsx
  13. 17 18
      src/pages/device/Instance/service.ts
  14. 11 5
      src/pages/device/Product/Detail/Access/AccessConfig/index.tsx
  15. 11 1
      src/pages/link/Certificate/index.tsx
  16. 9 6
      src/pages/media/Device/Channel/Live/index.tsx
  17. 16 13
      src/pages/media/Device/Channel/Save.tsx
  18. 84 72
      src/pages/media/Device/Playback/index.tsx
  19. 4 0
      src/pages/media/Device/Playback/service.ts
  20. 5 1
      src/pages/media/Device/Playback/timeLine.tsx
  21. 2 0
      src/pages/notice/Config/index.tsx
  22. 2 1
      src/pages/notice/Template/index.tsx
  23. 21 15
      src/pages/rule-engine/Alarm/Configuration/index.tsx
  24. 3 0
      src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx
  25. 0 1
      src/pages/rule-engine/Scene/Save/action/messageContent.tsx
  26. 23 18
      src/pages/rule-engine/Scene/Save/components/TimingTrigger/index.tsx
  27. 11 0
      src/pages/rule-engine/Scene/Save/index.tsx
  28. 3 6
      src/pages/rule-engine/Scene/Save/trigger/index.tsx
  29. 13 13
      src/pages/rule-engine/Scene/Save/trigger/operation.tsx
  30. 1 0
      src/pages/system/Platforms/Api/basePage.tsx
  31. 1 1
      src/pages/system/Platforms/Api/leftTree.tsx
  32. 5 9
      src/pages/system/Platforms/Api/swagger-ui/base.tsx

+ 1 - 1
src/components/AMapComponent/hooks/Distance.tsx

@@ -4,7 +4,7 @@ const useDistance = () => {
   const [distance, setDistance] = useState(0);
 
   const onDistance = (data: number[][]) => {
-    if (window.AMap && data && data.length > 2) {
+    if (window.AMap && data && data.length >= 2) {
       const pointArr = data.map((point) => new window.AMap.LngLat(point[0], point[1]));
       const distanceOfLine = AMap.GeometryUtil.distanceOfLine(pointArr);
       setDistance(Math.round(distanceOfLine));

+ 10 - 5
src/components/ProTableCard/CardItems/AlarmConfig.tsx

@@ -36,24 +36,29 @@ export default (props: AlarmConfigProps) => {
         </div>
         <div className={'card-item-body'}>
           <div className={'card-item-header'}>
-            <span className={'card-item-header-name ellipsis'}>{props?.name}</span>
+            <span className={'card-item-header-name ellipsis'}>
+              <Tooltip title={props?.name}>{props?.name}</Tooltip>
+            </span>
           </div>
           <div className={'card-item-content'}>
             <div>
               <label>关联场景联动</label>
-              <div>
+              <div className={'ellipsis'}>
                 <PermissionButton
                   type={'link'}
                   isPermission={!!getMenuPathByCode(MENUS_CODE['rule-engine/Scene'])}
                   style={{ padding: 0, height: 'auto' }}
+                  tooltip={{
+                    title: !!getMenuPathByCode(MENUS_CODE['rule-engine/Scene'])
+                      ? props?.sceneName
+                      : '没有权限,请联系管理员',
+                  }}
                   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>
+                  {props?.sceneName || ''}
                 </PermissionButton>
               </div>
             </div>

+ 11 - 2
src/components/ProTableCard/CardItems/noticeConfig.tsx

@@ -3,6 +3,7 @@ import { TableCard } from '@/components';
 import '@/style/common.less';
 import '../index.less';
 import { imgMap, typeList } from './noticeTemplate';
+import { Tooltip } from 'antd';
 
 export interface NoticeCardProps extends ConfigItem {
   detail?: React.ReactNode;
@@ -19,7 +20,11 @@ export default (props: NoticeCardProps) => {
         </div>
         <div className={'card-item-body'}>
           <div className={'card-item-header'}>
-            <span className={'card-item-header-name ellipsis'}>{props.name}</span>
+            <span className={'card-item-header-name ellipsis'}>
+              <Tooltip placement="topLeft" title={props.name}>
+                {props.name}
+              </Tooltip>
+            </span>
           </div>
           <div className={'card-item-content'}>
             <div>
@@ -28,7 +33,11 @@ export default (props: NoticeCardProps) => {
             </div>
             <div>
               <label>说明</label>
-              <div className={'ellipsis'}>{props.description}</div>
+              <div className={'ellipsis'}>
+                <Tooltip placement="topLeft" title={props.description}>
+                  {props.description}
+                </Tooltip>
+              </div>
             </div>
           </div>
         </div>

+ 7 - 2
src/components/ProTableCard/CardItems/noticeTemplate.tsx

@@ -2,6 +2,7 @@ import React from 'react';
 import { TableCard } from '@/components';
 import '@/style/common.less';
 import '../index.less';
+import { Tooltip } from 'antd';
 
 export interface NoticeCardProps extends TemplateItem {
   detail?: React.ReactNode;
@@ -64,7 +65,9 @@ export default (props: NoticeCardProps) => {
         </div>
         <div className={'card-item-body'}>
           <div className={'card-item-header'}>
-            <span className={'card-item-header-name ellipsis'}>{props.name}</span>
+            <span className={'card-item-header-name ellipsis'}>
+              <Tooltip title={props.name}>{props.name}</Tooltip>
+            </span>
           </div>
           <div className={'card-item-content'}>
             <div>
@@ -73,7 +76,9 @@ export default (props: NoticeCardProps) => {
             </div>
             <div>
               <label>说明</label>
-              <div className={'ellipsis'}>{props.description}</div>
+              <div className={'ellipsis'}>
+                <Tooltip title={props.description}>{props.description}</Tooltip>
+              </div>
             </div>
           </div>
         </div>

+ 12 - 0
src/pages/Northbound/AliCloud/Detail/index.tsx

@@ -196,6 +196,18 @@ const Detail = observer(() => {
               },
             ],
           },
+          instanceId: {
+            type: 'string',
+            title: '实例ID',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入实例ID',
+            },
+            'x-decorator-props': {
+              tooltip: '阿里云物联网平台中的实例ID,没有则不填',
+            },
+          },
           accessKeyId: {
             type: 'string',
             title: 'accessKey',

+ 1 - 0
src/pages/device/Instance/Detail/MetadataLog/Property/AMap.tsx

@@ -49,6 +49,7 @@ export default (props: Props) => {
             type="primary"
             onClick={() => {
               if (PathNavigatorRef.current) {
+                PathNavigatorRef.current.moveToPoint(0, 0);
                 PathNavigatorRef.current.stop();
               }
             }}

+ 10 - 1
src/pages/device/Instance/Detail/MetadataLog/Property/Charts.tsx

@@ -1,3 +1,4 @@
+import { Empty } from 'antd';
 import * as echarts from 'echarts';
 import _ from 'lodash';
 import { useEffect, useRef } from 'react';
@@ -58,7 +59,15 @@ const Charts = (props: Props) => {
     }
   }, [props.data]);
 
-  return <div id="charts" style={{ width: '100%', height: 500 }}></div>;
+  return (
+    <div>
+      {Array.isArray(props?.data) && props?.data.length > 2 ? (
+        <div id="charts" style={{ width: '100%', height: 500 }}></div>
+      ) : (
+        <Empty />
+      )}
+    </div>
+  );
 };
 
 export default Charts;

+ 2 - 3
src/pages/device/Instance/Detail/MetadataLog/Property/index.tsx

@@ -187,7 +187,6 @@ const PropertyLog = (props: Props) => {
         value: undefined,
         type: data?.name || '',
       });
-      console.log(dataList.length);
       setChartsList(dataList || []);
     }
   };
@@ -199,7 +198,7 @@ const PropertyLog = (props: Props) => {
     if (resp.status === 200) {
       const dataList: any[] = [
         {
-          year: start,
+          year: end,
           value: undefined,
           type: data?.name || '',
         },
@@ -213,7 +212,7 @@ const PropertyLog = (props: Props) => {
         });
       });
       dataList.push({
-        year: end,
+        year: start,
         value: undefined,
         type: data?.name || '',
       });

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

@@ -2,27 +2,27 @@
   display: flex;
   align-items: center;
   width: 100%;
-  max-height: 60px;
 
-  .other {
+  .cardValue {
+    display: flex;
+    align-items: center;
     width: 100%;
+    height: 60px;
     overflow: hidden;
     color: #323130;
     font-weight: 700;
     font-size: 24px;
     white-space: nowrap;
     text-overflow: ellipsis;
-  }
 
-  .img {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    width: 60px;
-    height: 100%;
+    img {
+      width: 60px;
+    }
+  }
 
+  .otherValue {
     img {
-      width: 100%;
+      width: 40px;
     }
   }
 }

+ 8 - 8
src/pages/device/Instance/Detail/Running/Property/FileComponent/index.tsx

@@ -33,14 +33,14 @@ const FileComponent = (props: Props) => {
 
   const renderValue = () => {
     if (value?.formatValue !== 0 && !value?.formatValue) {
-      return <div className={props.type === 'card' ? styles.other : {}}>--</div>;
+      return <div className={props.type === 'card' ? styles.cardValue : styles.otherValue}>--</div>;
     } else if (data?.valueType?.type === 'file') {
       if (
         data?.valueType?.fileType === 'base64' ||
         data?.valueType?.fileType === 'Binary(二进制)'
       ) {
         return (
-          <div className={props.type === 'card' ? styles.other : {}}>
+          <div className={props.type === 'card' ? styles.cardValue : styles.otherValue}>
             <Tooltip placement="topLeft" title={String(value?.formatValue)}>
               {String(value?.formatValue)}
             </Tooltip>
@@ -51,7 +51,7 @@ const FileComponent = (props: Props) => {
         // 图片
         return (
           <div
-            className={styles.img}
+            className={props.type === 'card' ? styles.cardValue : styles.otherValue}
             onClick={() => {
               if (isHttps && value?.formatValue.indexOf('http:') !== -1) {
                 message.error('域名为https时,不支持访问http地址');
@@ -80,7 +80,7 @@ const FileComponent = (props: Props) => {
       ) {
         return (
           <div
-            className={styles.img}
+            className={props.type === 'card' ? styles.cardValue : styles.otherValue}
             onClick={() => {
               if (isHttps && value?.formatValue.indexOf('http:') !== -1) {
                 message.error('域名为https时,不支持访问http地址');
@@ -107,20 +107,20 @@ const FileComponent = (props: Props) => {
             value?.formatValue.includes(item),
           ) || '';
         return (
-          <div className={styles.img}>
+          <div className={props.type === 'card' ? styles.cardValue : styles.otherValue}>
             <img src={imgMap.get(flag.slice(1))} />
           </div>
         );
       } else {
         return (
-          <div className={styles.img}>
+          <div className={props.type === 'card' ? styles.cardValue : styles.otherValue}>
             <img src={imgMap.get('other')} />
           </div>
         );
       }
     } else if (data?.valueType?.type === 'object' || data?.valueType?.type === 'geoPoint') {
       return (
-        <div className={props.type === 'card' ? styles.other : {}}>
+        <div className={props.type === 'card' ? styles.cardValue : styles.otherValue}>
           <Tooltip placement="topLeft" title={JSON.stringify(value?.formatValue)}>
             {JSON.stringify(value?.formatValue)}
           </Tooltip>
@@ -128,7 +128,7 @@ const FileComponent = (props: Props) => {
       );
     } else {
       return (
-        <div className={props.type === 'card' ? styles.other : {}}>
+        <div className={props.type === 'card' ? styles.cardValue : styles.otherValue}>
           <Tooltip placement="topLeft" title={String(value?.formatValue)}>
             {String(value?.formatValue)}
           </Tooltip>

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

@@ -11,7 +11,7 @@ import EditProperty from './EditProperty';
 import { useParams } from 'umi';
 import PropertyLog from '../../MetadataLog/Property';
 import styles from './index.less';
-import { throttle } from 'lodash';
+import { groupBy, throttle, toArray } from 'lodash';
 import PropertyTable from './PropertyTable';
 
 interface Props {
@@ -166,9 +166,28 @@ const Property = (props: Props) => {
     setLoading(true);
     service.propertyRealTime(param).subscribe({
       next: (resp) => {
+        if (resp.status === 200) {
+          const t1 = (resp?.result || []).map((item: any) => {
+            return {
+              timeString: item.data?.timeString,
+              timestamp: item.data?.timestamp,
+              ...item?.data?.value,
+            };
+          });
+          const obj: any = {};
+          toArray(groupBy(t1, 'property'))
+            .map((item) => {
+              return {
+                list: item.sort((a, b) => b.timestamp - a.timestamp),
+                property: item[0].property,
+              };
+            })
+            .forEach((i) => {
+              obj[i.property] = i.list[0];
+            });
+          setPropertyValue({ ...propertyValue, ...obj });
+        }
         setLoading(false);
-        propertyValue[resp.property] = resp.list[0];
-        setPropertyValue({ ...propertyValue });
       },
     });
   };

+ 2 - 2
src/pages/device/Instance/Detail/Tags/location/AMap.tsx

@@ -24,8 +24,8 @@ export default (props: Props) => {
   };
 
   useEffect(() => {
-    setValue(props.value);
-    const list = props?.value.split(',') || [];
+    setValue(props?.value || '');
+    const list = (props?.value || '').split(',') || [];
     if (!!props.value && list.length === 2) {
       setMarkerCenter({
         longitude: list[0],

+ 17 - 18
src/pages/device/Instance/service.ts

@@ -1,9 +1,9 @@
 import BaseService from '@/utils/BaseService';
 import { request } from 'umi';
-import type { DeviceInstance, PropertyData } from '@/pages/device/Instance/typings';
+import type { DeviceInstance } from '@/pages/device/Instance/typings';
 import SystemConst from '@/utils/const';
-import { defer, from, mergeMap, toArray } from 'rxjs';
-import { filter, groupBy, map } from 'rxjs/operators';
+import { defer, from } from 'rxjs';
+import { map } from 'rxjs/operators';
 
 class Service extends BaseService<DeviceInstance> {
   public detail = (id: string) => request(`${this.uri}/${id}/detail`, { method: 'GET' });
@@ -82,21 +82,20 @@ class Service extends BaseService<DeviceInstance> {
           data,
         }),
       ).pipe(
-        filter((resp) => resp.status === 200),
-        map((resp) => resp.result),
-        mergeMap((temp: PropertyData[]) => from(temp)),
-        map((item) => ({
-          timeString: item.data.timeString,
-          timestamp: item.data.timestamp,
-          ...item.data.value,
-        })),
-        groupBy((group$) => group$.property),
-        mergeMap((group) => group.pipe(toArray())),
-        map((arr) => ({
-          list: arr.sort((a, b) => a.timestamp - b.timestamp),
-          property: arr[0].property,
-        })),
-        // toArray()
+        // filter((resp) => resp.status === 200),
+        map((resp) => resp),
+        // mergeMap((temp: PropertyData[]) => from(temp)),
+        // map((item) => ({
+        //   timeString: item.data.timeString,
+        //   timestamp: item.data.timestamp,
+        //   ...item.data.value,
+        // })),
+        // groupBy((group$) => group$.property),
+        // mergeMap((group) => group.pipe(toArray())),
+        // map((arr) => ({
+        //   list: arr.sort((a, b) => a.timestamp - b.timestamp),
+        //   property: arr[0].property,
+        // }))
       ),
     );
 

+ 11 - 5
src/pages/device/Product/Detail/Access/AccessConfig/index.tsx

@@ -1,5 +1,5 @@
 import { useEffect, useState } from 'react';
-import { Button, Col, message, Modal, Pagination, Row } from 'antd';
+import { Col, message, Modal, Pagination, Row } from 'antd';
 import { service } from '@/pages/link/AccessConfig';
 import { productModel } from '@/pages/device/Product';
 import SearchComponent from '@/components/SearchComponent';
@@ -8,6 +8,8 @@ import styles from './index.less';
 import Service from '@/pages/device/Product/service';
 
 import AccessConfigCard from '@/components/ProTableCard/CardItems/AccessConfig';
+import { getMenuPathByCode } from '@/utils/menu';
+import PermissionButton from '@/components/PermissionButton';
 
 interface Props {
   close: () => void;
@@ -17,6 +19,7 @@ interface Props {
 const AccessConfig = (props: Props) => {
   const { close } = props;
   const service1 = new Service('device-product');
+  const { permission } = PermissionButton.usePermission('link/AccessConfig');
 
   const [dataSource, setDataSource] = useState<any>({
     data: [],
@@ -127,19 +130,22 @@ const AccessConfig = (props: Props) => {
           }}
         />
         <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
-          <Button
-            type="primary"
+          <PermissionButton
+            isPermission={permission.add}
             onClick={() => {
-              const tab: any = window.open(`${origin}/#/link/AccessConfig/Detail`);
+              const url = getMenuPathByCode('link/AccessConfig/Detail');
+              const tab: any = window.open(`${origin}/#${url}`);
               tab!.onTabSaveSuccess = (value: any) => {
                 if (value.status === 200) {
                   handleSearch(param);
                 }
               };
             }}
+            key="button"
+            type="primary"
           >
             新增
-          </Button>
+          </PermissionButton>
         </div>
       </div>
       <Row gutter={[16, 16]}>

+ 11 - 1
src/pages/link/Certificate/index.tsx

@@ -2,7 +2,7 @@ import { PageContainer } from '@ant-design/pro-layout';
 import { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { message } from 'antd';
+import { message, Tooltip } from 'antd';
 import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import SearchComponent from '@/components/SearchComponent';
@@ -29,10 +29,20 @@ const Certificate = () => {
     {
       dataIndex: 'name',
       title: '证书名称',
+      width: '30%',
+      ellipsis: true,
     },
     {
       dataIndex: 'description',
       title: '说明',
+      width: '30%',
+      render: (text: any) => (
+        <div style={{ width: '100%' }} className="ellipsis">
+          <Tooltip placement="topLeft" title={text}>
+            {text}
+          </Tooltip>
+        </div>
+      ),
     },
     {
       title: intl.formatMessage({

+ 9 - 6
src/pages/media/Device/Channel/Live/index.tsx

@@ -16,7 +16,7 @@ interface LiveProps {
 const LiveFC = (props: LiveProps) => {
   const [mediaType, setMediaType] = useState('mp4');
   const [url, setUrl] = useState('');
-  const [isRecord, setIsRecord] = useState(false);
+  const [isRecord, setIsRecord] = useState(0); // 0:停止录像; 1:请求录像中; 2:开始录像
 
   const mediaStart = useCallback(
     async (type) => {
@@ -56,24 +56,27 @@ const LiveFC = (props: LiveProps) => {
             <div
               className={'tool-item'}
               onClick={async () => {
-                if (isRecord) {
+                if (isRecord === 0) {
+                  setIsRecord(1);
                   const resp = await service.recordStop(props.deviceId, props.channelId, {
                     local: false,
                   });
                   if (resp.status === 200) {
-                    setIsRecord(!isRecord);
+                    setIsRecord(2);
+                  } else {
+                    setIsRecord(0);
                   }
-                } else {
+                } else if (isRecord === 2) {
                   const resp = await service.recordStart(props.deviceId, props.channelId, {
                     local: false,
                   });
                   if (resp.status === 200) {
-                    setIsRecord(!isRecord);
+                    setIsRecord(0);
                   }
                 }
               }}
             >
-              {isRecord ? '停止录像' : '开始录像'}
+              {isRecord === 0 ? '开始录像' : isRecord === 1 ? '请求录像中' : '停止录像'}
             </div>
             <div className={'tool-item'}>刷新</div>
             <div

+ 16 - 13
src/pages/media/Device/Channel/Save.tsx

@@ -99,19 +99,6 @@ 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,
@@ -128,6 +115,22 @@ const Save = (props: SaveModalProps) => {
             max: 128,
             message: '最多可输入128个字符',
           },
+          {
+            validator: (value: string) => {
+              const reg = /(http|https|rtsp|rtmp):\/\/([\w.]+\/?)\S*/;
+              return new Promise((resolve) => {
+                if (!value) {
+                  resolve('');
+                  return;
+                }
+                if (reg.test(value)) {
+                  resolve('');
+                } else {
+                  resolve('请输入正确的视频地址');
+                }
+              });
+            },
+          },
         ],
       },
       address: {

+ 84 - 72
src/pages/media/Device/Playback/index.tsx

@@ -14,6 +14,7 @@ import {
   CloudDownloadOutlined,
   DownloadOutlined,
   EyeOutlined,
+  LoadingOutlined,
   PauseCircleOutlined,
   PlayCircleOutlined,
 } from '@ant-design/icons';
@@ -21,6 +22,62 @@ import TimeLine from './timeLine';
 
 const service = new Service('media');
 
+interface IconNodeType {
+  type: string;
+  item: recordsItemType;
+  onCloudView: (startTime: number, endTime: number) => void;
+  onDownLoad: () => void;
+}
+
+const IconNode = (props: IconNodeType) => {
+  const [status, setStatus] = useState(props.item?.isServer ? 2 : 0); // type 为local时有效,0:未下载; 1:下载中:2:已下载
+
+  const getLocalIcon = (s: number) => {
+    if (s === 0) {
+      return <CloudDownloadOutlined />;
+    } else if (s === 2) {
+      return <EyeOutlined />;
+    } else {
+      return <LoadingOutlined />;
+    }
+  };
+
+  const Icon = props.type === 'local' ? getLocalIcon(status) : <DownloadOutlined />;
+
+  const downLoadCloud = (item: recordsItemType) => {
+    setStatus(1);
+    service
+      .downloadRecord(item.deviceId, item.channelId, {
+        local: false,
+        downloadSpeed: 4,
+        startTime: item.startTime,
+        endTime: item.endTime,
+      })
+      .then((res) => {
+        setStatus(res.status === 200 ? 2 : 0);
+      });
+  };
+  return (
+    <a
+      onClick={() => {
+        if (props.type === 'local') {
+          if (status === 2) {
+            // 已下载,进行跳转
+            props.onCloudView(props.item.startTime, props.item.endTime);
+          } else if (status === 0) {
+            // 未下载 进行下载
+            downLoadCloud(props.item);
+          }
+        } else {
+          props.onDownLoad();
+        }
+      }}
+    >
+      {Icon}
+    </a>
+  );
+};
+
 export default () => {
   const [url, setUrl] = useState('');
   const [type, setType] = useState('local');
@@ -48,26 +105,29 @@ export default () => {
         startTime: date.format('YYYY-MM-DD 00:00:00'),
         endTime: date.format('YYYY-MM-DD 23:59:59'),
       };
-      const list: recordsItemType[] = [];
       const localResp = await service.queryRecordLocal(deviceId, channelId, params);
-
       if (localResp.status === 200 && localResp.result.length) {
         const serviceResp = await service.recordsInServer(deviceId, channelId, {
           ...params,
           includeFiles: false,
         });
+        let newList: recordsItemType[] = serviceResp.result;
+
         if (serviceResp.status === 200 && serviceResp.result) {
-          const newList = list.map((item) => {
+          // 判断是否已下载云端视频
+          newList = localResp.result.map((item: recordsItemType) => {
             return {
               ...item,
               isServer: serviceResp.result.some(
-                (serverFile: any) => serverFile.streamStartTime === item.startTime,
+                (serverFile: any) =>
+                  item.startTime >= serverFile.streamStartTime &&
+                  serverFile.streamEndTime <= item.endTime,
               ),
             };
           });
           setHistoryList(newList);
         } else {
-          setHistoryList(list);
+          setHistoryList(newList);
         }
       } else {
         setHistoryList([]);
@@ -93,23 +153,6 @@ export default () => {
     }
   };
 
-  const downLoadCloud = useCallback(
-    (item: recordsItemType) => {
-      setHistoryList(
-        historyList.map((historyItem) => {
-          if (historyItem.startTime === item.startTime) {
-            return {
-              ...item,
-              isServer: true,
-            };
-          }
-          return item;
-        }),
-      );
-    },
-    [historyList],
-  );
-
   const cloudView = useCallback((startTime: number, endTime: number) => {
     setType('cloud');
     setCloudTime({
@@ -130,53 +173,6 @@ export default () => {
     document.body.removeChild(downNode);
   };
 
-  const DownloadIcon = useCallback(
-    (item: recordsItemType) => {
-      let title = '下载到云端';
-      let IconNode = (
-        <a
-          onClick={() => {
-            downLoadCloud(item);
-          }}
-        >
-          <CloudDownloadOutlined />
-        </a>
-      );
-      if (type === 'local') {
-        if (item.isServer) {
-          title = '查看';
-          IconNode = (
-            <a
-              onClick={() => {
-                cloudView(item.startTime, item.endTime);
-              }}
-            >
-              <EyeOutlined />
-            </a>
-          );
-        }
-      } else {
-        title = '下载录像文件';
-        IconNode = (
-          <a
-            onClick={() => {
-              downloadClick(item);
-            }}
-            download
-          >
-            <DownloadOutlined />
-          </a>
-        );
-      }
-
-      return {
-        title,
-        IconNode,
-      };
-    },
-    [type],
-  );
-
   useEffect(() => {
     setTime(moment(new Date()));
     queryLocalRecords(moment(new Date()));
@@ -285,7 +281,15 @@ export default () => {
                     'HH:mm:ss',
                   );
                   const endTime = moment(item.endTime || item.mediaEndTime).format('HH:mm:ss');
-                  const downloadObj = DownloadIcon(item);
+                  let title = '下载到云端';
+
+                  if (type === 'local') {
+                    if (item.isServer) {
+                      title = '查看';
+                    }
+                  } else {
+                    title = '下载录像文件';
+                  }
                   return (
                     <List.Item
                       actions={[
@@ -319,8 +323,16 @@ export default () => {
                             )}
                           </a>
                         </Tooltip>,
-                        <Tooltip key={'download'} title={downloadObj.title}>
-                          {downloadObj.IconNode}
+
+                        <Tooltip key={'download'} title={title}>
+                          <IconNode
+                            type={type}
+                            item={item}
+                            onCloudView={cloudView}
+                            onDownLoad={() => {
+                              downloadClick(item);
+                            }}
+                          />
                         </Tooltip>,
                       ]}
                     >

+ 4 - 0
src/pages/media/Device/Playback/service.ts

@@ -22,6 +22,10 @@ class Service extends BaseService<recordsItemType> {
       data,
     });
 
+  // 下载到云端
+  downloadRecord = (deviceId: string, channelId: string, data: any) =>
+    request(`${this.uri}/device/${deviceId}/${channelId}/_record`, { method: 'POST', data });
+
   // 播放本地回放
   playbackLocal = (
     deviceId: string,

+ 5 - 1
src/pages/media/Device/Playback/timeLine.tsx

@@ -185,7 +185,11 @@ const Progress = forwardRef((props: Props, ref) => {
     <div className={'time-line-warp'}>
       <div className={'time-line-clock'}>
         {Array.from(Array(25), (v, k) => k).map((item) => {
-          return <div key={item}>{item}</div>;
+          return (
+            <div key={item} style={{ width: 12 }}>
+              {item}
+            </div>
+          );
         })}
       </div>
       <div className={'time-line-content'} ref={LineContent}>

+ 2 - 0
src/pages/notice/Config/index.tsx

@@ -54,6 +54,7 @@ const Config = observer(() => {
     {
       dataIndex: 'name',
       title: '配置名称',
+      ellipsis: true,
     },
     {
       dataIndex: 'provider',
@@ -62,6 +63,7 @@ const Config = observer(() => {
     },
     {
       dataIndex: 'description',
+      ellipsis: true,
       title: '说明',
     },
     {

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

@@ -50,6 +50,7 @@ const Template = observer(() => {
     {
       dataIndex: 'name',
       title: '模版名称',
+      ellipsis: true,
     },
     {
       dataIndex: 'provider',
@@ -59,7 +60,7 @@ const Template = observer(() => {
     {
       dataIndex: 'description',
       title: '说明',
-      // valueType: 'dateTime',
+      ellipsis: true,
     },
     {
       title: intl.formatMessage({

+ 21 - 15
src/pages/rule-engine/Alarm/Configuration/index.tsx

@@ -35,6 +35,7 @@ const Configuration = () => {
     {
       dataIndex: 'name',
       title: '名称',
+      ellipsis: true,
     },
     {
       title: '类型',
@@ -62,17 +63,23 @@ const Configuration = () => {
     {
       title: '关联场景联动',
       dataIndex: 'sceneName',
+      width: 160,
       render: (text: any, record: any) => (
         <PermissionButton
           type={'link'}
           isPermission={!!getMenuPathByCode(MENUS_CODE['rule-engine/Scene'])}
-          style={{ padding: 0, height: 'auto' }}
+          style={{ padding: 0, height: 'auto', width: '100%' }}
+          tooltip={{
+            title: !!getMenuPathByCode(MENUS_CODE['rule-engine/Scene'])
+              ? text
+              : '没有权限,请联系管理员',
+          }}
           onClick={() => {
             const url = getMenuPathByCode('rule-engine/Scene/Save');
             history.push(`${url}?id=${record.sceneId}`);
           }}
         >
-          {text}
+          <span className="ellipsis">{text}</span>
         </PermissionButton>
       ),
     },
@@ -86,6 +93,7 @@ const Configuration = () => {
     {
       title: '说明',
       dataIndex: 'description',
+      ellipsis: true,
     },
     {
       title: '操作',
@@ -143,12 +151,11 @@ const Configuration = () => {
           key="action"
           style={{ padding: 0 }}
           popConfirm={{
-            title: intl.formatMessage({
-              id: `pages.data.option.${
-                record.state?.value !== 'disabled' ? 'disabled' : 'disabled'
-              }.tips`,
-              defaultMessage: `确认${record.state?.value !== 'disabled' ? '禁用' : '启用'}?`,
-            }),
+            title: `${
+              record.state?.value !== 'disabled'
+                ? '禁用告警不会影响关联的场景状态,确定要禁用吗'
+                : '确认启用'
+            }?`,
             onConfirm: async () => {
               if (record.state?.value === 'disabled') {
                 await service._enable(record.id);
@@ -174,7 +181,7 @@ const Configuration = () => {
           }}
           type="link"
         >
-          {record.state?.value === 'disabled' ? <CloseCircleOutlined /> : <PlayCircleOutlined />}
+          {record.state?.value === 'disabled' ? <PlayCircleOutlined /> : <CloseCircleOutlined />}
         </PermissionButton>,
         <PermissionButton
           type="link"
@@ -274,12 +281,11 @@ const Configuration = () => {
                 isPermission={permission.action}
                 style={{ padding: 0 }}
                 popConfirm={{
-                  title: intl.formatMessage({
-                    id: `pages.data.option.${
-                      record.state?.value !== 'disabled' ? 'disabled' : 'enabled'
-                    }.tips`,
-                    defaultMessage: `确认${record.state?.value !== 'disabled' ? '禁用' : '启用'}?`,
-                  }),
+                  title: `${
+                    record.state?.value !== 'disabled'
+                      ? '禁用告警不会影响关联的场景状态,确定要禁用吗'
+                      : '确认启用'
+                  }?`,
                   onConfirm: async () => {
                     if (record.state?.value === 'disabled') {
                       await service._enable(record.id);

+ 3 - 0
src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx

@@ -156,6 +156,9 @@ export default (props: WritePropertyProps) => {
               options={builtInList}
               fieldNames={{ label: 'name', value: 'id' }}
               placeholder={'请选择参数'}
+              onSelect={(value: any) => {
+                onChange(propertiesKey, value);
+              }}
             />
           ) : (
             <div>{inputNodeByType(propertiesType)}</div>

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

@@ -29,7 +29,6 @@ export default (props: MessageContentProps) => {
         if (type === 'user') {
           rules.push({
             validator: async (_: any, value: any) => {
-              console.log('user', value);
               if (!value) {
                 return Promise.reject(new Error('请选择' + item.name));
               } else {

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

@@ -1,6 +1,6 @@
 import { Col, Input, InputNumber, Row, Select, TimePicker } from 'antd';
 import { ItemGroup, TimeSelect } from '@/pages/rule-engine/Scene/Save/components';
-import { useCallback, useEffect, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
 import { omit } from 'lodash';
 import moment from 'moment';
 
@@ -45,6 +45,7 @@ const DefaultValue = {
 
 export default (props: TimingTrigger) => {
   const [data, setData] = useState<TimerType>(DefaultValue);
+  const timePickerRef = useRef(null);
 
   useEffect(() => {
     setData(props.value || DefaultValue);
@@ -190,23 +191,27 @@ export default (props: TimingTrigger) => {
                 onSelect={type2Select}
               />
               {data.mod === PeriodModEnum.period ? (
-                <TimePicker.RangePicker
-                  format={'HH:mm:ss'}
-                  value={[
-                    moment(data.period?.from || new Date(), 'HH:mm:ss'),
-                    moment(data.period?.to || new Date(), 'hh:mm:ss'),
-                  ]}
-                  onChange={(_, dateString) => {
-                    onChange({
-                      ...data,
-                      period: {
-                        ...data.period,
-                        from: dateString[0],
-                        to: dateString[1],
-                      },
-                    });
-                  }}
-                />
+                <div ref={timePickerRef}>
+                  <TimePicker.RangePicker
+                    format={'HH:mm:ss'}
+                    value={[
+                      moment(data.period?.from || new Date(), 'HH:mm:ss'),
+                      moment(data.period?.to || new Date(), 'hh:mm:ss'),
+                    ]}
+                    getPopupContainer={() => timePickerRef.current!}
+                    onChange={(_, dateString) => {
+                      onChange({
+                        ...data,
+                        period: {
+                          ...data.period,
+                          from: dateString[0],
+                          to: dateString[1],
+                        },
+                      });
+                    }}
+                    style={{ width: '100%' }}
+                  />
+                </div>
               ) : (
                 <TimePicker
                   format={'HH:mm:ss'}

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

@@ -232,6 +232,17 @@ export default () => {
                   setRequestParams({ trigger: allValues.trigger });
                   setTriggerDatas(allValues.trigger);
                 }
+
+                if (
+                  hasKeyInObject(['productId'], changeValue.trigger.device) ||
+                  (changeValue.trigger.device.operation &&
+                    hasKeyInObject(
+                      ['operator', 'eventId', 'functionId'],
+                      changeValue.trigger.device.operation,
+                    ))
+                ) {
+                  setActionParams({ trigger: allValues.trigger }); // 用于内置参数请求
+                }
               } else if (['timer', 'manual'].includes(changeValue.trigger.type)) {
                 setActionParams({ trigger: allValues.trigger }); // 用于内置参数请求
               }

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

@@ -40,7 +40,7 @@ export default observer((props: TriggerProps) => {
   const [productId, setProductId] = useState('');
   const [selector, setSelector] = useState('fixed');
 
-  const [operatorOptions, setOperatorOptions] = useState<any[]>([]);
+  const [operatorOptions, setOperatorOptions] = useState<any[] | undefined>(undefined);
 
   const [properties, setProperties] = useState<any[]>([]); // 属性
   const [events, setEvents] = useState([]); // 事件
@@ -129,11 +129,8 @@ export default observer((props: TriggerProps) => {
     getProducts();
   }, []);
 
-  console.log('triggerModel', selector);
-
   useEffect(() => {
     const triggerData = props.value;
-    console.log('triggerData', triggerData);
     if (triggerData && triggerData.device) {
       const _device = triggerData.device;
 
@@ -231,7 +228,7 @@ export default observer((props: TriggerProps) => {
           </Col>
         )}
         <Col span={6}>
-          {functions.length || events.length || properties.length ? (
+          {operatorOptions && (
             <Form.Item
               name={['trigger', 'device', 'operation', 'operator']}
               initialValue={undefined}
@@ -243,7 +240,7 @@ export default observer((props: TriggerProps) => {
                 style={{ width: '100%' }}
               />
             </Form.Item>
-          ) : null}
+          )}
         </Col>
       </Row>
       {FormModel.trigger?.device?.operation?.operator === OperatorEnum.invokeFunction ||

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

@@ -65,6 +65,7 @@ export default (props: OperatorProps) => {
 
   useEffect(() => {
     if (props.value && props.propertiesList?.length) {
+      console.log(Object.keys(props.value));
       const _key = Object.keys(props.value);
       setKey(_key);
       setData(objToArray(props.value));
@@ -90,20 +91,19 @@ export default (props: OperatorProps) => {
           style={{ width: '100%' }}
           placeholder={'请选择属性'}
           onSelect={(id: any) => {
-            if (props.value) {
-              const _value: any = { ...props.value };
-              if (id in props.value) {
-                delete _value[id];
-              } else {
-                _value[id] = undefined;
-              }
-              if (props.onChange) {
-                props.onChange(_value!);
-              }
+            if (props.onChange) {
+              props.onChange({ [id]: undefined });
+            }
+          }}
+          onDeselect={(id: any) => {
+            const _value: any = { ...props.value };
+            if (id in props.value) {
+              delete _value[id];
             } else {
-              if (props.onChange) {
-                props.onChange({ [id]: undefined });
-              }
+              _value[id] = undefined;
+            }
+            if (props.onChange) {
+              props.onChange(_value!);
             }
           }}
         />

+ 1 - 0
src/pages/system/Platforms/Api/basePage.tsx

@@ -117,6 +117,7 @@ export default (props: TableProps) => {
                   type={'link'}
                   style={{ padding: 0 }}
                   onClick={() => {
+                    console.log(record);
                     ApiModel.swagger = record;
                     ApiModel.showTable = false;
                   }}

+ 1 - 1
src/pages/system/Platforms/Api/leftTree.tsx

@@ -88,7 +88,7 @@ export default (props: LeftTreeType) => {
     return new Promise(async (resolve) => {
       const resp = await service.getApiNextLevel(name);
       if (resp) {
-        ApiModel.components = resp.components;
+        ApiModel.components = { ...ApiModel.components, ...resp.components.schemas };
         const handleData = handleTreeData(resp);
         setTreeData((origin) => {
           const data = updateTreeData(origin, key, handleData);

+ 5 - 9
src/pages/system/Platforms/Api/swagger-ui/base.tsx

@@ -77,7 +77,8 @@ export default observer(() => {
         const entityName = refUrl.split('/').pop();
         const entityType = ObjectFindValue('type', ApiModel.swagger.requestBody.content);
         const entityRequired = ApiModel.swagger.requestBody.required;
-        const entity: any = ApiModel.components.schemas[entityName];
+        console.log(entityName);
+        const entity: any = ApiModel.components[entityName];
         const file = ObjectFindValue('file', ApiModel.swagger.requestBody.content);
         // 是否为文件上传
         if (file && isObject(file)) {
@@ -104,13 +105,11 @@ export default observer(() => {
 
   const handleEntity = (entityData: any): any => {
     let newEntity = {};
-    console.log(entityData);
     if (isArray(entityData)) {
       newEntity = [handleEntity(entityData[0])];
     } else if (isObject(entityData)) {
       Object.keys(entityData).forEach((key) => {
         const type = entityData[key].type;
-        console.log(entityData[key]);
         if (type) {
           if (type.includes('integer')) {
             newEntity[key] = 0;
@@ -132,12 +131,11 @@ export default observer(() => {
   };
 
   const getResult = (name: string, oldName: string = '') => {
-    const entity = cloneDeep(ApiModel.components.schemas[name].properties);
-    console.log(entity);
     if (name === oldName) {
       // 禁止套娃
       return [];
     }
+    const entity = cloneDeep(ApiModel.components[name].properties);
     Object.keys(entity).forEach((key) => {
       const type = entity[key].type;
       if ((entity[key].items && entity[key].items.$ref) || entity[key].$ref) {
@@ -162,12 +160,11 @@ export default observer(() => {
   };
 
   const handleResponseParam = (name: any, oldName: string = ''): any[] => {
-    if (!ApiModel.components.schemas[name]) {
+    if (!ApiModel.components[name]) {
       return [];
     }
 
-    const entity = cloneDeep(ApiModel.components.schemas[name].properties);
-    console.log(entity);
+    const entity = cloneDeep(ApiModel.components[name].properties);
 
     const newArr: any[] = [];
     if (name === oldName) {
@@ -323,7 +320,6 @@ export default observer(() => {
           {responseData
             .filter((item) => item.code !== '400')
             .map((item) => {
-              console.log(item);
               return (
                 <Tabs.TabPane key={item.code} tab={item.code}>
                   <div>