Selaa lähdekoodia

1. 修改球机默认类型,将默认类型命名为Base
2. 修复自动补全rtsp的bug
3. 将outMatrix放在组件内部
4. 所有与点相关的对象,统一使用PTXYMap
5. 修改标记颜色渲染逻辑
6. 增加一些窗口的监听,当窗口和页面切换时触发点位缩放相关的计算
7. PT 位置不再进行自动计算,使用手动刷新按钮进行计算
8. 去除头部操作栏

王帅锟 3 viikkoa sitten
vanhempi
commit
5bae2361c6

+ 1 - 1
package.json

@@ -3,7 +3,7 @@
   "private": true,
   "packageManager": "pnpm@10.1.0+sha1.ab7948c89104fdd3fc88b5b391fa4b73fd800631",
   "scripts": {
-    "build": "vite-ssg build",
+    "build": "vite build",
     "dev": "vite --port 3333 --host",
     "lint": "eslint .",
     "preview": "vite preview",

+ 0 - 1
src/components.d.ts

@@ -10,7 +10,6 @@ declare module 'vue' {
   export interface GlobalComponents {
     AddBall: typeof import('./components/AddBall.vue')['default']
     BallCameraSettings: typeof import('./components/BallCameraSettings.vue')['default']
-    HeaderTool: typeof import('./components/HeaderTool.vue')['default']
     README: typeof import('./components/README.md')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

+ 19 - 20
src/components/AddBall.vue

@@ -14,13 +14,13 @@ const system = useSystemStore()
 
 const name = ref('')
 const ip = ref('')
-const typeV = ref('HikSdk')
+const typeV = ref('Base')
 const port = ref(8000)
 const user = ref('')
 const password = ref('')
 const rtspUrl = ref('')
 const loading = ref(false)
-const typeVOptions = ref([{ label: '海康原汁原味', value: 'HikSdk' }, { label: '海康模组但串口通信(布控球)', value: 'BuKongQiu' }])
+const typeVOptions = ref([{ label: '海康原汁原味', value: 'Base' }, { label: '海康模组但串口通信(布控球)', value: 'BuKongQiu' }])
 
 function resetHide(isActive: Ref<boolean>) {
   // handleReset()
@@ -85,13 +85,9 @@ const rules = {
   ],
 }
 
-const rtspUrlCom = computed({
-  get() {
-    return `rtsp://${user.value}:${password.value}@${ip.value}:${port.value}/0`
-  },
-  set(value: string) {
-    rtspUrl.value = value
-  },
+// 监听 user、password 和 ip 的变化
+watch([user, password, ip], () => {
+  rtspUrl.value = `rtsp://${user.value}:${password.value}@${ip.value}:554/0`
 })
 
 async function submit(event: any) {
@@ -106,7 +102,7 @@ async function submit(event: any) {
         Port: port.value,
         User: user.value,
         Password: password.value,
-        RtspUrl: rtspUrl.value || rtspUrlCom.value,
+        RtspUrl: rtspUrl.value,
         Channel: 1,
       }, { headers: { Deviceid: system.currentDevice } }).then((res) => {
         axios.post(`https://${system.globalConfig.serverUrl}/api/BallCamera/Matrix?CameraId=${res.data.data.BallCameraId}`, {
@@ -128,19 +124,22 @@ async function submit(event: any) {
       })
     }
     else {
-      axios.post(`https://${system.globalConfig.serverUrl}/api/BallCamera?CameraId=${props.cameraId}`, {
-        Type: typeV,
-        Name: name,
-        Ip: ip,
-        Port: port,
-        User: user,
-        Password: password,
-        RtspUrl: rtspUrl,
+      axios.post(`https://${system.globalConfig.serverUrl}/api/BallCamera/Change?CameraId=${props.cameraId}`, {
+        Type: typeV.value,
+        Name: name.value,
+        Ip: ip.value,
+        Port: port.value,
+        User: user.value,
+        Password: password.value,
+        RtspUrl: rtspUrl.value,
         Channel: 1,
-      }).then(() => {
+      }, { headers: { Deviceid: system.currentDevice } }).then(() => {
         loading.value = false
         dialog.value = false
         system.getBallCameraList()
+        if (props.cameraId !== null) {
+          system.getBallCameraInfo(props.cameraId.toString())
+        }
       })
     }
   }
@@ -187,7 +186,7 @@ watch(dialog, (newV) => {
             />
             <v-text-field v-model="user" :rules="rules.user" label="SDK 用户名" />
             <v-text-field v-model="password" :rules="rules.password" label="SDK 密码" />
-            <v-text-field v-model="rtspUrlCom" :rules="rules.rtspUrl" label="RTSP 地址" />
+            <v-text-field v-model="rtspUrl" :rules="rules.rtspUrl" label="RTSP 地址" />
 
             <v-divider />
 

+ 120 - 168
src/components/BallCameraSettings.vue

@@ -11,6 +11,7 @@ const system = useSystemStore()
 
 const addDialogV = ref(false)
 const updDialogV = ref(false)
+const outMatrix = ref<any>(null)
 
 const tools = ref([
   {},
@@ -50,32 +51,26 @@ const naturalWH = {
   width: 0,
   height: 0,
 }
+
+const ShowWidthZoom = ref(1)
+const ShowHeightZoom = ref(1)
+const ShowWidthOffset = ref(0)
+const ShowHeightOffset = ref(0)
+
 function imgSrcLoad() {
   imgSrcLoading.value = false
 }
 
-interface MarkItemType { id: string, p: number, t: number, x: number, y: number, color: string }
-type MarkType = MarkItemType[]
-const mark = ref<MarkType>([])
-const markClone = ref<MarkType>([])
-const currentMark = ref<MarkItemType>({
-  id: '',
-  p: 0.0,
-  t: 0.0,
-  x: 0.0,
-  y: 0.0,
-  color: '#000',
-})
+const currentMark = ref('')
 
-const PTXYMap = new Map<string, { P: number, T: number, X: number, Y: number }>()
+const PTXYMap = ref(new Map<string, { P: number, T: number, X: number, Y: number }>())
 
-// opencv ==============start
+// 计算一个矩阵的逆矩阵
 function calculateInverseMatrix(Matrix: number[]) {
   // 检查矩阵是否有效
   if (Matrix.length !== 9) {
     return []
   }
-
   // 提取矩阵元素
   const a = Matrix[0]
   const b = Matrix[1]
@@ -86,18 +81,14 @@ function calculateInverseMatrix(Matrix: number[]) {
   const g = Matrix[6]
   const h = Matrix[7]
   const i = Matrix[8]
-
   // 计算行列式
   const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g)
-
   // 检查矩阵是否可逆
   if (det === 0) {
     return []
   }
-
   // 计算行列式的倒数
   const invDet = 1.0 / det
-
   return [
     (e * i - f * h) * invDet,
     (c * h - b * i) * invDet,
@@ -110,12 +101,12 @@ function calculateInverseMatrix(Matrix: number[]) {
     (a * e - b * d) * invDet,
   ]
 }
-
-function WarpingPtByHomography(matrix: number[], X: number, Y: number) {
+// 通过单应性矩阵计算映射后的坐标
+function WarpingPtByHomography(X: number, Y: number) {
   let x, y
-  x = matrix[0] * X + matrix[1] * Y + 1.0 * matrix[2]
-  y = matrix[3] * X + matrix[4] * Y + 1.0 * matrix[5]
-  const z = matrix[6] * X + matrix[7] * Y + 1.0 * matrix[8]
+  x = outMatrix.value[0] * X + outMatrix.value[1] * Y + outMatrix.value[2]
+  y = outMatrix.value[3] * X + outMatrix.value[4] * Y + outMatrix.value[5]
+  const z = outMatrix.value[6] * X + outMatrix.value[7] * Y + outMatrix.value[8]
 
   x /= z
   y /= z
@@ -123,46 +114,31 @@ function WarpingPtByHomography(matrix: number[], X: number, Y: number) {
 }
 
 async function buildMatrix() {
-  if (PTXYMap.size < 4) {
+  if (PTXYMap.value.size < 4) {
     system.msg('映射矩阵最少四个')
     return
   }
 
-  const PT: any[] = []; const XY: any[] = []
-  PTXYMap.forEach((v: any) => {
+  const PT: any[] = []
+  const XY: any[] = []
+  PTXYMap.value.forEach((v: any) => {
     PT.push(v.P, v.T)
     XY.push(v.X, v.Y)
   })
   // eslint-disable-next-line new-cap
-  const srcPointMat = new window.cv.matFromArray(4, 1, window.cv.CV_64FC2, XY)
+  const srcPointMat = new window.cv.matFromArray(PT.length, 1, window.cv.CV_64FC2, XY)
   // eslint-disable-next-line new-cap
-  const dtsPointMat = new window.cv.matFromArray(4, 1, window.cv.CV_64FC2, PT)
+  const dtsPointMat = new window.cv.matFromArray(PT.length, 1, window.cv.CV_64FC2, PT)
 
   // 计算单应性矩阵
-  const outMatrix = window.cv.findHomography(srcPointMat, dtsPointMat)
-  window.outMatrix = outMatrix.data64F
-
+  outMatrix.value = window.cv.findHomography(srcPointMat, dtsPointMat, window.cv.RANSAC).data64F
   // 清理临时矩阵(OpenCV.js内存管理)
   srcPointMat.delete()
   dtsPointMat.delete()
-
-  // 应用矩阵到所有点
-  PTXYMap.forEach((v: any) => {
-    if (v.P !== undefined && v.T !== undefined) {
-      const out = WarpingPtByHomography(outMatrix.data64F, v.X, v.Y)
-      v.P = out.X
-      v.T = out.Y
-    }
-  })
 }
 
-// opencv =============end
-
-function markClick(item: MarkItemType) {
-  if (!isDisabled.value)
-    return system.msg('正在装载,请稍后...')
+function markClick(item: string) {
   currentMark.value = item
-  colorReset(item.id)
 }
 
 // 计算原始坐标的函数
@@ -172,95 +148,51 @@ function calculateOriginalCoords(x: number, y: number, originalWidth: number, or
   return { originalX, originalY }
 }
 
-// 重置颜色
-function colorReset(id: string) {
-  mark.value.forEach((v, i) => {
-    v.color = '#000'
-    markClone.value[i].color = '#000'
-  })
-  if (!id)
-    return
-  const current1 = mark.value.length ? mark.value.find(v => v.id === id) : ''
-  const current2 = markClone.value.length ? markClone.value.find(v => v.id === id) : ''
-  current1 && (current1.color = '#FFEB3B')
-  current2 && (current2.color = '#FFEB3B')
-}
-
 // 删除标记
 function del(id: string) {
   if (!id)
     return
-  PTXYMap.delete(id)
-  mark.value = mark.value.filter(v => v.id !== id)
-  markClone.value = markClone.value.filter(v => v.id !== id)
-  mark.value.length && (currentMark.value = mark.value[mark.value.length - 1])
-  mark.value.length && colorReset(currentMark.value.id)
+  PTXYMap.value.delete(id)
 }
 // 清空标记
 function clear() {
-  mark.value = []
-  markClone.value = []
-  currentMark.value = {
-    id: '',
-    p: 0.0,
-    t: 0.0,
-    x: 0.0,
-    y: 0.0,
-    color: '#000',
-  }
-  PTXYMap.clear()
+  currentMark.value = ''
+  PTXYMap.value.clear()
 }
 
-/**
- * 在缩放后的图片上标点(考虑外边距)
- * @param {HTMLElement} imgElement - 图片元素
- */
-function markPointOnImageWithMargin(imgElement: HTMLImageElement) {
+function RefreshDisplayParameters(imgElement: HTMLImageElement) {
+  if (!imgElement) {
+    return
+  }
   const originalWidth = imgElement.naturalWidth // 原始宽度
   const originalHeight = imgElement.naturalHeight // 原始高度
   const scaledWidth = imgElement.offsetWidth // 缩放后宽度
   const scaledHeight = imgElement.offsetHeight // 缩放后高度
   const top = +(imgElement as HTMLImageElement).style.marginTop.replace('px', '') // 外边距上边距
   const left = +(imgElement as HTMLImageElement).style.marginLeft.replace('px', '') // 外边距左边距
-  mark.value = markClone.value.map((v) => {
-    return {
-      id: v.id,
-      x: v.x * (scaledWidth / originalWidth) + left - 12,
-      y: v.y * (scaledHeight / originalHeight) + top - 12,
-      p: v.p,
-      t: v.t,
-      color: v.color,
-    }
-  })
-  mark.value.length && colorReset(currentMark.value.id)
+
+  ShowWidthZoom.value = scaledWidth / originalWidth
+  ShowHeightZoom.value = scaledHeight / originalHeight
+  ShowWidthOffset.value = left
+  ShowHeightOffset.value = top
 }
 
 function dbClick(e: MouseEvent) {
   // 获取鼠标点击位置
   const { offsetX, offsetY } = e
   const { originalX, originalY } = calculateOriginalCoords(offsetX, offsetY, (imgElement as HTMLImageElement).naturalWidth, (imgElement as HTMLImageElement).naturalHeight, (imgElement as HTMLImageElement).offsetWidth, (imgElement as HTMLImageElement).offsetHeight)
-
-  const markV = {
-    id: uuidv4(),
-    x: originalX,
-    y: originalY,
-    p: 0.0,
-    t: 0.0,
-    color: '#000',
-  }
-  mark.value.push(markV)
-  markClone.value.push(markV)
-  currentMark.value = mark.value[mark.value.length - 1]
-  PTXYMap.set(currentMark.value.id, { P: 0.0, T: 0.0, X: currentMark.value.x, Y: currentMark.value.y })
-  markPointOnImageWithMargin(imgElement as HTMLImageElement)
+  const id = uuidv4()
+  PTXYMap.value.set(id, { P: 0.0, T: 0.0, X: originalX, Y: originalY })
+  currentMark.value = id
+  RefreshDisplayParameters(imgElement as HTMLImageElement)
 }
 
-function getPTXY(id: string, x: number, y: number) {
+function getPTXY(id: string) {
   if (!system.globalConfig.serverUrl) {
     system.msg('请先设置服务器地址')
     return
   }
-  if (!currentMark.value.id) {
+  if (!currentMark.value) {
     system.msg('请双击创建标记')
     return
   }
@@ -268,19 +200,20 @@ function getPTXY(id: string, x: number, y: number) {
     system.msg('请选择球机')
     return
   }
+
+  // 获取已有的 X/Y
+  const existing = PTXYMap.value.get(currentMark.value)
+  const x = existing?.X ?? 0
+  const y = existing?.Y ?? 0
+
   system.msg('正在装载,请稍后...')
   isDisabled.value = false
   axios.get(`https://${system.globalConfig.serverUrl}/api/BallCamera/PtzGet?CameraId=${id}`).then((res) => {
-    PTXYMap.set(currentMark.value.id, { P: res.data.data.P, T: res.data.data.T, X: x, Y: y })
-    mark.value.forEach((v, i) => {
-      if (v.id === currentMark.value.id) {
-        const p = system.mapping(system.positiveConfig.P_Start, system.positiveConfig.P_Max, res.data.data.P, system.positiveConfig.P_Direction, '')
-        const t = system.mapping(system.positiveConfig.T_Start, system.positiveConfig.T_Max, res.data.data.T, system.positiveConfig.T_Direction, '')
-        v.p = p
-        v.t = t
-        markClone.value[i].p = p
-        markClone.value[i].t = t
-      }
+    PTXYMap.value.set(currentMark.value, {
+      P: system.mapping(system.positiveConfig.P_Start, system.positiveConfig.P_Max, res.data.data.P, system.positiveConfig.P_Direction, ''),
+      T: system.mapping(system.positiveConfig.T_Start, system.positiveConfig.T_Max, res.data.data.T, system.positiveConfig.T_Direction, ''),
+      X: x,
+      Y: y,
     })
     isDisabled.value = true
     system.msg('装载完成')
@@ -290,43 +223,47 @@ function getPTXY(id: string, x: number, y: number) {
 function loadOld() {
   if (!system.ballCameraInfo)
     return
-  PTXYMap.clear()
+  PTXYMap.value.clear()
   const ps: any = system.ballCameraInfo.Matrix.PointSet
 
   for (const key in ps) {
-    PTXYMap.set(key, {
+    PTXYMap.value.set(key, {
       P: ps[key].P,
       T: ps[key].T,
       X: ps[key].X,
       Y: ps[key].Y,
     })
-    mark.value.push({
-      id: key,
-      x: ps[key].X,
-      y: ps[key].Y,
-      p: ps[key].P,
-      t: ps[key].T,
-      color: '#000',
-    })
   }
-
-  if (!mark.value.length)
-    return
-  markClone.value = JSON.parse(JSON.stringify(mark.value))
-  colorReset(mark.value[mark.value.length - 1].id)
-  currentMark.value = mark.value[mark.value.length - 1]
-  markPointOnImageWithMargin(imgElement as HTMLImageElement)
+  currentMark.value = ''
+  RefreshDisplayParameters(imgElement as HTMLImageElement)
 }
 
-function jumpToPT(p: number, t: number) {
+function jumpToPT() {
   if (!system.globalConfig.serverUrl) {
     system.msg('请先设置服务器地址')
     return
   }
-  const pV = system.mapping(system.positiveConfig.P_Start, system.positiveConfig.P_Max, p, system.positiveConfig.P_Direction, 'inv')
-  const tV = system.mapping(system.positiveConfig.T_Start, system.positiveConfig.T_Max, t, system.positiveConfig.T_Direction, 'inv')
-  axios.put(`https://${system.globalConfig.serverUrl}/api/BallCamera/PtzSet?CameraId=${system.currentBallCamera}&Action=1`, { P: pV, T: tV, Z: 0 }).then((res) => {
-    console.log(res)
+  if (!PTXYMap.value.get(currentMark.value)) {
+    system.msg('未选中标记')
+    return
+  }
+
+  // Get the current mark's data from PTXYMap
+  const currentMarkData = PTXYMap.value.get(currentMark.value)
+
+  // Check if the data exists, if not, set pV and tV to default values
+  const pV = currentMarkData
+    ? system.mapping(system.positiveConfig.P_Start, system.positiveConfig.P_Max, currentMarkData.P, system.positiveConfig.P_Direction, 'inv')
+    : 0
+  const tV = currentMarkData
+    ? system.mapping(system.positiveConfig.T_Start, system.positiveConfig.T_Max, currentMarkData.T, system.positiveConfig.T_Direction, 'inv')
+    : 0
+  axios.put(`https://${system.globalConfig.serverUrl}/api/BallCamera/PtzSet?CameraId=${system.currentBallCamera}&Action=1`, {
+    P: pV,
+    T: tV,
+    Z: 0,
+  }).then((res) => {
+    console.warn(res)
   })
 }
 
@@ -335,16 +272,9 @@ function ptByMatrix() {
     system.msg('请先设置服务器地址')
     return
   }
-  const out: any = WarpingPtByHomography(window.outMatrix, currentMark.value.x, currentMark.value.y)
-  PTXYMap.set(currentMark.value.id, { P: out.P, T: out.T, X: currentMark.value.x, Y: currentMark.value.y })
-  mark.value.forEach((v, i) => {
-    if (v.id === currentMark.value.id) {
-      v.p = out.P
-      v.t = out.T
-      markClone.value[i].p = out.P
-      markClone.value[i].t = out.T
-    }
-  })
+  const v = PTXYMap.value.get(currentMark.value)
+  const out: any = WarpingPtByHomography(v ? v.X : 0, v ? v.Y : 0)
+  PTXYMap.value.set(currentMark.value, { P: out.X, T: out.Y, X: v ? v.X : 0, Y: v ? v.Y : 0 })
 }
 
 function updateMatrix(callback?: () => void) {
@@ -357,7 +287,7 @@ function updateMatrix(callback?: () => void) {
     return
   }
   const pointSet: any = {}
-  PTXYMap.forEach((v, k) => {
+  PTXYMap.value.forEach((v, k) => {
     pointSet[k] = {
       P: v.P,
       T: v.T,
@@ -372,7 +302,7 @@ function updateMatrix(callback?: () => void) {
     T_Start: system.positiveConfig.T_Start,
     T_Max: system.positiveConfig.T_Max,
     T_Positive_Direction: system.positiveConfig.T_Direction,
-    Matrix: window.outMatrix || system.ballCameraInfo?.Matrix.Matrix || [],
+    Matrix: outMatrix.value || system.ballCameraInfo?.Matrix.Matrix || [],
     InvMatrix: system.ballCameraInfo?.Matrix.InvMatrix || [],
     PointSet: JSON.stringify(pointSet) === '{}' ? system.ballCameraInfo?.Matrix.PointSet : pointSet,
   }
@@ -383,11 +313,11 @@ function updateMatrix(callback?: () => void) {
 }
 
 function saveBtn() {
-  if (!window.outMatrix) {
+  if (!outMatrix.value) {
     system.msg('请先生成映射矩阵')
     return
   }
-  const invMatrix = calculateInverseMatrix(window.outMatrix)
+  const invMatrix = calculateInverseMatrix(outMatrix.value)
   system.ballCameraInfo!.Matrix.InvMatrix = invMatrix
   updateMatrix(() => {
     system.msg('保存成功')
@@ -428,10 +358,10 @@ function initViewer() {
       naturalWH.height = e.detail.image.naturalHeight
     },
     zoomed() {
-      markPointOnImageWithMargin(imgElement as HTMLImageElement)
+      RefreshDisplayParameters(imgElement as HTMLImageElement)
     },
     moved() {
-      markPointOnImageWithMargin(imgElement as HTMLImageElement)
+      RefreshDisplayParameters(imgElement as HTMLImageElement)
     },
   })
 }
@@ -449,6 +379,19 @@ watch(() => system.globalConfig.imgUrl, (file) => {
 watch(() => system.positiveConfig, () => {
   updateMatrix()
 }, { deep: true })
+// 监听窗口变化
+window.addEventListener('resize', () => {
+  RefreshDisplayParameters(imgElement as HTMLImageElement)
+})
+
+document.addEventListener('visibilitychange', () => {
+  if (document.hidden) {
+    RefreshDisplayParameters(imgElement as HTMLImageElement)
+  }
+  else {
+    RefreshDisplayParameters(imgElement as HTMLImageElement)
+  }
+})
 </script>
 
 <template>
@@ -564,9 +507,17 @@ watch(() => system.positiveConfig, () => {
         <!-- 视频播放 -->
         <div class="p-1">
           <v-card
-            :loading="system.canvasVideoLoading" title="画面预览"
-            :subtitle="`当前P ${system.PT.p},当前T ${system.PT.t},当前标定点位的个数0`" text=""
+            :loading="system.canvasVideoLoading" title="画面预览" text=""
           >
+            <template #subtitle>
+              <div class="d-flex align-center justify-between">
+                <span>当前P {{ system.PT.p }},当前T {{ system.PT.t }},当前标定点位的个数{{ PTXYMap.size }}</span>
+                <v-btn
+                  v-tooltip:top="'刷新PT位置'" size="large" rounded="0" variant="flat" icon="mdi-refresh"
+                  @click="system.GetPT(system.currentBallCamera)"
+                />
+              </div>
+            </template>
             <div class="relative aspect-ratio-video b-1 b-#ccc b-solid bg-black">
               <v-icon
                 icon="mdi-plus" color="#ef4444" class="absolute left-50% top-50% transform-translate--50%" :style="{
@@ -640,19 +591,20 @@ watch(() => system.positiveConfig, () => {
           <img id="uploadImage" class="w-full" :src="imgSrc" alt="图片预览" @load="imgSrcLoad">
           <v-empty-state v-if="!imgSrcLoading && !imgSrc" icon="mdi-image-broken-variant" title="暂无上传图片" />
           <v-icon
-            v-for="(item, index) in mark" :key="index" :style="{
-              top: `${item.y}px`,
-              left: `${item.x}px`,
+            v-for="(item) in Array.from(PTXYMap)" :key="item[0]"
+            :style="{
+              top: `${(item[1].Y * ShowHeightZoom + ShowHeightOffset) - 12}px`,
+              left: `${(item[1].X * ShowWidthZoom + ShowWidthOffset) - 12}px`,
               textShadow: `0 -1px #fff, 1px 0px #fff, 0 1px #fff, -1px 0 #fff,
           -1px -1px #fff, 1px 1px #fff, 1px -1px #fff, -1px 1px #fff`,
-            }" :color="item.color" icon="mdi-plus-thick" class="absolute bottom-0" variant="tonal"
-            @click="markClick(item)"
+            }" :color="currentMark === item[0] ? '#FFEB3B' : '#000000'" icon="mdi-plus-thick" class="absolute bottom-0" variant="tonal"
+            @click="markClick(item[0])"
           />
         </div>
         <div class="flex flex-col items-center">
           <v-chip class="ma-2" color="blue" label>
             <v-icon icon="mdi-map-marker-radius" start />
-            选中的标记 P: {{ currentMark.p }}, T: {{ currentMark.t }}
+            选中的标记 P: {{ PTXYMap.get(currentMark)?.P }}, T: {{ PTXYMap.get(currentMark)?.T }}
           </v-chip>
           <v-card min-width="90%">
             <v-card-text>
@@ -664,7 +616,7 @@ watch(() => system.positiveConfig, () => {
                 </div>
                 <v-row justify="center">
                   <v-col>
-                    <v-btn width="100%" @click="getPTXY(system.currentBallCamera, currentMark.x, currentMark.y)">
+                    <v-btn width="100%" @click="getPTXY(system.currentBallCamera)">
                       当前PT位置装载PT
                     </v-btn>
                   </v-col>
@@ -680,7 +632,7 @@ watch(() => system.positiveConfig, () => {
                     </v-btn>
                   </v-col>
                   <v-col>
-                    <v-btn width="100%" @click="jumpToPT(currentMark.p, currentMark.t)">
+                    <v-btn width="100%" @click="jumpToPT">
                       跳转到PT
                     </v-btn>
                   </v-col>
@@ -689,7 +641,7 @@ watch(() => system.positiveConfig, () => {
                   <v-btn color="#FFA726" @click="clear">
                     清空
                   </v-btn>
-                  <v-btn color="#F44336" @click="del(currentMark.id)">
+                  <v-btn color="#F44336" @click="del(currentMark)">
                     删除选中
                   </v-btn>
                 </div>

+ 0 - 45
src/components/HeaderTool.vue

@@ -1,45 +0,0 @@
-<script setup lang="ts">
-import { useLocale, useTheme } from 'vuetify'
-import { availableLocales, loadLanguageAsync } from '~/modules/i18n'
-
-const { t, locale } = useI18n()
-const { current } = useLocale()
-const theme = useTheme()
-
-function toggleTheme() {
-  toggleDark()
-  theme.global.name.value = isDark.value ? 'light' : 'dark'
-}
-async function toggleLocales() {
-  // change to some real logic
-  const locales = availableLocales
-  const newLocale = locales[(locales.indexOf(locale.value) + 1) % locales.length]
-  await loadLanguageAsync(newLocale)
-  locale.value = newLocale
-  current.value = newLocale === 'zh-CN' ? 'zhHans' : newLocale
-}
-</script>
-
-<template>
-  <nav flex="~ gap-4" justify-center text-xl>
-    <!-- <RouterLink icon-btn to="/" :title="t('button.home')">
-      <div i-carbon-campsite />
-    </RouterLink> -->
-
-    <button icon-btn :title="t('button.toggle_dark')" @click="toggleTheme">
-      <div i="carbon-sun dark:carbon-moon" />
-    </button>
-
-    <!-- <a icon-btn :title="t('button.toggle_langs')" @click="toggleLocales()">
-      <div i-carbon-language />
-    </a> -->
-
-    <!-- <RouterLink icon-btn to="/about" :title="t('button.about')" data-test-id="about">
-      <div i-carbon-dicom-overlay />
-    </RouterLink>
-
-    <a icon-btn rel="noreferrer" href="https://github.com/antfu/vitesse" target="_blank" title="GitHub">
-      <div i-carbon-logo-github />
-    </a> -->
-  </nav>
-</template>

+ 2 - 10
src/layouts/home.vue

@@ -1,15 +1,7 @@
-<script setup lang="ts">
-const theme = computed(() => isDark.value ? 'dark' : 'light')
-</script>
-
 <template>
-  <v-app :theme="theme">
-    <v-app-bar class="px-3">
-      <v-app-bar-title>CAMERA-MODULE-TOOL</v-app-bar-title>
-      <HeaderTool />
-    </v-app-bar>
+  <v-app>
     <v-main scrollable>
-      <v-container fluid>
+      <v-container fluid style="padding: 0">
         <RouterView />
       </v-container>
     </v-main>

+ 0 - 14
src/modules/pwa.ts

@@ -1,14 +0,0 @@
-import type { UserModule } from '~/types'
-
-// https://github.com/antfu/vite-plugin-pwa#automatic-reload-when-new-content-available
-export const install: UserModule = ({ isClient, router }) => {
-  if (!isClient)
-    return
-
-  router.isReady()
-    .then(async () => {
-      const { registerSW } = await import('virtual:pwa-register')
-      registerSW({ immediate: true })
-    })
-    .catch(() => {})
-}

+ 1 - 2
src/shims.d.ts

@@ -1,7 +1,6 @@
 declare interface Window {
   // extend the window
-  cv: any,
-  outMatrix: any
+  cv: any
 }
 
 // with unplugin-vue-markdown, markdown files can be treated as Vue components

+ 12 - 9
src/stores/system.ts

@@ -2,15 +2,17 @@ import axios from 'axios'
 import useWorker from 'omnimatrix-video-player'
 import { acceptHMRUpdate, defineStore } from 'pinia'
 
+// 设置 axios 的默认超时时间
 axios.defaults.timeout = 15000
+// 对响应数据进行处理
 axios.interceptors.response.use((response) => {
   return response
 }, (error) => {
   console.error(error)
   return Promise.reject(error)
 })
-
-interface IVersion {
+// 获取服务端的版本信息
+interface ServiceVersion {
   Baseline: string
   SubSystem: string
   Version: string
@@ -59,7 +61,7 @@ export const useSystemStore = defineStore('system', () => {
     serverUrl: '',
   })
   const ballCameraList = ref<IOptions[]>([])
-  const type = ref<IVersion>()
+  const type = ref<ServiceVersion>()
   const deviceList = ref<IDeviceListOptions[]>([])
   const ballCameraInfo = ref<IBallCameraInfo>()
   const positiveConfig = reactive({
@@ -243,12 +245,6 @@ export const useSystemStore = defineStore('system', () => {
     if (PTTimer.value) {
       clearTimeout(PTTimer.value)
     }
-    PTTimer.value = setTimeout(() => {
-      axios.get(`https://${globalConfig.serverUrl}/api/BallCamera/PtzGet?CameraId=${id}`).then((res) => {
-        PT.p = mapping(positiveConfig.P_Start, positiveConfig.P_Max, res.data.data.P, positiveConfig.P_Direction, '')
-        PT.t = mapping(positiveConfig.T_Start, positiveConfig.T_Max, res.data.data.T, positiveConfig.T_Direction, '')
-      })
-    }, 3000)
     axios.put(`https://${globalConfig.serverUrl}/api/BallCamera/Move?CameraId=${id}`, data)
   }
   function ballStop(id: string, data: {
@@ -264,6 +260,12 @@ export const useSystemStore = defineStore('system', () => {
     }
     axios.put(`https://${globalConfig.serverUrl}/api/BallCamera/Stop?CameraId=${id}`, data)
   }
+  function GetPT(id: string) {
+    axios.get(`https://${globalConfig.serverUrl}/api/BallCamera/PtzGet?CameraId=${id}`).then((res) => {
+      PT.p = mapping(positiveConfig.P_Start, positiveConfig.P_Max, res.data.data.P, positiveConfig.P_Direction, '')
+      PT.t = mapping(positiveConfig.T_Start, positiveConfig.T_Max, res.data.data.T, positiveConfig.T_Direction, '')
+    })
+  }
 
   function delBallCamera(id: string) {
     if (!globalConfig.serverUrl) {
@@ -312,6 +314,7 @@ export const useSystemStore = defineStore('system', () => {
     ballStop,
     PT,
     mapping,
+    GetPT,
   }
 })
 

+ 32 - 29
vite.config.ts

@@ -13,7 +13,7 @@ import Markdown from 'unplugin-vue-markdown/vite'
 import { VueRouterAutoImports } from 'unplugin-vue-router'
 import VueRouter from 'unplugin-vue-router/vite'
 import { defineConfig } from 'vite'
-import { VitePWA } from 'vite-plugin-pwa'
+// import { VitePWA } from 'vite-plugin-pwa'
 // import VueDevTools from 'vite-plugin-vue-devtools'
 import Layouts from 'vite-plugin-vue-layouts'
 import generateSitemap from 'vite-ssg-sitemap'
@@ -108,33 +108,36 @@ export default defineConfig({
     }),
 
     // https://github.com/antfu/vite-plugin-pwa
-    VitePWA({
-      registerType: 'autoUpdate',
-      includeAssets: ['favicon.svg', 'safari-pinned-tab.svg'],
-      manifest: {
-        name: 'Vitesse',
-        short_name: 'Vitesse',
-        theme_color: '#ffffff',
-        icons: [
-          {
-            src: '/pwa-192x192.png',
-            sizes: '192x192',
-            type: 'image/png',
-          },
-          {
-            src: '/pwa-512x512.png',
-            sizes: '512x512',
-            type: 'image/png',
-          },
-          {
-            src: '/pwa-512x512.png',
-            sizes: '512x512',
-            type: 'image/png',
-            purpose: 'any maskable',
-          },
-        ],
-      },
-    }),
+    // VitePWA({
+    //   registerType: 'autoUpdate',
+    //   includeAssets: ['favicon.svg', 'safari-pinned-tab.svg'],
+    //   workbox: {
+    //     globIgnores: ['**/assets/ffmpeg-core-*.wasm','/js/'], // 排除特定文件
+    //   },
+    //   manifest: {
+    //     name: 'Vitesse',
+    //     short_name: 'Vitesse',
+    //     theme_color: '#ffffff',
+    //     icons: [
+    //       {
+    //         src: '/pwa-192x192.png',
+    //         sizes: '192x192',
+    //         type: 'image/png',
+    //       },
+    //       {
+    //         src: '/pwa-512x512.png',
+    //         sizes: '512x512',
+    //         type: 'image/png',
+    //       },
+    //       {
+    //         src: '/pwa-512x512.png',
+    //         sizes: '512x512',
+    //         type: 'image/png',
+    //         purpose: 'any maskable',
+    //       },
+    //     ],
+    //   },
+    // }),
 
     // https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n
     VueI18n({
@@ -172,6 +175,6 @@ export default defineConfig({
 
   ssr: {
     // TODO: workaround until they support native ESM
-    noExternal: ['workbox-window', /vue-i18n/],
+    noExternal: ['workbox-window', /vue-i18n/, 'vuetify'],
   },
 })