Parcourir la source

Merge branch 'wyf-nvr' into 'dev'

NVR回放bug和样式修改

See merge request skyeye/skyeye_frontend/skyeye-admin!81
航飞 楼 il y a 1 an
Parent
commit
de80b7047c

+ 1 - 12
.env.development

@@ -20,18 +20,7 @@ VITE_DROP_CONSOLE = true
 # VITE_PROXY=[["/skyeye-admin-api","http://192.168.13.68/skyeye-admin-api"],[],["/eye_api_bak","http://192.168.13.68/eye_api"],["/push_stream_host","http://192.168.13.68/push_stream_host"],["/skyeye-login","http://192.168.13.68/skyeye-login"],["/ws_api_bak","ws://192.168.13.68/ws_api_bak"]]
 # 中建材 staff
 #VITE_PROXY=[["/skyeye-admin-api","http://192.168.13.68:70/skyeye-admin-api"],["/eye_api_bak","http://192.168.13.68:70/eye_api"],["/push_stream_host","http://192.168.13.68:70/push_stream_host"],["/skyeye-login","http://192.168.13.68:70/skyeye-login"],["/ws_api_bak","ws://192.168.13.68:70/ws_api_bak"]]
-
-
-
-
-# VITE_PROXY=[["/skyeye-admin-api","http://192.168.13.68/skyeye-admin-api"],[],["/eye_api_bak","http://192.168.13.68/eye_api"],["/push_stream_host","http://192.168.13.68/push_stream_host"],["/skyeye-login","http://192.168.13.68/skyeye-login"],["/ws_api_bak","ws://192.168.13.68/ws_api_bak"],["/skyeye-file-upload","http://192.168.13.68/skyeye-file-upload"]]
-# VITE_PROXY=[["/skyeye-admin-api","http://192.168.23.182:8800/api"],[],["/eye_api_bak","http://192.168.23.182:8800"],["/push_stream_host","http://192.168.23.182/push_stream_host"],["/skyeye-login","http://192.168.13.68/skyeye-login"],["/ws_api_bak","ws://192.168.23.182/ws_api_bak"],["/skyeye-file-upload","http://192.168.13.68/skyeye-file-upload"]]
-VITE_PROXY=[["/skyeye-admin-api","http://192.168.22.234:8243/api"],[],["/eye_api_bak","http://192.168.22.234:8243"],["/push_stream_host","http://192.168.22.234/push_stream_host"],["/skyeye-login","http://192.168.13.68/skyeye-login"],["/ws_api_bak","ws://192.168.22.234/ws_api_bak"],["/skyeye-file-upload","http://192.168.13.68/skyeye-file-upload"]]
-
-
-
-
-
+VITE_PROXY=[["/skyeye-admin-api","http://192.168.13.68/skyeye-admin-api"],[],["/eye_api_bak","http://192.168.13.68/eye_api"],["/push_stream_host","http://192.168.13.68/push_stream_host"],["/skyeye-login","http://192.168.13.68/skyeye-login"],["/ws_api_bak","ws://192.168.13.68/ws_api_bak"],["/skyeye-file-upload","http://192.168.13.68/skyeye-file-upload"],["/nvr_download","http://192.168.13.68/nvr_download"]]
 # VITE_PROXY=[["/skyeye-admin-api","http://192.168.22.163:8800/api"],[],["/eye_api_bak","http://192.168.22.163:8800/api"],["/push_stream_host","http://192.168.13.68/push_stream_host"],["/skyeye-login","http://192.168.13.68/skyeye-login"],["/ws_api_bak","ws://192.168.13.68/ws_api_bak"]]
 #VITE_PROXY=[["/skyeye-admin-api","http://192.168.22.121:8800/api"],["/eye_api_bak","http://192.168.22.121:8800/api"],["/push_stream_host","http://192.168.13.68/push_stream_host"],["/skyeye-login","http://192.168.13.68/skyeye-login"],["/ws_api_bak","ws://192.168.13.68/ws_api_bak"],["/skyeye-file-upload","http://192.168.13.68/skyeye-file-upload"]]
 # VITE_PROXY=[["/skyeye-admin-api","http://192.168.22.163:8800/api"],[],["/eye_api_bak","http://192.168.22.163:8800/api"],["/push_stream_host","http://192.168.13.68/push_stream_host"],["/skyeye-login","http://192.168.13.68/skyeye-login"],["/ws_api_bak","ws://192.168.13.68/ws_api_bak"],["/skyeye-file-upload","http://192.168.13.68/skyeye-file-upload"]]

+ 1 - 0
src/api/datamanagement/playback.ts

@@ -49,5 +49,6 @@ export const getNvrDownloadUrl = (data: GetReplayNvrBody) => {
     url: '/nvrOption/download',
     method: 'post',
     data,
+    timeout: 100000000000,
   });
 };

BIN
src/assets/icons/icon-picker.png


+ 5 - 0
src/views/cameras/preview/store/useCameraAlgoStore.ts

@@ -45,6 +45,9 @@ const useCameraAlgoStore = defineStore('cameraAlgo', () => {
     { manual: true },
   );
 
+  // 选中的算法id列表
+  const selectedAlgoList = ref<number[]>([]);
+
   const {
     data: allAlgoList,
     runAsync: getAllAlgoList,
@@ -105,12 +108,14 @@ const useCameraAlgoStore = defineStore('cameraAlgo', () => {
     mutateCameraAlgoList();
     mutateAllAlgoList();
     selectedAlgoDetail.value = { ...defaultSelectedAlgoDetail } as CameraAlgoItemInCard;
+    selectedAlgoList.value = [];
   };
 
   return {
     cameraAlgoList,
     getCameraAlgoList,
     selectedAlgoId,
+    selectedAlgoList,
     metaObjList,
     allAlgoList,
     markedParamCardIds,

+ 12 - 6
src/views/datamanager/playback/Playback.vue

@@ -12,7 +12,7 @@
       </div>
       <div class="camera-placeholder" v-if="!cameraDetailStore.cameraId">请选择左侧相机</div>
       <div v-else class="camera-setting-wrapper">
-        <NvrCameraView :camera-id="cameraDetailStore.cameraId" />
+        <NvrCameraView ref="nvrCameraViewRef" :camera-id="cameraDetailStore.cameraId" />
       </div>
     </div>
   </div>
@@ -22,10 +22,10 @@
   import NvrCameraView from './components/NvrCameraView.vue';
   import CameraTreeCom from '@/views/cameras/preview/components/CameraTree/CameraTree.vue';
   import { onUnmounted, ref, watch } from 'vue';
-  import useCameraDetailStore from '@/views/cameras/preview/store/useCameraDetailStore';
+  import useCameraDetail from '@/views/cameras/preview/store/useCameraDetailStore';
   import usePresetListStore from '@/views/cameras/preview/store/usePresetListStore';
   import useFenceStore from '@/views/cameras/preview/store/useFenceStore';
-  // import useCameraAlgoStore from '@/views/cameras/preview/store/useCameraAlgoStore';
+  import useCameraAlgoStore from '@/views/cameras/preview/store/useCameraAlgoStore';
   import { onMounted } from 'vue';
   import { CameraDetailServer, IsPtz } from '@/api/camera/camera-overview';
   import { CameraTree, getCameraTree } from '@/api/camera/camera-preview';
@@ -34,13 +34,14 @@
   const cameraStatus = useCameraStatus();
   const { noNetworkingNum, openInterval, closeInterval } = cameraStatus;
 
-  const cameraDetailStore = useCameraDetailStore();
+  const cameraDetailStore = useCameraDetail();
   const fenceStore = useFenceStore();
   const presetListStore = usePresetListStore();
-  // const cameraAlgoStore = useCameraAlgoStore();
+  const cameraAlgoStore = useCameraAlgoStore();
   const cameraTree = ref<CameraTree[]>([]);
   const codeShowList = ref<string[]>([]);
   const noIntegrationNum = ref<number>(0);
+  const nvrCameraViewRef = ref();
 
   // const { allAlgoList } = storeToRefs(cameraAlgoStore);
 
@@ -105,6 +106,10 @@
         }
         // cameraAlgoStore.getCameraAlgoList(cameraId);
         // cameraAlgoStore.selectedAlgoId = null;
+
+        cameraAlgoStore.selectedAlgoList = [];
+        cameraAlgoStore.getCameraAlgoList(cameraId).then();
+        nvrCameraViewRef.value.clearNvrUrl();
       } else {
         /** 没有相机的时候也要请求相机树 */
         const tree = await getCameraTree();
@@ -129,10 +134,11 @@
   onUnmounted(() => {
     /** 离开页面要清理掉所有的store */
     cameraDetailStore.clear();
-    // cameraAlgoStore.clear();
+    cameraAlgoStore.clear();
     fenceStore.clear();
     presetListStore.clear();
     closeInterval();
+    nvrCameraViewRef.value.clearNvrUrl();
   });
 
   function getCameraDetail(tree: CameraTree[], cameraId: number): CameraTree | null {

+ 42 - 34
src/views/datamanager/playback/components/NvrCameraView.vue

@@ -2,7 +2,7 @@
   <div class="nvr-camera-view">
     <div class="nvr-tips">
       <el-icon size="18" color="#409eff" style="margin: 11px 8px 11px 16px"><InfoFilled /></el-icon>
-      可以回看和下载三个月内的视频回放数据;默认显示直播画面</div
+      默认播放当前实时视频画面,可选择具体日期和时间进行回看,最长支持90天内视频内容回看</div
     >
     <div class="nvr-date-picker">
       <el-date-picker
@@ -21,13 +21,12 @@
         >查看回放</el-button
       >
     </div>
-    <div>
+    <div style="width: 960px; height: 540px">
       <LiveVideo v-if="videoUrl" :url="videoUrl" @time-update="handleTimeUpdate" />
     </div>
-    <div class="nvr-slider">
+    <div class="nvr-slider" v-if="confirmDate">
       <NvrSlider
         ref="nvrSliderRef"
-        v-if="confirmDate"
         :start-time="dateRange[0]"
         :end-time="dateRange[1]"
         :start-position="100"
@@ -87,29 +86,27 @@
   const downloadUrl = ref();
 
   const judgeDate = (date: Date[]) => {
-    // 判断日期范围大于半小时小于三个月
+    confirmDate.value = false;
+    nvrUrl.value = undefined;
     if (date && date.length === 2) {
       const startTime = new Date(date[0]);
       const endTime = new Date(date[1]);
       if ((endTime.getTime() - startTime.getTime()) / (1000 * 60) < 1) {
         ElMessage({
-          message: `选择回放时间范围不小于1分钟`,
+          message: `选择回放时间范围需大于1分钟`,
+          type: 'error',
+        });
+        dateRange.value = undefined;
+        return;
+      }
+      if (endTime > new Date()) {
+        ElMessage({
+          message: `结束时间不能在当前时间之后`,
           type: 'error',
         });
         dateRange.value = undefined;
         return;
       }
-      // if (startTime.diff(endTime, 'month') > 3) {
-      //   ElMessage({
-      //     message: `选择回放时间范围不大于三个月`,
-      //     type: 'error',
-      //   });
-      //   dateRange.value = undefined;
-      //   return;
-      // }
-
-      confirmDate.value = false;
-      nvrUrl.value = undefined;
     }
   };
   const handleNvrUrl = (nowTime?: Date | null) => {
@@ -126,13 +123,25 @@
     };
     confirmDate.value = true;
     if (nowTime) {
-      nvrParams.startTime = dayjs(nowTime).format('YYYY-MM-DD HH:mm:ss');
+      const nowTimeMin = new Date(nowTime.setSeconds(0));
+      if (nowTimeMin < dateRange.value[0]) {
+        nvrParams.startTime = dayjs(dateRange.value[0]).format('YYYY-MM-DD HH:mm:ss');
+      } else {
+        nvrParams.startTime = dayjs(nowTimeMin).format('YYYY-MM-DD HH:mm:ss');
+      }
     }
     getReplayNvr(nvrParams).then((res) => {
       nvrUrl.value = res.data;
     });
   };
 
+  const clearNvrUrl = () => {
+    nvrUrl.value = undefined;
+    confirmDate.value = false;
+    dateRange.value = undefined;
+    nvrTimeSelectRef.value.clearTime();
+  };
+
   const handleVioTags = (tags: string[]) => {
     if (tags.length === 0) {
       violationsList.value = [];
@@ -153,30 +162,33 @@
     if (!confirmDate.value) {
       return;
     }
+    const nowTimeMin = new Date(nvrSliderRef.value.onTime.setSeconds(0));
     if (isStart) {
       const end = nvrTimeSelectRef.value.endTime;
-      if (end.length > 0 && new Date(end) <= new Date(nvrSliderRef.value.onTime)) {
+      if (end.length > 0 && new Date(end) <= nowTimeMin) {
         ElMessage({
           message: `结束时间不早于开始时间`,
           type: 'error',
         });
         return;
       }
-      nvrTimeSelectRef.value.startTime = dayjs(nvrSliderRef.value.onTime).format(
-        'YYYY-MM-DD HH:mm:ss',
-      );
+      nvrTimeSelectRef.value.startTime = dayjs(
+        nowTimeMin < dateRange.value[0] ? dateRange.value[0] : nowTimeMin,
+      ).format('YYYY-MM-DD HH:mm:ss');
     } else {
       const start = nvrTimeSelectRef.value.startTime;
-      if (start.length > 0 && new Date(start) >= new Date(nvrSliderRef.value.onTime)) {
+      if (start.length > 0 && new Date(start) >= nowTimeMin) {
         ElMessage({
           message: `开始时间不晚于结束时间`,
           type: 'error',
         });
         return;
       }
-      nvrTimeSelectRef.value.endTime = dayjs(nvrSliderRef.value.onTime).format(
-        'YYYY-MM-DD HH:mm:ss',
-      );
+      nvrTimeSelectRef.value.endTime = dayjs(
+        new Date(nowTimeMin.setMinutes(nowTimeMin.getMinutes() + 1)) > dateRange.value[1]
+          ? dateRange.value[1]
+          : nowTimeMin,
+      ).format('YYYY-MM-DD HH:mm:ss');
     }
   };
 
@@ -200,14 +212,6 @@
     getNvrDownloadUrl(nvrParams).then((res) => {
       downloadUrl.value = res;
       downloadRef.value.click();
-
-      // const link = document.createElement('a');
-      // link.href = res;
-      // link.setAttribute('download', 'nvr.mp4');
-      // link.style.display = 'none';
-      // document.body.appendChild(link);
-      // link.click();
-      // document.body.removeChild(link);
     });
   };
 
@@ -223,6 +227,10 @@
       ? cameraDetailStore.detail?.pushstreamIp
       : '';
   });
+
+  defineExpose({
+    clearNvrUrl,
+  });
 </script>
 
 <style scoped lang="scss">

+ 8 - 8
src/views/datamanager/playback/components/NvrCheckbox.vue

@@ -6,9 +6,9 @@
     </div>
 
     <div class="checkbox">
-      <el-checkbox-group v-if="tags?.length" v-model="checkedtags">
+      <el-checkbox-group v-if="cameraAlgoList?.length" v-model="selectedAlgoList">
         <el-checkbox
-          v-for="tag in tags"
+          v-for="tag in cameraAlgoList"
           :disabled="!available"
           :key="tag.algoInfo.id"
           :label="tag.algoInfo.name"
@@ -23,26 +23,26 @@
 
 <script setup lang="ts">
   import { ElCheckboxGroup, ElCheckbox } from 'element-plus';
-  import { onMounted, ref } from 'vue';
+  import { onMounted } from 'vue';
   import { storeToRefs } from 'pinia';
   import useCameraAlgoStore from '@/views/cameras/preview/store/useCameraAlgoStore';
 
   const cameraAlgoStore = useCameraAlgoStore();
-  const { cameraAlgoList } = storeToRefs(cameraAlgoStore);
-  const checkedtags = ref<any[] | undefined>([]);
-  const tags = ref<any>([]);
+  const { cameraAlgoList, selectedAlgoList } = storeToRefs(cameraAlgoStore);
+  // const checkedtags = ref<any[] | undefined>([]);
+  // const tags = ref<any>([]);
 
   const props = defineProps<{ available: boolean; cameraId: number }>();
 
   const emit = defineEmits(['checkTags']);
   const sendCheckTag = () => {
-    emit('checkTags', checkedtags.value);
+    emit('checkTags', selectedAlgoList.value);
   };
 
   onMounted(() => {
     cameraAlgoStore.getCameraAlgoList(props.cameraId).then(() => {
       // checkedtags.value = cameraAlgoList.value?.map((item) => item.algoInfo.id); // 默认选中
-      tags.value = cameraAlgoList.value;
+      // tags.value = cameraAlgoList.value;
     });
   });
 </script>

+ 21 - 7
src/views/datamanager/playback/components/NvrSlider.vue

@@ -58,7 +58,7 @@
 
   onMounted(() => {
     createTimeline(props.startTime, durationMins.value);
-
+    createViolationsLine([], props.startTime, props.endTime);
     sliderPosition.value = props.startPosition;
 
     watch([() => props.startTime, () => props.endTime], () => {
@@ -96,7 +96,7 @@
 
   // 时间条长度计算
   const durationMins = computed(() => {
-    return Math.floor((props.endTime.valueOf() - props.startTime.valueOf()) / (1000 * 60));
+    return Math.ceil((props.endTime.valueOf() - props.startTime.valueOf()) / (1000 * 60));
   });
 
   // 计算停驻时间
@@ -127,6 +127,20 @@
     container!.style.width = `${durationMins}px`;
     // 计算持续小时数 向下取整
     const duration = Math.floor(durationMins / 60);
+    if (marginMins === 0) {
+      // 添加第一个小时
+      const mark = document.createElement('div');
+      mark.className = 'time-slider-mark';
+      mark.style.left = `${marginMins}px`;
+      container?.appendChild(mark);
+
+      // 添加第一个小时文本
+      const text = document.createElement('span');
+      text.className = 'time-slider-text';
+      text.textContent = `${startHours % 24}:00`;
+      text.style.left = `${marginMins}px`;
+      container?.appendChild(text);
+    }
 
     for (let i = 1; i <= duration; i++) {
       // 刻度元素
@@ -139,7 +153,7 @@
       const text = document.createElement('span');
       text.className = 'time-slider-text';
       text.textContent = `${(startHours + i) % 24}:00`;
-      text.style.left = `${i * 60 - marginMins}px`;
+      text.style.left = `${i * 60 - marginMins - 12}px`;
       container?.appendChild(text);
     }
   };
@@ -224,13 +238,13 @@
   .time-slider-mark {
     position: absolute;
     width: 1px;
-    height: 15px;
-    background-color: #000;
+    height: 8px;
+    background-color: #979797;
   }
   .time-slider-text {
     position: absolute;
-    top: 15px;
-    color: #000;
+    top: 10px;
+    color: #333333;
   }
   .violations-line {
     overflow: hidden;

+ 38 - 8
src/views/datamanager/playback/components/NvrTimeSelect.vue

@@ -3,6 +3,7 @@
     <div class="head">
       <div class="line"></div>
       <div class="title">视频下载</div>
+      <span class="tips">(请先拖动进度条,再点击选取时间按钮)</span>
     </div>
 
     <div class="timeselect">
@@ -17,9 +18,12 @@
         <img
           @click="callSetTime(true)"
           style="cursor: pointer"
-          src="@\assets\icons\edit.png"
+          src="@\assets\icons\icon-picker.png"
           alt=""
         />
+        <el-icon class="icon-refresh" :size="18">
+          <Refresh @click="startTime = ''" />
+        </el-icon>
       </div>
       <div class="time-picker">
         结束时间:
@@ -32,12 +36,20 @@
         <img
           @click="callSetTime(false)"
           style="cursor: pointer"
-          src="@\assets\icons\edit.png"
+          src="@\assets\icons\icon-picker.png"
           alt=""
         />
+        <el-icon class="icon-refresh" :size="18">
+          <Refresh @click="endTime = ''" />
+        </el-icon>
       </div>
 
-      <el-button type="primary" @click="nvrDownload">下 载</el-button>
+      <el-button
+        type="primary"
+        :disabled="!startTime.length || !endTime.length"
+        @click="nvrDownload"
+        >下 载</el-button
+      >
     </div>
   </div>
 </template>
@@ -45,6 +57,8 @@
 <script setup lang="ts">
   import { ref } from 'vue';
   import { ElInput } from 'element-plus';
+  import { Refresh } from '@element-plus/icons-vue';
+  import { ElIcon } from 'element-plus';
 
   const startTime = ref('');
   const endTime = ref('');
@@ -64,8 +78,12 @@
   const nvrDownload = () => {
     emit('downloadNvr');
   };
+  const clearTime = () => {
+    startTime.value = '';
+    endTime.value = '';
+  };
 
-  defineExpose({ startTime, endTime });
+  defineExpose({ startTime, endTime, clearTime });
 </script>
 
 <style scoped lang="scss">
@@ -90,15 +108,27 @@
         line-height: 20px;
         margin-left: 8px;
       }
+      .tips {
+        font-weight: 400;
+        font-size: 10px;
+        color: #909399;
+        line-height: 14px;
+      }
     }
     .timeselect {
       padding: 12px 22px 0 22px;
       .time-picker {
         margin-bottom: 12px;
-      }
-      img {
-        display: inline;
-        margin-left: 20px;
+        img {
+          display: inline;
+          margin-left: 20px;
+        }
+        .icon-refresh {
+          color: #1890ff;
+          top: 5px;
+          margin: 0 5px;
+          cursor: pointer;
+        }
       }
     }
   }