Kaynağa Gözat

fix🐛: 修复告警

gitboyzcf 1 ay önce
ebeveyn
işleme
203e848c90

+ 33 - 54
src/components/alarm-drawer/Drawer.vue

@@ -9,6 +9,34 @@
       <template #header> 告警查看 </template>
       <!-- <template #footer> Footer </template> -->
       <slot></slot>
+      <template #footer>
+        <div class="flex w-full flex-col items-center rounded-2xl bg-[#fff] px-6 py-4">
+          <NRadioGroup
+            v-model:value="alarmOk"
+            name="radiobuttongroup1"
+          >
+            <NRadioButton
+              v-for="item in handleListAlarmOk"
+              :key="item.value"
+              :value="item.value"
+              :label="item.label"
+            />
+          </NRadioGroup>
+          <NDivider vertical />
+          <NRadioGroup
+            v-model:value="handleOk"
+            name="radiobuttongroup1"
+            @update:value="handleOkChange"
+          >
+            <NRadioButton
+              v-for="item in handleOkList"
+              :key="item.value"
+              :value="item.value"
+              :label="item.label"
+            />
+          </NRadioGroup>
+        </div>
+      </template>
     </NDrawerContent>
 
     <div
@@ -30,55 +58,6 @@
           size="large"
         />
       </div>
-      <!-- <div class="flex items-center rounded-2xl bg-[#fff] px-6 py-4 z-100">
-        <NButton
-          v-for="(item, i) in baseHandles"
-          :key="i"
-          quaternary
-          round
-          type="info"
-          @click="item.onClick"
-        >
-          <template #icon>
-            <span :class="[item.icon]"></span>
-          </template>
-        </NButton>
-        <NDivider vertical />
-        <NRadioGroup
-          v-model:value="alarmOk"
-          name="radiobuttongroup1"
-        >
-          <NRadioButton
-            v-for="item in handleListAlarmOk"
-            :key="item.value"
-            :value="item.value"
-            :label="item.label"
-          />
-        </NRadioGroup>
-        <NButton
-          quaternary
-          round
-          type="info"
-          @click="alarmOk = null"
-        >
-          <template #icon>
-            <span class="iconify-[bx--reset]"></span>
-          </template>
-        </NButton>
-        <NDivider vertical />
-        <NRadioGroup
-          v-model:value="handleOk"
-          name="radiobuttongroup1"
-          @update:value="handleOkChange"
-        >
-          <NRadioButton
-            v-for="item in handleOkList"
-            :key="item.value"
-            :value="item.value"
-            :label="item.label"
-          />
-        </NRadioGroup>
-      </div> -->
     </div>
   </NDrawer>
 </template>
@@ -203,11 +182,11 @@ const showImage = () => {
           show: 1,
           size: 'large',
         },
-        download: {
-          show: 1,
-          size: 'large',
-          click: function () {},
-        },
+        // download: {
+        //   show: 1,
+        //   size: 'large',
+        //   click: function () {},
+        // },
       },
       viewed() {
         nextTick(() => {

+ 21 - 3
src/components/alarm-drawer/components/AlarmShow.vue

@@ -14,16 +14,34 @@
           :cols="6"
         >
           <NGi span="2">
-            <span class="text-[rgba(14,27,46,.45)]">算法ID</span>
+            <span class="text-[rgba(14,27,46,.45)]">告警时间</span>
+          </NGi>
+          <NGi span="4">
+            <span>{{ drawerProps?.EventTimeFormat }}</span>
+          </NGi>
+          <NGi span="2">
+            <span class="text-[rgba(14,27,46,.45)]">事件ID</span>
           </NGi>
           <NGi span="4">
             <span>{{ drawerProps?.EventID }}</span>
           </NGi>
           <NGi span="2">
-            <span class="text-[rgba(14,27,46,.45)]">告警时间</span>
+            <span class="text-[rgba(14,27,46,.45)]">位置</span>
           </NGi>
           <NGi span="4">
-            <span>{{ drawerProps?.EventTimeFormat }}</span>
+            <span>水平 {{ drawerProps?.Pan }}<br />垂直 {{ drawerProps?.Tilt }}</span>
+          </NGi>
+          <NGi span="2">
+            <span class="text-[rgba(14,27,46,.45)]">置信度</span>
+          </NGi>
+          <NGi span="4">
+            <span>{{ drawerProps?.Score }}</span>
+          </NGi>
+          <NGi span="2">
+            <span class="text-[rgba(14,27,46,.45)]">类型</span>
+          </NGi>
+          <NGi span="4">
+            <span>{{ drawerProps?.Type == 0 ? '鸟' : '未知' }}</span>
           </NGi>
         </NGrid>
       </NCollapseItem>

+ 71 - 22
src/utils/index.ts

@@ -1,31 +1,80 @@
+const imgBlobList: any = []
+
+export function revokeBlob() {
+  imgBlobList.forEach((item: any) => {
+    URL.revokeObjectURL(item)
+  })
+}
+
 // 绘制边界框的函数
-export function drawBoundingBox(item: any) {
+export function drawBoundingBox(item: any, isClip: boolean = false) {
   return new Promise((resolve, reject) => {
     const img = new Image()
     img.crossOrigin = 'Anonymous' // 处理跨域图片
 
     img.onload = function () {
-      const canvas = document.createElement('canvas')
-      canvas.width = img.width
-      canvas.height = img.height
-
-      const ctx = canvas.getContext('2d')
-      if (!ctx) return
-      ctx.drawImage(img, 0, 0)
-
-      // 绘制红色边界框
-      ctx.strokeStyle = '#ff0000'
-      ctx.lineWidth = 3
-      ctx.strokeRect(item.X, item.Y, item.W, item.H)
-
-      // 添加标签
-      ctx.fillStyle = '#ff0000'
-      ctx.font = '16px Arial'
-      ctx.fillText('告警区域', item.X, item.Y - 5)
-
-      // 将Canvas转换为Data URL
-      const dataURL = canvas.toDataURL('image/jpeg')
-      resolve(dataURL)
+      if (isClip) {
+        // 裁剪模式:创建一个新的canvas只包含裁剪区域
+        const canvas = document.createElement('canvas')
+        canvas.width = item.W
+        canvas.height = item.H
+
+        const ctx = canvas.getContext('2d')
+        if (!ctx) return
+
+        // 从原图中裁剪指定区域
+        ctx.drawImage(
+          img,
+          item.X,
+          item.Y,
+          item.W,
+          item.H, // 源图像的矩形选择框
+          0,
+          0,
+          item.W,
+          item.H, // 在目标canvas中的位置和尺寸
+        )
+
+        // 将Canvas转换为Blob URL
+        canvas.toBlob((blob) => {
+          if (blob) {
+            const blobURL = URL.createObjectURL(blob)
+            imgBlobList.push(blobURL)
+            resolve(blobURL)
+          } else {
+            reject(new Error('无法创建Blob'))
+          }
+        }, 'image/jpeg')
+      } else {
+        // 原有逻辑:绘制边界框
+        const canvas = document.createElement('canvas')
+        canvas.width = img.width
+        canvas.height = img.height
+
+        const ctx = canvas.getContext('2d')
+        if (!ctx) return
+        ctx.drawImage(img, 0, 0)
+
+        // 绘制红色边界框
+        ctx.strokeStyle = '#ff0000'
+        ctx.lineWidth = 3
+        ctx.strokeRect(item.X, item.Y, item.W, item.H)
+
+        // 添加标签
+        ctx.fillStyle = '#ff0000'
+        ctx.font = '16px Arial'
+        ctx.fillText('告警区域', item.X, item.Y - 5)
+
+        // 将Canvas转换为Blob URL
+        canvas.toBlob((blob) => {
+          if (blob) {
+            const blobURL = URL.createObjectURL(blob)
+            resolve(blobURL)
+          } else {
+            reject(new Error('无法创建Blob'))
+          }
+        }, 'image/jpeg')
+      }
     }
 
     img.onerror = function () {

+ 60 - 16
src/views/alarm-management/index.vue

@@ -126,12 +126,18 @@
         </div>
       </div>
     </NCard>
-    <NDropdown
+    <!-- <NDropdown
       placement="bottom-start"
       trigger="manual"
       v-bind="dropdownOptions"
       :show="enableContextmenu && showDropdown"
-    />
+    /> -->
+    <Drawer
+      v-model="drawerShow"
+      v-model:currentItem="currentItem"
+    >
+      <AlarmShow />
+    </Drawer>
   </ScrollContainer>
 </template>
 
@@ -153,12 +159,13 @@ import {
   NTag,
   NNumberAnimation,
   NDatePicker,
-  NImage,
 } from 'naive-ui'
-import { reactive, ref, useTemplateRef, nextTick } from 'vue'
+import { reactive, ref, useTemplateRef, nextTick, h } from 'vue'
 
 import { useRequest } from '@/api'
 import { ScrollContainer } from '@/components'
+import AlarmShow from '@/components/alarm-drawer/components/AlarmShow.vue'
+import Drawer from '@/components/alarm-drawer/Drawer.vue'
 import { useInjection, useComponentModifier, useResettableReactive } from '@/composables'
 import { mediaQueryInjectionKey } from '@/injection'
 import { useAlarmStore } from '@/stores'
@@ -306,15 +313,24 @@ const CellActions = (row: AlarmInfo) => (
 const columns: DataTableColumns<AlarmInfo> = [
   {
     key: 'number',
-    title: '号',
+    title: '号',
     width: 100,
     align: 'center',
   },
+  {
+    key: 'event_id',
+    width: 160,
+    align: 'center',
+    title: '事件ID',
+  },
   {
     key: 'alarm_type',
     width: 160,
     align: 'center',
     title: '告警类型',
+    render: (row) => {
+      return [h('span', row.alarm_type != 0 ? '未知' : '鸟')]
+    },
   },
   {
     key: 'status',
@@ -367,22 +383,22 @@ const columns: DataTableColumns<AlarmInfo> = [
     width: 200,
     align: 'center',
     render: (row) => (
-      <NImage
-        lazy
-        img-props={{ class: 'h-[60px]' }}
+      <img
+        class={'m-auto w-[60px] cursor-pointer object-cover'}
         alt='告警图片'
         src={row.image_file_name}
+        onClick={() => alarmShow(row)}
       />
     ),
   },
-  {
-    width: 140,
-    key: 'actions',
-    align: 'center',
-    title: '操作',
-    fixed: 'right',
-    render: (row) => <CellActions {...row} />,
-  },
+  // {
+  //   width: 140,
+  //   key: 'actions',
+  //   align: 'center',
+  //   title: '操作',
+  //   fixed: 'right',
+  //   render: (row) => <CellActions {...row} />,
+  // },
 ]
 
 function rowProps(row: AlarmInfo) {
@@ -528,4 +544,32 @@ async function getDataList() {
 }
 
 getDataList()
+
+const drawerShow = ref(false)
+const currentItem = ref<any>(null)
+const alarmShow = (item: any) => {
+  currentItem.value = {
+    ...item,
+    ID: item.alert_id,
+    UsbCameraIndex: 1,
+    Image: item.image_file_name,
+    ImageData: '',
+    Video: item.video_file_name,
+    Degree: item.degree,
+    EventTime: item.event_timestamp,
+    X: item.target_x,
+    Y: item.target_y,
+    W: item.target_width,
+    H: item.target_height,
+    Score: item.confidence_score,
+    Type: item.alarm_type,
+    Pan: item.pan_angle,
+    Tilt: item.tilt_angle,
+    EventID: item.event_id,
+    Status: item.status,
+    EventTimeFormat: dayjs(item.event_timestamp).format('YYYY-MM-DD HH:mm:ss'),
+    ImgUrl: item.image_file_name,
+  }
+  drawerShow.value = true
+}
 </script>

+ 123 - 38
src/views/monitoring-platform/data-monitoring.vue

@@ -4,7 +4,7 @@
     :scrollable="false"
   >
     <div class="data-m-container relative flex h-full flex-col gap-4 overflow-hidden">
-      <div class="video-container-box flex">
+      <div class="video-container-box relative flex">
         <div
           class="relative flex-1"
           v-for="(v, i) in videoInfo"
@@ -12,21 +12,26 @@
         >
           <canvas :class="['dm-video' + i, 'h-30 w-full']"></canvas>
         </div>
+        <NSkeleton
+          v-if="loading"
+          width="100%"
+          height="100%"
+        />
       </div>
       <div class="flex flex-1 gap-4 max-sm:flex-col">
         <div class="flex flex-2 flex-col gap-4">
           <div
-            class="grid flex-2 grid-cols-2 grid-rows-3 gap-1 overflow-hidden sm:grid-cols-3 lg:grid-cols-5"
+            class="relative grid flex-2 grid-cols-2 grid-rows-3 gap-1 overflow-hidden sm:grid-cols-3 lg:grid-cols-5"
           >
             <div
               v-for="v in dataListComputed"
-              :key="v.EventId"
+              :key="v.EventID"
               class="h-full w-full bg-naive-card"
             >
-              <div class="flex h-full flex-col">
+              <div class="relative flex h-full flex-col">
                 <img
-                  class="h-[100px] w-full object-cover"
-                  :src="v.imageFileName"
+                  class="h-[100px] w-full"
+                  :src="v.ImgUrlClip"
                   alt=""
                 />
                 <div class="flex h-full items-center justify-between pl-2">
@@ -34,12 +39,13 @@
                     <span :style="{ color: parseFloat(v.Score) > 0.5 ? 'red' : 'green' }">{{
                       v.Score
                     }}</span>
-                    <span>{{ v.EventTime.split(' ')[1] }}</span>
+                    <span>{{ v.EventTimeFormat.split(' ')[1] }}</span>
                     <span>{{ v.Type == '0' ? '鸟' : '未知' }}</span>
                   </div>
                   <NButton
                     quaternary
                     circle
+                    @click="alarmShow(v)"
                   >
                     <template #icon>
                       <i class="iconify-[fe--warning] iconify"></i>
@@ -48,6 +54,14 @@
                 </div>
               </div>
             </div>
+            <template v-for="v in 15">
+              <NSkeleton
+                v-if="isRequestLoading"
+                :key="v"
+                width="100%"
+                height="100%"
+              />
+            </template>
           </div>
           <div class="flex-1">
             <NDataTable
@@ -66,41 +80,45 @@
           <BallCameraTracking />
         </div>
       </div>
-      <!-- <div
-        v-if="loading"
-        class="absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center bg-[rgba(0,0,0,0.1)]"
-      >
-        <NSpin size="medium" />
-      </div> -->
     </div>
+    <Drawer
+      v-model="drawerShow"
+      v-model:currentItem="currentItem"
+    >
+      <AlarmShow />
+    </Drawer>
   </ScrollContainer>
 </template>
 
 <script setup lang="ts">
+import { watchThrottled } from '@vueuse/core'
 import dayjs from 'dayjs'
-import { NDataTable, NButton } from 'naive-ui'
+import { NDataTable, NButton, NSkeleton } from 'naive-ui'
 import useWorker from 'omnimatrix-video-player'
 import { onMounted, onUnmounted, ref, h, watch, computed } from 'vue'
 
 import { useRequest } from '@/api'
 import { ScrollContainer } from '@/components'
+import AlarmShow from '@/components/alarm-drawer/components/AlarmShow.vue'
+import Drawer from '@/components/alarm-drawer/Drawer.vue'
 import { useInjection } from '@/composables'
 import { mediaQueryInjectionKey } from '@/injection'
 import { useAlarmStore } from '@/stores'
+import { drawBoundingBox, revokeBlob } from '@/utils'
 
 import BallCameraTracking from './components/ballCameraTracking.vue'
 
 import type { DataTableColumns } from 'naive-ui'
 // import { enableImageManipulation } from '@/utils'
 interface RowData {
-  EventId: string
-  EventTime: string
+  EventID: string
+  EventTimeFormat: string
   Score: string
   Pan: string
   Tilt: string
   Type: string
   Status: number
-  imageFileName?: string
+  ImgUrlClip?: string
 }
 
 defineOptions({ name: 'DataMonitoring' })
@@ -115,8 +133,8 @@ const videoInfo = ref([
   `wss://${alarmStore.ipF}/VideoShow/Preview/Full/Main?DeviceID=1`,
   `wss://${alarmStore.ipF}/VideoShow/Preview/Full/Main?DeviceID=2`,
 ])
-const loading = ref(false)
-const isRequestLoading = ref(false)
+const loading = ref(true)
+const isRequestLoading = ref(true)
 const tableRowsStyle = { fontSize: '12px' }
 const columns: DataTableColumns<RowData> = [
   {
@@ -130,11 +148,11 @@ const columns: DataTableColumns<RowData> = [
   },
   {
     title: '事件ID',
-    key: 'EventId',
+    key: 'EventID',
     width: 150,
     align: 'center',
     render(row) {
-      return h('span', { style: tableRowsStyle }, row.EventId)
+      return h('span', { style: tableRowsStyle }, row.EventID)
     },
   },
   {
@@ -143,7 +161,7 @@ const columns: DataTableColumns<RowData> = [
     align: 'center',
     width: 200,
     render(row) {
-      return h('span', { style: tableRowsStyle }, row.EventTime)
+      return h('span', { style: tableRowsStyle }, row.EventTimeFormat)
     },
   },
   {
@@ -217,28 +235,94 @@ const showVideo = () => {
   })
 }
 
-watch(
+const formatData = async (list: any[]) => {
+  const promises = list.map((item: any) => {
+    return drawBoundingBox(
+      {
+        X: item.target_x ? item.target_x : item.X,
+        Y: item.target_y ? item.target_y : item.Y,
+        W: item.target_width ? item.target_width : item.W,
+        H: item.target_height ? item.target_height : item.H,
+        ImgUrl: item.ImgUrl ? item.ImgUrl : getFileUrl(item.image_file_name),
+      },
+      true,
+    )
+  })
+  const data = await Promise.all(promises)
+
+  return list.map((item: any, i: number) => {
+    return {
+      Image: item.image_file_name ? item.image_file_name : item.Image,
+      Video: item.video_file_name ? item.video_file_name : item.Video,
+      Degree: item.degree ? item.degree : item.Degree,
+      EventTime: item.event_timestamp ? item.event_timestamp : item.EventTime,
+      X: item.target_x ? item.target_x : item.X,
+      Y: item.target_y ? item.target_y : item.Y,
+      W: item.target_width ? item.target_width : item.W,
+      H: item.target_height ? item.target_height : item.H,
+      Score: item.confidence_score ? item.confidence_score : item.Score,
+      Type: item.alarm_type ? item.alarm_type : item.Type,
+      Pan: item.pan_angle ? item.pan_angle : item.Pan,
+      Tilt: item.tilt_angle ? item.tilt_angle : item.Tilt,
+      EventID: item.event_id ? item.event_id : item.EventID,
+      Status: item.status ? item.status : item.Status,
+      EventTimeFormat: dayjs(item.event_timestamp ? item.event_timestamp : item.EventTime).format(
+        'YYYY-MM-DD HH:mm:ss',
+      ),
+      ImgUrlClip: data[i],
+      ImgUrl: item.ImgUrl ? item.ImgUrl : getFileUrl(item.image_file_name),
+    }
+  })
+}
+
+watchThrottled(
   () => alarmStore.alarms,
   (v) => {
-    console.log(v)
+    console.log(v) // TODO
   },
+  { throttle: 3000, deep: true },
 )
 async function getDataList() {
-  isRequestLoading.value = true
-  const res: any = await API_ALERT_ESEARCH_GET({ page: 1, page_size: 20 }).finally(() => {
-    isRequestLoading.value = false
-  })
+  const res: any = await API_ALERT_ESEARCH_GET({ page: 1, page_size: 20 })
+  console.log(res.items)
+
+  const resF = await formatData(res.items)
+  dataList.value = resF as RowData[]
+  isRequestLoading.value = false
+}
+
+const drawerShow = ref(false)
+const currentItem = ref<any>(null)
+const alarmShow = (item: any) => {
+  console.log(item)
 
-  dataList.value = res.items.map((item: any) => ({
-    EventId: item.event_id,
-    EventTime: dayjs(item.event_timestamp).format('YYYY-MM-DD HH:mm:ss'),
-    Score: item.confidence_score,
-    Pan: item.pan_angle,
-    Tilt: item.tilt_angle,
-    Type: item.alarm_type,
-    Status: item.status,
-    imageFileName: getFileUrl(item.image_file_name),
-  }))
+  if (item.event_id) {
+    currentItem.value = {
+      ...item,
+      ID: item.alert_id,
+      UsbCameraIndex: 1,
+      Image: item.image_file_name,
+      ImageData: '',
+      Video: item.video_file_name,
+      Degree: item.degree,
+      EventTime: item.event_timestamp,
+      X: item.target_x,
+      Y: item.target_y,
+      W: item.target_width,
+      H: item.target_height,
+      Score: item.confidence_score,
+      Type: item.alarm_type,
+      Pan: item.pan_angle,
+      Tilt: item.tilt_angle,
+      EventID: item.event_id,
+      Status: item.status,
+      EventTimeFormat: dayjs(item.event_timestamp).format('YYYY-MM-DD HH:mm:ss'),
+      ImgUrl: getFileUrl(item.image_file_name),
+    }
+  } else {
+    currentItem.value = item
+  }
+  drawerShow.value = true
 }
 
 onMounted(() => {
@@ -250,5 +334,6 @@ onUnmounted(() => {
   workerObj.forEach((item: any) => {
     item.close()
   })
+  revokeBlob()
 })
 </script>

+ 78 - 33
src/views/monitoring-platform/video-monitoring.vue

@@ -4,9 +4,14 @@
     :scrollable="false"
   >
     <div
+      id="video-container"
       class="video-container relative flex h-full flex-col justify-center overflow-hidden bg-[#525252]"
     >
-      <div class="video-container-box flex">
+      <div
+        ref="videoBoxRef"
+        id="video-container-box"
+        class="video-container-box flex"
+      >
         <div
           class="relative flex-1"
           v-for="(v, i) in videoInfo"
@@ -14,6 +19,16 @@
           @dblclick="dblclickFn($event, i + 1)"
         >
           <canvas :class="['pub-video' + i, 'w-full']"></canvas>
+          <template v-if="toolForm.referenceLine.show">
+            <i
+              class="absolute top-0 left-0 block w-full border-[1.5px] border-dashed border-[blue]"
+              :style="{ top: toolForm.referenceLine.y1 + 'px' }"
+            ></i>
+            <i
+              class="absolute top-0 left-0 block w-full border-[1.5px] border-dashed border-[blue]"
+              :style="{ top: toolForm.referenceLine.y2 + 'px' }"
+            ></i>
+          </template>
           <div
             v-if="rsRes?.index === i"
             class="z-2 border-2 border-solid border-red-500"
@@ -21,14 +36,6 @@
           ></div>
         </div>
       </div>
-      <!-- <div class="flex">
-        <div>
-
-        </div>
-        <div>
-
-        </div>
-      </div> -->
       <NModal
         v-model:show="showModal"
         title="细节查看"
@@ -54,16 +61,38 @@
         v-if="loading"
         class="absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center bg-[rgba(0,0,0,0.6)]"
       >
-        <NSpin size="medium" />
+        <NSpin
+          size="medium"
+          :border-radius="12"
+        />
       </div>
+
+      <NFloatButton
+        position="absolute"
+        menu-trigger="click"
+        right="15"
+        bottom="15"
+      >
+        <i class="iconify-[tabler--tools-off] iconify"></i>
+        <template #menu>
+          <NFloatButton
+            shape="square"
+            width="100"
+            right="60"
+          >
+            <NSwitch v-model:value="toolForm.referenceLine.show" />
+            <template #description> 参考线 </template>
+          </NFloatButton>
+        </template>
+      </NFloatButton>
     </div>
   </ScrollContainer>
 </template>
 
 <script setup lang="ts">
-import { NModal, NSpin } from 'naive-ui'
+import { NModal, NSpin, NFloatButton, NSwitch } from 'naive-ui'
 import useWorker from 'omnimatrix-video-player'
-import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
+import { computed, nextTick, onMounted, onUnmounted, ref, watch, useTemplateRef, toRefs } from 'vue'
 
 import { useRequest } from '@/api'
 import { ScrollContainer } from '@/components'
@@ -83,6 +112,7 @@ defineOptions({ name: 'VideoMonitoring' })
 const { API_RA_GET } = useRequest()
 const alarmStore = useAlarmStore()
 const workerObj: any[] = []
+const videoBoxRef = useTemplateRef<HTMLDivElement>('videoBoxRef')
 const videoInfo = ref([
   `wss://${alarmStore.ipF}/VideoShow/Preview/Full/Main?DeviceID=1`,
   `wss://${alarmStore.ipF}/VideoShow/Preview/Full/Main?DeviceID=2`,
@@ -90,26 +120,6 @@ const videoInfo = ref([
 const loading = ref(true)
 const partLoading = ref(true)
 
-const showVideo = () => {
-  videoInfo.value.forEach((item: string, i: number) => {
-    workerObj.push(
-      useWorker(item, '.pub-video' + i, () => {
-        enableImageManipulation(
-          document.querySelector('.video-container'),
-          document.querySelector('.video-container-box'),
-          {
-            enableZoom: true, // 启用缩放
-            enableDrag: true, // 启用拖动
-            minScale: 1, // 最小缩放比例
-            dragSpeed: 1.9, // 拖动速度
-          },
-        )
-        loading.value = false
-      }),
-    )
-  })
-}
-
 const showModal = ref(false)
 const partInfo = {
   X: 0,
@@ -118,6 +128,41 @@ const partInfo = {
   // W: 0,
   // H: 0,
 }
+const toolForm = ref({
+  referenceLine: {
+    show: true,
+    y1: 0,
+    y2: 0,
+  },
+})
+
+const showVideo = () => {
+  videoInfo.value.forEach((item: string, i: number) => {
+    workerObj.push(
+      useWorker(item, '.pub-video' + i, () => {
+        nextTick(() => {
+          enableImageManipulation(
+            document.querySelector('#video-container'),
+            document.querySelector('#video-container-box'),
+            {
+              enableZoom: true, // 启用缩放
+              enableDrag: true, // 启用拖动
+              minScale: 1, // 最小缩放比例
+              dragSpeed: 1.9, // 拖动速度
+            },
+          )
+          toolForm.value.referenceLine.y1 = videoBoxRef.value
+            ? 500 * (videoBoxRef.value.offsetHeight / 2160)
+            : 0
+          toolForm.value.referenceLine.y2 = videoBoxRef.value
+            ? 1000 * (videoBoxRef.value.offsetHeight / 2160)
+            : 0
+          loading.value = false
+        })
+      }),
+    )
+  })
+}
 
 const rsRes = ref<IraRes>()
 const rsResBorder = computed(() => ({
@@ -183,7 +228,7 @@ onMounted(() => {
 
 onUnmounted(() => {
   workerObj.forEach((item: any) => {
-    item.close()
+    item?.close()
   })
 })
 </script>