Quellcode durchsuchen

```
feat(system): 新增告警数量状态并优化告警列表逻辑

- 在 system store 中新增 `alarmCount` 状态用于记录未读告警数
- 增加对 home store 的订阅,当切换到视频页面时清除所有通知
- 调整告警列表长度限制逻辑,从 30 条开始删除旧数据
- 引入 home store 并在连接报警 WebSocket 时使用其状态判断是否显示通知

feat(video-box): 新增顶部工具栏与告警视频弹窗组件

- 创建 topTools 组件,包含返回地图按钮、告警徽章及告警列表展示
- 实现点击告警项打开对应视频播放窗口的功能
- 添加 alarmVideo.vue 组件用于播放指定设备的实时视频流
- 在 VideoBox 主组件中引入并使用 TopTools 替换原有返回按钮
```

gitboyzcf vor 2 Wochen
Ursprung
Commit
e2a25d7242

+ 12 - 1
src/App.vue

@@ -83,6 +83,17 @@
       borderRadiusMedium: '0px',
       borderRadiusLarge: '0px'
     },
+    Popover: {
+      color: 'rgba(0, 49, 88, 1)',
+      borderRadius: '0'
+    },
+    Alert: {
+      padding: '6px 10px',
+      borderRadius: '0',
+      textColor: '#fff',
+      iconMargin: '4px 8px 0 12px',
+      colorWarning: 'rgba(254, 247, 237, 0.2)'
+    },
     Message: {
       padding: '5px 10px',
       borderRadius: '0'
@@ -91,7 +102,7 @@
       padding: '6px 10px',
       borderRadius: '0',
       textColor: '#fff',
-      color: 'rgba(0, 49, 88, 0.7)'
+      color: 'rgba(0, 49, 88, 0.8)'
     }
     // ...
   }

+ 28 - 15
src/stores/modules/system.js

@@ -3,6 +3,7 @@ import storage from '@/utils/storage'
 import dayjs from 'dayjs'
 import { useNotification } from 'naive-ui'
 import { h } from 'vue'
+import { useOutsideHomeStore } from './home'
 
 const {
   API_PANORAMA_POST,
@@ -32,7 +33,8 @@ export const useSystemStore = defineStore('systemStore', {
     videoList: {}, // 视频地址列表
     partObj: {}, //细节X Y
     ratio: 0, //全景比例
-    alarmList: [] // 告警列表
+    alarmList: [], // 告警列表
+    alarmCount: 0 // 告警数量
   }),
   getters: {
     ipF() {
@@ -93,6 +95,7 @@ export const useSystemStore = defineStore('systemStore', {
     },
     // 连接报警信息
     async connectAlarmWS() {
+      const useHomeStore = useOutsideHomeStore()
       const notification = useNotification()
       const eventTypes = { 1: '车牌', 2: '安全帽', 3: '定员' }
       // setInterval(() => {
@@ -123,6 +126,12 @@ export const useSystemStore = defineStore('systemStore', {
       //     this.alarmList.splice(20, 10)
       //   }
       // }, 1000)
+
+      useHomeStore.$subscribe((mutation, state) => {
+        if (state.temp === 'video') {
+          notification.destroyAll()
+        }
+      })
       let websocket
       let that = this
       ;(() => {
@@ -134,21 +143,25 @@ export const useSystemStore = defineStore('systemStore', {
         }
         websocket.onmessage = function (e) {
           const data = JSON.parse(e.data)
-          that.alarmList.unshift(data)
+          that.alarmList.unshift({ ...data, eventTypes })
 
-          notification.warning({
-            title: eventTypes[data.EventId] ? eventTypes[data.EventId] + '警告' : '其他警告',
-            content: () =>
-              h('p', [
-                h('span', '时间:'),
-                h(
-                  'span',
-                  dayjs(parseInt(`${data.HaveTime}`.padEnd(13, '0'))).format('YYYY-MM-DD HH:mm:ss')
-                )
-              ])
-          })
-          if (that.alarmList.length >= 30) {
-            that.alarmList.splice(20, 10)
+          if (useHomeStore.temp != 'video') {
+            notification.warning({
+              title: eventTypes[data.EventId] ? eventTypes[data.EventId] + '警告' : '其他警告',
+              content: () =>
+                h('p', [
+                  h('span', '时间:'),
+                  h(
+                    'span',
+                    dayjs(parseInt(`${data.HaveTime}`.padEnd(13, '0'))).format(
+                      'YYYY-MM-DD HH:mm:ss'
+                    )
+                  )
+                ])
+            })
+          }
+          if (that.alarmList.length >= 50) {
+            that.alarmList.splice(30, 20)
           }
         }
         websocket.onerror = function () {

+ 51 - 0
src/views/VideoBox/components/alarmVideo.vue

@@ -0,0 +1,51 @@
+<template>
+  <div class="alarm-video-box relative">
+    <pub-video-new newClass="alarm-video" class="aspect-ratio-video" />
+    <div
+      v-if="loading"
+      class="absolute top-50% left-50% transform-translate--50% flex justify-center items-center"
+    >
+      <NSpin />
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { NSpin } from 'naive-ui'
+  import { useOutsideSystemStore } from '@/stores/modules/system.js'
+  import { omatVideoPlayer } from '@/assets/js/video-lib/flv/omatVideoPlayer'
+
+  defineOptions({
+    name: 'alarm-video-box'
+  })
+
+  let playerObj = null
+  const loading = ref(true)
+  const useSystem = useOutsideSystemStore()
+  const props = defineProps({
+    alarm: Object
+  })
+
+  const init = async () => {
+    const { url } = await useSystem.getStream({ device_id: props.alarm.sn })
+    playerObj = omatVideoPlayer(
+      '.alarm-video',
+      url,
+      () => {
+        loading.value = false
+      },
+      {
+        enableZoom: false,
+        enableDrag: false
+      }
+    )
+  }
+  onMounted(() => {
+    init()
+  })
+
+  onUnmounted(() => {
+    playerObj.destroy()
+    playerObj = null
+  })
+</script>

+ 102 - 0
src/views/VideoBox/components/topTools.vue

@@ -0,0 +1,102 @@
+<template>
+  <div class="top-tools flex justify-between">
+    <span
+      class="cursor-pointer block w-30px h-30px color-#8ac5ff i-bx:arrow-to-left"
+      @click="toMap"
+    ></span>
+    <n-popover
+      placement="bottom-end"
+      trigger="click"
+      style="max-height: 400px"
+      scrollable
+      :on-update:show="popoverUpd"
+    >
+      <template #trigger>
+        <n-badge :value="useSystem.alarmCount" :max="40" :offset="[0, 0]">
+          <div class="i-fluent-alert-on-16-regular size-30px color-#8ac5ff cursor-pointer"></div>
+        </n-badge>
+      </template>
+
+      <TransitionGroup name="list" tag="ul">
+        <n-alert
+          v-for="item in useSystem.alarmList"
+          :key="item.HaveTime"
+          :bordered="false"
+          type="warning"
+          class="mb-3 hover:bg-blue-300 cursor-pointer"
+          @click="openAlarmVideo(item)"
+        >
+          <template #header>
+            <p class="text-14px">
+              {{ tfmt(item.HaveTime) }}
+              {{
+                item.eventTypes[item.EventId] ? item.eventTypes[item.EventId] + '警告' : '其他警告'
+              }}
+            </p>
+          </template>
+        </n-alert>
+      </TransitionGroup>
+    </n-popover>
+  </div>
+</template>
+
+<script setup>
+  import dayjs from 'dayjs'
+  import { useModal, NSpin, NBadge, NPopover, NSpace, NAlert } from 'naive-ui'
+  import { useOutsideHomeStore } from '@/stores/modules/home'
+  import { useOutsideSystemStore } from '@/stores/modules/system.js'
+
+  const alarmModel = useModal()
+  const useSystem = useOutsideSystemStore()
+  const useHomeStore = useOutsideHomeStore()
+  const flag = ref(false)
+
+  const tfmt = (time) => dayjs(parseInt(`${time}`.padEnd(13, '0'))).format('YYYY-MM-DD HH:mm:ss')
+
+  const toMap = () => {
+    // router.push('/')
+    // $mitt.emit('onPreLevel', useHomeStore.codes[0])
+    useHomeStore.temp = 'map'
+  }
+
+  const openAlarmVideo = (item) => {
+    alarmModel.create({
+      title: item.eventTypes[item.EventId]
+        ? item.eventTypes[item.EventId] + '警告视频'
+        : '其他警告视频',
+      draggable: true,
+      preset: 'card',
+      maskClosable: false,
+      style: {
+        width: '50%',
+        marginTop: '10%'
+      },
+      render: () =>
+        h(defineAsyncComponent({ loader: () => import('./AlarmVideo.vue') }), {
+          alarm: item
+        })
+    })
+  }
+
+  const popoverUpd = (show) => {
+    flag.value = show
+    useSystem.alarmCount = show ? 0 : useSystem.alarmCount + 1
+  }
+
+  watch(
+    () => useSystem.alarmList,
+    () => popoverUpd(flag.value),
+    { deep: true }
+  )
+</script>
+<style scoped>
+  .list-enter-active,
+  .list-leave-active {
+    transition: all 0.5s ease;
+  }
+  .list-enter-from,
+  .list-leave-to {
+    opacity: 0;
+    transform: translateX(30px);
+  }
+</style>

+ 2 - 11
src/views/VideoBox/index.vue

@@ -1,8 +1,6 @@
 <template>
   <div class="video-box w-100%">
-    <div class="cursor-pointer" @click="toMap">
-      <span class="block w-30px h-30px color-#8ac5ff i-bx:arrow-to-left"></span>
-    </div>
+    <TopTools />
     <div class="video-box-wrapper mt-10px relative bg-#00000080">
       <UseFullscreen v-slot="{ toggle, isFullscreen }">
         <div class="relative w-full h-full flex">
@@ -84,12 +82,12 @@
   import { omatVideoPlayer } from '@/assets/js/video-lib/flv/omatVideoPlayer'
   import TagBox from './components/tagBox.vue'
   import { useOutsideSystemStore } from '@/stores/modules/system.js'
-  import { useOutsideHomeStore } from '@/stores/modules/home'
   import { useOutsideTagStore } from '@/stores/modules/tag.js'
 
   import { UseFullscreen } from '@vueuse/components'
   import { $mitt, uuid } from '@/utils'
   import RightClick from './components/rightBox.vue'
+  import TopTools from './components/topTools.vue'
   import VuePhotoZoomPro from 'vue-photo-zoom-pro'
   import 'vue-photo-zoom-pro/dist/style/vue-photo-zoom-pro.css'
 
@@ -98,7 +96,6 @@
     RtspMain: String
   })
   const playbackModal = useModal()
-  const useHomeStore = useOutsideHomeStore()
   const useSystem = useOutsideSystemStore()
   const useTag = useOutsideTagStore()
   const loading = ref(true)
@@ -135,12 +132,6 @@
     }
   }
 
-  const toMap = () => {
-    // router.push('/')
-    // $mitt.emit('onPreLevel', useHomeStore.codes[0])
-    useHomeStore.temp = 'map'
-  }
-
   const xy = ref({ x: 0, y: 0 })
   const rightClickFn = (event) => {
     isShow.value = true