Kaynağa Gözat

fix: bug#4625、4861

xieyonghong 3 yıl önce
ebeveyn
işleme
cc0ad3b838

+ 4 - 4
config/proxy.ts

@@ -12,11 +12,11 @@ export default {
       // target: 'http://192.168.32.44:8844/',
       // ws: 'ws://192.168.32.44:8844/',
       // 开发环境
-      // target: 'http://120.79.18.123:8844/',
-      // ws: 'ws://120.79.18.123:8844/',
+      target: 'http://120.79.18.123:8844/',
+      ws: 'ws://120.79.18.123:8844/',
       // 测试环境
-      target: 'http://120.77.179.54:8844/',
-      ws: 'ws://120.77.179.54:8844/',
+      // target: 'http://120.77.179.54:8844/',
+      // ws: 'ws://120.77.179.54:8844/',
       // target: 'http://192.168.66.5:8844/',
       // ws: 'ws://192.168.66.5:8844/',
       // ws: 'ws://demo.jetlinks.cn/jetlinks',

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

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