浏览代码

master: Fixed 问题修复7.4

gitboyzcf 3 月之前
父节点
当前提交
bd276835a5

+ 1 - 0
.gitignore

@@ -4,6 +4,7 @@
 *.local
 *.local
 dist
 dist
 dist-ssr
 dist-ssr
+cmt
 node_modules
 node_modules
 .idea/
 .idea/
 *.log
 *.log

+ 2 - 2
package.json

@@ -24,13 +24,13 @@
     "nprogress": "catalog:frontend",
     "nprogress": "catalog:frontend",
     "omnimatrix-video-player": "file:omnimatrix-video-player",
     "omnimatrix-video-player": "file:omnimatrix-video-player",
     "pinia": "catalog:frontend",
     "pinia": "catalog:frontend",
+    "uuid": "catalog:frontend",
     "vee-validate": "catalog:frontend",
     "vee-validate": "catalog:frontend",
     "viewerjs": "catalog:frontend",
     "viewerjs": "catalog:frontend",
     "vue": "catalog:frontend",
     "vue": "catalog:frontend",
     "vue-i18n": "catalog:frontend",
     "vue-i18n": "catalog:frontend",
     "vue-router": "catalog:frontend",
     "vue-router": "catalog:frontend",
-    "vuetify": "catalog:frontend",
-    "uuid": "catalog:frontend"
+    "vuetify": "catalog:frontend"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@antfu/eslint-config": "catalog:dev",
     "@antfu/eslint-config": "catalog:dev",

二进制
public/assets/image/test.jpg


+ 0 - 1
src/components.d.ts

@@ -15,7 +15,6 @@ declare module 'vue' {
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     RouterView: typeof import('vue-router')['RouterView']
     TheCounter: typeof import('./components/TheCounter.vue')['default']
     TheCounter: typeof import('./components/TheCounter.vue')['default']
-    TheFooter: typeof import('./components/TheFooter.vue')['default']
     TheInput: typeof import('./components/TheInput.vue')['default']
     TheInput: typeof import('./components/TheInput.vue')['default']
   }
   }
 }
 }

+ 69 - 29
src/components/AddBall.vue

@@ -1,6 +1,14 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import axios from 'axios'
 import axios from 'axios'
 
 
+const props = withDefaults(defineProps<{
+  [key: string]: any
+  flag?: string
+  cameraId?: number | null
+}>(), {
+  flag: 'add',
+  cameraId: null,
+})
 const dialog = defineModel<boolean>('dialog', { type: Boolean, default: false })
 const dialog = defineModel<boolean>('dialog', { type: Boolean, default: false })
 const system = useSystemStore()
 const system = useSystemStore()
 
 
@@ -32,7 +40,7 @@ const rules = {
       if (!value) {
       if (!value) {
         return '请输入球机IP'
         return '请输入球机IP'
       }
       }
-      else if (/\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}/.test(value)) {
+      else if (/(?:2(?:5[0-5]|[0-4]\d)|[01]?\d{1,2})(?:\.(?:2(?:5[0-5]|[0-4]\d)|[01]?\d{1,2})){3}/.test(value)) {
         return true
         return true
       }
       }
       else {
       else {
@@ -90,37 +98,70 @@ async function submit(event: any) {
   loading.value = true
   loading.value = true
   const results = await event
   const results = await event
   if (results.valid) {
   if (results.valid) {
-    axios.put(`https://${system.globalConfig.serverUrl}/api/BallCamera/Register`, {
-      Type: typeV,
-      Name: name,
-      Ip: ip,
-      Port: port,
-      User: user,
-      Password: password,
-      RtspUrl: rtspUrl,
-      Channel: 1,
-    }, { headers: { Deviceid: system.currentDevice } }).then((res) => {
-      axios.post(`https://${system.globalConfig.serverUrl}/api/BallCamera/Matrix?CameraId=${res.data.data}`, {
-        P_Start: 0.0,
-        P_Max: 360,
-        p_Positive_Direction: '+',
-        T_Start: 0.0,
-        T_Max: 180,
-        T_Positive_Direction: '+',
-        Matrix: [],
-        InvMatrix: [],
-        PointSet: {},
-      }, { headers: { Deviceid: system.currentDevice } }).then(() => {
+    if (props.flag === 'add') {
+      axios.put(`https://${system.globalConfig.serverUrl}/api/BallCamera/Register`, {
+        Type: typeV.value,
+        Name: name.value,
+        Ip: ip.value,
+        Port: port.value,
+        User: user.value,
+        Password: password.value,
+        RtspUrl: rtspUrl.value || rtspUrlCom.value,
+        Channel: 1,
+      }, { headers: { Deviceid: system.currentDevice } }).then((res) => {
+        axios.post(`https://${system.globalConfig.serverUrl}/api/BallCamera/Matrix?CameraId=${res.data.data.BallCameraId}`, {
+          P_Start: 0.0,
+          P_Max: 360,
+          p_Positive_Direction: '+',
+          T_Start: 0.0,
+          T_Max: 180,
+          T_Positive_Direction: '+',
+          Matrix: [],
+          InvMatrix: [],
+          PointSet: {},
+        }, { headers: { Deviceid: system.currentDevice } }).then(() => {
+          loading.value = false
+          dialog.value = false
+          system.msg('添加成功')
+          system.getBallCameraList()
+        })
+      })
+    }
+    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,
+        Channel: 1,
+      }).then(() => {
         loading.value = false
         loading.value = false
         dialog.value = false
         dialog.value = false
         system.getBallCameraList()
         system.getBallCameraList()
       })
       })
-    })
+    }
   }
   }
   else {
   else {
     loading.value = false
     loading.value = false
   }
   }
 }
 }
+
+watch(dialog, (newV) => {
+  if (newV) {
+    if (props.flag === 'edit') {
+      name.value = system.ballCameraInfo?.Name as string
+      ip.value = system.ballCameraInfo?.Ip as string
+      typeV.value = system.ballCameraInfo?.Type as string
+      port.value = system.ballCameraInfo?.Port as number
+      user.value = system.ballCameraInfo?.User as string
+      password.value = system.ballCameraInfo?.Password as string
+      rtspUrl.value = system.ballCameraInfo?.RtspUrl as string
+    }
+  }
+})
 </script>
 </script>
 
 
 <template>
 <template>
@@ -130,20 +171,19 @@ async function submit(event: any) {
     </template>
     </template>
 
 
     <template #default="{ isActive }">
     <template #default="{ isActive }">
-      <v-card prepend-icon="mdi-plus" title="添加球机">
+      <v-card prepend-icon="mdi-plus" :title="flag === 'add' ? '添加球机' : '修改球机'">
         <v-card-item>
         <v-card-item>
           <v-form validate-on="submit lazy" :disabled="!system.globalConfig.serverUrl" @submit.prevent="submit">
           <v-form validate-on="submit lazy" :disabled="!system.globalConfig.serverUrl" @submit.prevent="submit">
             <v-select
             <v-select
-              v-model="typeV" :items="typeVOptions"
-              :rules="rules.typeV" item-title="label" item-value="value" label="球机类型"
+              v-model="typeV" :items="typeVOptions" :rules="rules.typeV" item-title="label" item-value="value"
+              label="球机类型"
             />
             />
             <v-text-field v-model="name" :rules="rules.name" label="球机名称" />
             <v-text-field v-model="name" :rules="rules.name" label="球机名称" />
 
 
             <v-text-field v-model="ip" :rules="rules.ip" label="球机 IP" />
             <v-text-field v-model="ip" :rules="rules.ip" label="球机 IP" />
             <v-number-input
             <v-number-input
-              v-model="port"
-              :rules="rules.port" :reverse="false"
-              control-variant="stacked" label="球机私有协议端口" :hide-input="false" inset
+              v-model="port" :rules="rules.port" :reverse="false" control-variant="stacked"
+              label="球机私有协议端口" :hide-input="false" inset
             />
             />
             <v-text-field v-model="user" :rules="rules.user" label="SDK 用户名" />
             <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="password" :rules="rules.password" label="SDK 密码" />

+ 268 - 147
src/components/BallCameraSettings.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import axios from 'axios'
 import axios from 'axios'
-import { v4 as uuidv4 } from 'uuid';
+import { v4 as uuidv4 } from 'uuid'
 import Viewer from 'viewerjs'
 import Viewer from 'viewerjs'
 import 'viewerjs/dist/viewer.css'
 import 'viewerjs/dist/viewer.css'
 
 
@@ -8,9 +8,9 @@ const positiveDirectionPOptions = ref([{ label: '+', value: '+' }, { label: '-',
 const positiveDirectionTOptions = ref([{ label: '+', value: '+' }, { label: '-', value: '-' }])
 const positiveDirectionTOptions = ref([{ label: '+', value: '+' }, { label: '-', value: '-' }])
 
 
 const system = useSystemStore()
 const system = useSystemStore()
-const currentBallCamera = ref('')
 
 
 const addDialogV = ref(false)
 const addDialogV = ref(false)
+const updDialogV = ref(false)
 
 
 const tools = ref([
 const tools = ref([
   {},
   {},
@@ -40,9 +40,10 @@ const tools = ref([
   {},
   {},
 ])
 ])
 
 
+const isDisabled = ref(true)
 const step = ref(7)
 const step = ref(7)
 
 
-const imgSrc = ref('/assets/image/test.jpg')
+const imgSrc = ref('')
 const imgSrcLoading = ref(true)
 const imgSrcLoading = ref(true)
 let imgElement: HTMLImageElement | null = null
 let imgElement: HTMLImageElement | null = null
 const naturalWH = {
 const naturalWH = {
@@ -66,96 +67,100 @@ const currentMark = ref<MarkItemType>({
   color: '#000',
   color: '#000',
 })
 })
 
 
-// opencv ==============start
-async function buildMatrix() {
-  let srcPoints: any = [];
-  let dstPoints: any = [];
-
-  // 遍历点集收集源点和目标点
-  // Object.values(_imgController.p).forEach(v => {
-  //     if (v.p !== undefined && v.t !== undefined) {
-  //         srcPoints.push([v.x, v.y]);
-  //         dstPoints.push([v.p, v.t]);
-  //     }
-  // });
-  PTXYMap.forEach(function (v: any, key) {
-    if (v.P !== undefined && v.T !== undefined) {
-      srcPoints.push([v.X, v.Y]);
-      dstPoints.push([v.P, v.T]);
-    }
-  })
-
-  let matrix = null;
-
-  if (srcPoints.length >= 4) {
-    // 将点集转换为OpenCV矩阵格式
-    const srcMat = pointsToMat(srcPoints);
-    const dstMat = pointsToMat(dstPoints);
+const PTXYMap = new Map<string, { P: number, T: number, X: number, Y: number }>()
 
 
-    // 计算单应性矩阵
-    matrix = findHomography(srcMat, dstMat);
-    console.log(matrix);
+// opencv ==============start
+function calculateInverseMatrix(Matrix: number[]) {
+  // 检查矩阵是否有效
+  if (Matrix.length !== 9) {
+    return []
+  }
 
 
-    // 清理临时矩阵(OpenCV.js内存管理)
-    srcMat.delete();
-    dstMat.delete();
+  // 提取矩阵元素
+  const a = Matrix[0]
+  const b = Matrix[1]
+  const c = Matrix[2]
+  const d = Matrix[3]
+  const e = Matrix[4]
+  const f = Matrix[5]
+  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 []
   }
   }
 
 
-  // 应用矩阵到所有点
-  PTXYMap.forEach(function (v: any, key) {
-    if (v.P !== undefined && v.T !== undefined) {
-      matrix2Point(v, matrix);
-    }
-  })
+  // 计算行列式的倒数
+  const invDet = 1.0 / det
+
+  return [
+    (e * i - f * h) * invDet,
+    (c * h - b * i) * invDet,
+    (b * f - c * e) * invDet,
+    (f * g - d * i) * invDet,
+    (a * i - c * g) * invDet,
+    (c * d - a * f) * invDet,
+    (d * h - e * g) * invDet,
+    (b * g - a * h) * invDet,
+    (a * e - b * d) * invDet,
+  ]
 }
 }
 
 
-// 将点数组转换为OpenCV矩阵(CV_64FC2类型)
-function pointsToMat(points: any) {
-  const data = new Float64Array(points.length * 2);
-  points.forEach(([x, y]:[any, any], i: number) => {
-    data[i * 2] = x;
-    data[i * 2 + 1] = y;
-  });
-  return window.cv.matFromArray(points.length, 1, window.cv.CV_64FC2, data);
-}
+function WarpingPtByHomography(matrix: number[], 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]
 
 
-// 计算单应性矩阵
-function findHomography(srcPoints:any, dstPoints:any) {
-  const homography = new window.cv.Mat();
-    // 使用基本参数版本:src, dst, method (0), ransacReprojThreshold (3)
-    window.cv.findHomography(srcPoints, dstPoints, homography, 0, 3);
-    return homography;
+  x /= z
+  y /= z
+  return { X: x, Y: y }
 }
 }
 
 
-// 应用矩阵变换到单个点
-function matrix2Point(v:any, matrix:any) {
-  if (!matrix || matrix.empty()) return;
+async function buildMatrix() {
+  if (PTXYMap.size < 4) {
+    system.msg('映射矩阵最少四个')
+    return
+  }
 
 
-  // 创建输入矩阵(齐次坐标)
-  const src = window.cv.matFromArray(3, 1, window.cv.CV_64FC1, [v.X, v.Y, 1]);
-  const dst = new window.cv.Mat();
+  const PT: any[] = []; const XY: any[] = []
+  PTXYMap.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)
+  // eslint-disable-next-line new-cap
+  const dtsPointMat = new window.cv.matFromArray(4, 1, window.cv.CV_64FC2, PT)
 
 
-  // 执行矩阵变换
-  window.cv.gemm(matrix, src, 1, null, 0, dst);
+  // 计算单应性矩阵
+  const outMatrix = window.cv.findHomography(srcPointMat, dtsPointMat)
+  window.outMatrix = outMatrix.data64F
 
 
-  // 转换为笛卡尔坐标
-  const w = dst.data64F[2];
-  if (Math.abs(w) > Number.EPSILON) {
-    v.p_transformed = dst.data64F[0] / w;
-    v.t_transformed = dst.data64F[1] / w;
-  }
+  // 清理临时矩阵(OpenCV.js内存管理)
+  srcPointMat.delete()
+  dtsPointMat.delete()
 
 
-  // 清理临时矩阵
-  src.delete();
-  dst.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
 // opencv =============end
 
 
-
 function markClick(item: MarkItemType) {
 function markClick(item: MarkItemType) {
-  console.log(item);
-
+  if (!isDisabled.value)
+    return system.msg('正在装载,请稍后...')
   currentMark.value = item
   currentMark.value = item
   colorReset(item.id)
   colorReset(item.id)
 }
 }
@@ -185,6 +190,7 @@ function colorReset(id: string) {
 function del(id: string) {
 function del(id: string) {
   if (!id)
   if (!id)
     return
     return
+  PTXYMap.delete(id)
   mark.value = mark.value.filter(v => v.id !== id)
   mark.value = mark.value.filter(v => v.id !== id)
   markClone.value = markClone.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 && (currentMark.value = mark.value[mark.value.length - 1])
@@ -202,6 +208,7 @@ function clear() {
     y: 0.0,
     y: 0.0,
     color: '#000',
     color: '#000',
   }
   }
+  PTXYMap.clear()
 }
 }
 
 
 /**
 /**
@@ -244,13 +251,10 @@ function dbClick(e: MouseEvent) {
   mark.value.push(markV)
   mark.value.push(markV)
   markClone.value.push(markV)
   markClone.value.push(markV)
   currentMark.value = mark.value[mark.value.length - 1]
   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)
   markPointOnImageWithMargin(imgElement as HTMLImageElement)
 }
 }
 
 
-
-const PTXYMap = new Map<string, { P: number, T: number, X: number, Y: number }>()
-
-
 function getPTXY(id: string, x: number, y: number) {
 function getPTXY(id: string, x: number, y: number) {
   if (!system.globalConfig.serverUrl) {
   if (!system.globalConfig.serverUrl) {
     system.msg('请先设置服务器地址')
     system.msg('请先设置服务器地址')
@@ -264,28 +268,30 @@ function getPTXY(id: string, x: number, y: number) {
     system.msg('请选择球机')
     system.msg('请选择球机')
     return
     return
   }
   }
-  axios.get(`https://${system.globalConfig.serverUrl}/api/BallCamera/PtzGet?CameraId=${id}`).then(res => {
+  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 })
     PTXYMap.set(currentMark.value.id, { P: res.data.data.P, T: res.data.data.T, X: x, Y: y })
     mark.value.forEach((v, i) => {
     mark.value.forEach((v, i) => {
       if (v.id === currentMark.value.id) {
       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, "")
+        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.p = p
         v.t = t
         v.t = t
         markClone.value[i].p = p
         markClone.value[i].p = p
         markClone.value[i].t = t
         markClone.value[i].t = t
       }
       }
     })
     })
+    isDisabled.value = true
+    system.msg('装载完成')
   })
   })
-
 }
 }
 
 
-const loadOld = () => {
-  if (!system.ballCameraInfo) return
-  console.log(system.ballCameraInfo);
+function loadOld() {
+  if (!system.ballCameraInfo)
+    return
   PTXYMap.clear()
   PTXYMap.clear()
   const ps: any = system.ballCameraInfo.Matrix.PointSet
   const ps: any = system.ballCameraInfo.Matrix.PointSet
-  console.log(ps);
 
 
   for (const key in ps) {
   for (const key in ps) {
     PTXYMap.set(key, {
     PTXYMap.set(key, {
@@ -304,33 +310,96 @@ const loadOld = () => {
     })
     })
   }
   }
 
 
+  if (!mark.value.length)
+    return
   markClone.value = JSON.parse(JSON.stringify(mark.value))
   markClone.value = JSON.parse(JSON.stringify(mark.value))
   colorReset(mark.value[mark.value.length - 1].id)
   colorReset(mark.value[mark.value.length - 1].id)
   currentMark.value = mark.value[mark.value.length - 1]
   currentMark.value = mark.value[mark.value.length - 1]
   markPointOnImageWithMargin(imgElement as HTMLImageElement)
   markPointOnImageWithMargin(imgElement as HTMLImageElement)
 }
 }
 
 
-const jumpToPT = (p: number, t: number) => {
+function jumpToPT(p: number, t: number) {
   if (!system.globalConfig.serverUrl) {
   if (!system.globalConfig.serverUrl) {
     system.msg('请先设置服务器地址')
     system.msg('请先设置服务器地址')
     return
     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=${currentBallCamera.value}&Action=1`, { P: pV, T: tV, Z: 0 }).then(res => {
-    console.log(res);
+  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)
   })
   })
 }
 }
 
 
+function ptByMatrix() {
+  if (!system.globalConfig.serverUrl) {
+    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
+    }
+  })
+}
 
 
+function updateMatrix(callback?: () => void) {
+  if (!system.globalConfig.serverUrl) {
+    system.msg('请先设置服务器地址')
+    return
+  }
+  if (!system.currentBallCamera) {
+    system.msg('请选择球机')
+    return
+  }
+  const pointSet: any = {}
+  PTXYMap.forEach((v, k) => {
+    pointSet[k] = {
+      P: v.P,
+      T: v.T,
+      X: v.X,
+      Y: v.Y,
+    }
+  })
+  const data = {
+    P_Start: system.positiveConfig.P_Start,
+    P_Max: system.positiveConfig.P_Max,
+    p_Positive_Direction: system.positiveConfig.P_Direction,
+    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 || [],
+    InvMatrix: system.ballCameraInfo?.Matrix.InvMatrix || [],
+    PointSet: JSON.stringify(pointSet) === '{}' ? system.ballCameraInfo?.Matrix.PointSet : pointSet,
+  }
+  data.Matrix = Array.isArray(data.Matrix) ? data.Matrix : Array.from(data.Matrix)
+  axios.post(`https://${system.globalConfig.serverUrl}/api/BallCamera/Matrix?CameraId=${system.currentBallCamera}`, data).then(() => {
+    callback && callback()
+  })
+}
 
 
-let viewer: Viewer
-
-watch(() => system.drawer, () => {
-  console.log(viewer)
-})
+function saveBtn() {
+  if (!window.outMatrix) {
+    system.msg('请先生成映射矩阵')
+    return
+  }
+  const invMatrix = calculateInverseMatrix(window.outMatrix)
+  system.ballCameraInfo!.Matrix.InvMatrix = invMatrix
+  updateMatrix(() => {
+    system.msg('保存成功')
+  })
+}
 
 
+let viewer: Viewer | null = null
 function initViewer() {
 function initViewer() {
+  if (viewer) {
+    viewer.destroy()
+    viewer = null
+  }
   const updloadImg = document.getElementById('uploadImage') as HTMLImageElement
   const updloadImg = document.getElementById('uploadImage') as HTMLImageElement
   viewer = new Viewer(updloadImg, {
   viewer = new Viewer(updloadImg, {
     inline: true,
     inline: true,
@@ -367,42 +436,68 @@ function initViewer() {
   })
   })
 }
 }
 
 
-onMounted(() => {
-  initViewer()
+watch(() => system.globalConfig.imgUrl, (file) => {
+  const url = file && URL.createObjectURL(file)
+  if (url) {
+    imgSrc.value = url
+    nextTick(() => {
+      initViewer()
+    })
+  }
 })
 })
+
+watch(() => system.positiveConfig, () => {
+  updateMatrix()
+}, { deep: true })
 </script>
 </script>
 
 
 <template>
 <template>
   <v-row>
   <v-row>
     <v-col class="relative" cols="12" md="6">
     <v-col class="relative" cols="12" md="6">
       <div class="flex-1">
       <div class="flex-1">
-        <v-select v-if="system.type?.Baseline === 'BoGuan'" v-model="system.currentDevice" :items="system.deviceList"
-          item-title="name" item-value="id" label="选择设备" @update:model-value="system.getBallCameraList" />
-        <v-select v-model="currentBallCamera" :items="system.ballCameraList" label="球机列表" item-title="name"
-          item-value="id" @update:model-value="system.getBallCameraInfo">
+        <v-select
+          v-if="system.type?.Baseline === 'BoGuan'" v-model="system.currentDevice" :items="system.deviceList"
+          item-title="name" item-value="id" label="选择设备" @update:model-value="system.getBallCameraList"
+        />
+        <v-select
+          v-model="system.currentBallCamera" :items="system.ballCameraList" label="球机列表" item-title="name"
+          item-value="id" @update:model-value="system.getBallCameraInfo"
+        >
           <template #append>
           <template #append>
-            <add-ball v-model:dialog="addDialogV">
+            <add-ball v-model:dialog="addDialogV" flag="add">
+              <template #activator="{ props: activatorProps }">
+                <v-btn
+                  v-tooltip:top="'添加球机'" size="large" rounded="0" variant="flat" icon="mdi-plus-thick"
+                  v-bind="activatorProps"
+                />
+              </template>
+            </add-ball>
+            <add-ball v-model:dialog="updDialogV" flag="edit" :camera-id="system.currentBallCamera">
               <template #activator="{ props: activatorProps }">
               <template #activator="{ props: activatorProps }">
-                <v-btn v-tooltip:top="'添加球机'" size="large" rounded="0" variant="flat" icon="mdi-plus-thick"
-                  v-bind="activatorProps" />
+                <v-btn
+                  v-tooltip:top="'修改球机'" size="large" rounded="0" variant="flat" icon="mdi-pencil"
+                  v-bind="activatorProps"
+                />
               </template>
               </template>
             </add-ball>
             </add-ball>
             <v-dialog max-width="400">
             <v-dialog max-width="400">
               <template #activator="{ props: activatorProps }">
               <template #activator="{ props: activatorProps }">
-                <v-btn v-tooltip:top="'删除球机'" size="large" rounded="0" variant="flat" icon="mdi-trash-can"
-                  v-bind="activatorProps" />
+                <v-btn
+                  v-tooltip:top="'删除球机'" size="large" rounded="0" variant="flat" icon="mdi-trash-can"
+                  v-bind="activatorProps"
+                />
               </template>
               </template>
 
 
               <template #default="{ isActive }">
               <template #default="{ isActive }">
                 <v-card title="警告" color="#f87171">
                 <v-card title="警告" color="#f87171">
                   <v-card-text>
                   <v-card-text>
-                    确定删除吗?确定删除吗?
+                    确定删除当前球机吗?
                   </v-card-text>
                   </v-card-text>
 
 
                   <v-card-actions>
                   <v-card-actions>
                     <v-spacer />
                     <v-spacer />
 
 
-                    <v-btn text="确定" border @click="isActive.value = false" />
+                    <v-btn text="确定" border @click="() => { system.delBallCamera(system.currentBallCamera);isActive.value = false }" />
                   </v-card-actions>
                   </v-card-actions>
                 </v-card>
                 </v-card>
               </template>
               </template>
@@ -412,62 +507,80 @@ onMounted(() => {
         <v-row>
         <v-row>
           <v-col>
           <v-col>
             <v-sheet>
             <v-sheet>
-              <v-number-input v-model="system.positiveConfig.P_Start" control-variant="stacked" :precision="1"
-                hide-details="auto" label="P 起始值" />
+              <v-number-input
+                v-model="system.positiveConfig.P_Start" control-variant="stacked" :precision="1"
+                hide-details="auto" label="P 起始值"
+              />
             </v-sheet>
             </v-sheet>
           </v-col>
           </v-col>
 
 
           <v-col>
           <v-col>
             <v-sheet>
             <v-sheet>
-              <v-number-input v-model="system.positiveConfig.T_Start" control-variant="stacked" :precision="1"
-                hide-details="auto" label="T 起始值" />
+              <v-number-input
+                v-model="system.positiveConfig.T_Start" control-variant="stacked" :precision="1"
+                hide-details="auto" label="T 起始值"
+              />
             </v-sheet>
             </v-sheet>
           </v-col>
           </v-col>
         </v-row>
         </v-row>
         <v-row>
         <v-row>
           <v-col>
           <v-col>
             <v-sheet>
             <v-sheet>
-              <v-number-input v-model="system.positiveConfig.P_Max" control-variant="stacked" :precision="1"
-                hide-details="auto" label="P 最大值" />
+              <v-number-input
+                v-model="system.positiveConfig.P_Max" control-variant="stacked" :precision="1"
+                hide-details="auto" label="P 最大值"
+              />
             </v-sheet>
             </v-sheet>
           </v-col>
           </v-col>
 
 
           <v-col>
           <v-col>
             <v-sheet>
             <v-sheet>
-              <v-number-input v-model="system.positiveConfig.T_Max" control-variant="stacked" :precision="1"
-                hide-details="auto" label="T 最大值" />
+              <v-number-input
+                v-model="system.positiveConfig.T_Max" control-variant="stacked" :precision="1"
+                hide-details="auto" label="T 最大值"
+              />
             </v-sheet>
             </v-sheet>
           </v-col>
           </v-col>
         </v-row>
         </v-row>
         <v-row>
         <v-row>
           <v-col>
           <v-col>
             <v-sheet>
             <v-sheet>
-              <v-select v-model="system.positiveConfig.P_Direction" :items="positiveDirectionPOptions"
-                item-title="label" item-value="value" label="P 正方向" />
+              <v-select
+                v-model="system.positiveConfig.P_Direction" :items="positiveDirectionPOptions"
+                item-title="label" item-value="value" label="P 正方向"
+              />
             </v-sheet>
             </v-sheet>
           </v-col>
           </v-col>
           <v-col>
           <v-col>
             <v-sheet>
             <v-sheet>
-              <v-select v-model="system.positiveConfig.T_Direction" :items="positiveDirectionTOptions"
-                item-title="label" item-value="value" label="T 正方向" />
+              <v-select
+                v-model="system.positiveConfig.T_Direction" :items="positiveDirectionTOptions"
+                item-title="label" item-value="value" label="T 正方向"
+              />
             </v-sheet>
             </v-sheet>
           </v-col>
           </v-col>
         </v-row>
         </v-row>
 
 
         <!-- 视频播放 -->
         <!-- 视频播放 -->
         <div class="p-1">
         <div class="p-1">
-          <v-card :loading="system.canvasVideoLoading" title="画面预览"
-            :subtitle="`当前P ${system.PT.p},当前T ${system.PT.t},当前标定点位的个数0`" text="">
+          <v-card
+            :loading="system.canvasVideoLoading" title="画面预览"
+            :subtitle="`当前P ${system.PT.p},当前T ${system.PT.t},当前标定点位的个数0`" text=""
+          >
             <div class="relative aspect-ratio-video b-1 b-#ccc b-solid bg-black">
             <div class="relative aspect-ratio-video b-1 b-#ccc b-solid bg-black">
-              <v-icon icon="mdi-plus" color="#ef4444" class="absolute top-50% left-50% transform-translate--50%" :style="{
-                textShadow: `0 -1px #fff, 1px 0px #fff, 0 1px #fff, -1px 0 #fff,
+              <v-icon
+                icon="mdi-plus" color="#ef4444" class="absolute left-50% top-50% transform-translate--50%" :style="{
+                  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`,
           -1px -1px #fff, 1px 1px #fff, 1px -1px #fff, -1px 1px #fff`,
-              }" />
+                }"
+              />
               <canvas v-if="system.resetDom" class="canvas-video h-full w-full" />
               <canvas v-if="system.resetDom" class="canvas-video h-full w-full" />
             </div>
             </div>
             <v-card-actions>
             <v-card-actions>
-              <v-btn :disabled="system.canvasVideoLoading" block variant="tonal"
-                @click="system.playBallCamera(currentBallCamera)">
+              <v-btn
+                :disabled="system.canvasVideoLoading" block variant="tonal"
+                @click="system.playBallCamera(system.currentBallCamera)"
+              >
                 播放
                 播放
               </v-btn>
               </v-btn>
             </v-card-actions>
             </v-card-actions>
@@ -477,9 +590,11 @@ onMounted(() => {
           <div class="grid grid-cols-3 grid-rows-3">
           <div class="grid grid-cols-3 grid-rows-3">
             <div v-for="tool in tools" :key="tool.type">
             <div v-for="tool in tools" :key="tool.type">
               <div v-if="!tool.type" class="invisible" />
               <div v-if="!tool.type" class="invisible" />
-              <v-btn v-else width="60" height="40" variant="tonal" color="blue-darken-2"
-                @mousedown="system.ballMove(currentBallCamera, { Speed: step, Direction: tool.value })"
-                @mouseup="system.ballStop(currentBallCamera, { Direction: tool.value })">
+              <v-btn
+                v-else width="60" height="40" variant="tonal" color="blue-darken-2"
+                @mousedown="system.ballMove(system.currentBallCamera, { Speed: step, Direction: tool.value })"
+                @mouseup="system.ballStop(system.currentBallCamera, { Direction: tool.value })"
+              >
                 {{ tool.icon }}
                 {{ tool.icon }}
               </v-btn>
               </v-btn>
             </div>
             </div>
@@ -487,14 +602,18 @@ onMounted(() => {
           <v-divider class="mx-2" vertical opacity="8" />
           <v-divider class="mx-2" vertical opacity="8" />
           <div class="flex-1">
           <div class="flex-1">
             <div class="flex justify-around">
             <div class="flex justify-around">
-              <v-btn variant="tonal" color="blue-darken-2"
-                @mousedown="system.ballMove(currentBallCamera, { Speed: step, Direction: 9 })"
-                @mouseup="system.ballStop(currentBallCamera, { Direction: 9 })">
+              <v-btn
+                variant="tonal" color="blue-darken-2"
+                @mousedown="system.ballMove(system.currentBallCamera, { Speed: step, Direction: 9 })"
+                @mouseup="system.ballStop(system.currentBallCamera, { Direction: 9 })"
+              >
                 🔍+
                 🔍+
               </v-btn>
               </v-btn>
-              <v-btn variant="tonal" color="blue-darken-2"
-                @mousedown="system.ballMove(currentBallCamera, { Speed: step, Direction: 10 })"
-                @mouseup="system.ballStop(currentBallCamera, { Direction: 10 })">
+              <v-btn
+                variant="tonal" color="blue-darken-2"
+                @mousedown="system.ballMove(system.currentBallCamera, { Speed: step, Direction: 10 })"
+                @mouseup="system.ballStop(system.currentBallCamera, { Direction: 10 })"
+              >
                 🔎-
                 🔎-
               </v-btn>
               </v-btn>
             </div>
             </div>
@@ -520,13 +639,15 @@ onMounted(() => {
           <v-skeleton-loader v-if="imgSrcLoading" height="100%" type="image" />
           <v-skeleton-loader v-if="imgSrcLoading" height="100%" type="image" />
           <img id="uploadImage" class="w-full" :src="imgSrc" alt="图片预览" @load="imgSrcLoad">
           <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-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`,
-            textShadow: `0 -1px #fff, 1px 0px #fff, 0 1px #fff, -1px 0 #fff,
+          <v-icon
+            v-for="(item, index) in mark" :key="index" :style="{
+              top: `${item.y}px`,
+              left: `${item.x}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`,
           -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="item.color" icon="mdi-plus-thick" class="absolute bottom-0" variant="tonal"
+            @click="markClick(item)"
+          />
         </div>
         </div>
         <div class="flex flex-col items-center">
         <div class="flex flex-col items-center">
           <v-chip class="ma-2" color="blue" label>
           <v-chip class="ma-2" color="blue" label>
@@ -537,19 +658,19 @@ onMounted(() => {
             <v-card-text>
             <v-card-text>
               <div class="flex flex-col items-center gap-2">
               <div class="flex flex-col items-center gap-2">
                 <div>
                 <div>
-                  <v-btn color="blue">
+                  <v-btn color="blue" @click="saveBtn">
                     保存
                     保存
                   </v-btn>
                   </v-btn>
                 </div>
                 </div>
                 <v-row justify="center">
                 <v-row justify="center">
                   <v-col>
                   <v-col>
-                    <v-btn width="100%" @click="getPTXY(currentBallCamera, currentMark.x, currentMark.y)">
+                    <v-btn width="100%" @click="getPTXY(system.currentBallCamera, currentMark.x, currentMark.y)">
                       当前PT位置装载PT
                       当前PT位置装载PT
                     </v-btn>
                     </v-btn>
                   </v-col>
                   </v-col>
 
 
                   <v-col>
                   <v-col>
-                    <v-btn width="100%">
+                    <v-btn width="100%" @click="ptByMatrix">
                       根据映射矩阵装载PT
                       根据映射矩阵装载PT
                     </v-btn>
                     </v-btn>
                   </v-col>
                   </v-col>

+ 2 - 2
src/components/HeaderTool.vue

@@ -30,9 +30,9 @@ async function toggleLocales() {
       <div i="carbon-sun dark:carbon-moon" />
       <div i="carbon-sun dark:carbon-moon" />
     </button>
     </button>
 
 
-    <a icon-btn :title="t('button.toggle_langs')" @click="toggleLocales()">
+    <!-- <a icon-btn :title="t('button.toggle_langs')" @click="toggleLocales()">
       <div i-carbon-language />
       <div i-carbon-language />
-    </a>
+    </a> -->
 
 
     <!-- <RouterLink icon-btn to="/about" :title="t('button.about')" data-test-id="about">
     <!-- <RouterLink icon-btn to="/about" :title="t('button.about')" data-test-id="about">
       <div i-carbon-dicom-overlay />
       <div i-carbon-dicom-overlay />

+ 1 - 1
src/layouts/home.vue

@@ -8,7 +8,7 @@ const theme = computed(() => isDark.value ? 'dark' : 'light')
       <v-app-bar-title>CAMERA-MODULE-TOOL</v-app-bar-title>
       <v-app-bar-title>CAMERA-MODULE-TOOL</v-app-bar-title>
       <HeaderTool />
       <HeaderTool />
     </v-app-bar>
     </v-app-bar>
-    <v-main>
+    <v-main scrollable>
       <v-container fluid>
       <v-container fluid>
         <RouterView />
         <RouterView />
       </v-container>
       </v-container>

+ 2 - 6
src/pages/index.vue

@@ -20,7 +20,7 @@ const globalConfig = reactive<{
   serverUrl: '192.168.211.3',
   serverUrl: '192.168.211.3',
 })
 })
 
 
-const text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.'
+const text = '敬请期待。。。'
 
 
 const currentItem = ref(3)
 const currentItem = ref(3)
 const tabItems = ref([
 const tabItems = ref([
@@ -29,10 +29,6 @@ const tabItems = ref([
   { id: 3, label: '球机设置' },
   { id: 3, label: '球机设置' },
 ])
 ])
 
 
-watch(() => globalConfig.imgUrl, (val) => {
-  console.log(val)
-}, { deep: true })
-
 function applyFn() {
 function applyFn() {
   system.setGlobalConfig(globalConfig)
   system.setGlobalConfig(globalConfig)
   system.saveGlobalConfig(() => { system.drawer = false })
   system.saveGlobalConfig(() => { system.drawer = false })
@@ -44,7 +40,7 @@ useHead({
 </script>
 </script>
 
 
 <template>
 <template>
-  <v-navigation-drawer v-model="system.drawer" location="right" :width="300">
+  <v-navigation-drawer v-model="system.drawer" location="right" :width="300" temporary>
     <div class="p-4 pb-0">
     <div class="p-4 pb-0">
       <span class="text-5 font-700">全局配置</span>
       <span class="text-5 font-700">全局配置</span>
     </div>
     </div>

+ 2 - 1
src/shims.d.ts

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

+ 51 - 22
src/stores/system.ts

@@ -2,7 +2,7 @@ import axios from 'axios'
 import useWorker from 'omnimatrix-video-player'
 import useWorker from 'omnimatrix-video-player'
 import { acceptHMRUpdate, defineStore } from 'pinia'
 import { acceptHMRUpdate, defineStore } from 'pinia'
 
 
-axios.defaults.timeout = 3000
+axios.defaults.timeout = 15000
 axios.interceptors.response.use((response) => {
 axios.interceptors.response.use((response) => {
   return response
   return response
 }, (error) => {
 }, (error) => {
@@ -71,6 +71,7 @@ export const useSystemStore = defineStore('system', () => {
     T_Max: 0.0,
     T_Max: 0.0,
   })
   })
   const currentDevice = ref()
   const currentDevice = ref()
+  const currentBallCamera = ref('')
   const canvasVideoLoading = ref(false)
   const canvasVideoLoading = ref(false)
   const snackbar = ref(false)
   const snackbar = ref(false)
   const snackbarText = ref('')
   const snackbarText = ref('')
@@ -96,8 +97,12 @@ export const useSystemStore = defineStore('system', () => {
     }
     }
   }
   }
 
 
-  function getBallCameraList() {
-    if (globalConfig.serverUrl) {
+  function getBallCameraList(): Promise<IOptions[]> | undefined {
+    if (!globalConfig.serverUrl) {
+      msg('请先设置服务器地址')
+      return
+    }
+    return new Promise((resolve) => {
       axios.get(`https://${globalConfig.serverUrl}/api/BallCamera/List`, { headers: { Deviceid: currentDevice.value } }).then((res) => {
       axios.get(`https://${globalConfig.serverUrl}/api/BallCamera/List`, { headers: { Deviceid: currentDevice.value } }).then((res) => {
         ballCameraList.value = []
         ballCameraList.value = []
         Object.keys(res.data.data).forEach((key) => {
         Object.keys(res.data.data).forEach((key) => {
@@ -106,8 +111,9 @@ export const useSystemStore = defineStore('system', () => {
             name: res.data.data[key],
             name: res.data.data[key],
           })
           })
         })
         })
+        resolve(ballCameraList.value)
       })
       })
-    }
+    })
   }
   }
 
 
   function getVersion() {
   function getVersion() {
@@ -189,31 +195,35 @@ export const useSystemStore = defineStore('system', () => {
     }
     }
   }
   }
 
 
-
   // startV : 以 startV 为 0
   // startV : 以 startV 为 0
   // max : 原始段最大长度
   // max : 原始段最大长度
   // value : 值
   // value : 值
   // direction (String): 正方向的位置,value +1 的值对应实际的 +/- 方向
   // direction (String): 正方向的位置,value +1 的值对应实际的 +/- 方向
   function mapping(startV: number, max: number, value: number, direction: string, method: string) {
   function mapping(startV: number, max: number, value: number, direction: string, method: string) {
-    if (direction == '+') {
-      if (method == 'inv') {
+    if (direction === '+') {
+      if (method === 'inv') {
         if (value > (max - startV)) {
         if (value > (max - startV)) {
-          return value - (max - startV);
-        } else {
-          return startV + value; // 映射
+          return value - (max - startV)
+        }
+        else {
+          return startV + value // 映射
         }
         }
-      } else {
+      }
+      else {
         if (value > startV) {
         if (value > startV) {
-          return (value - startV);
-        } else {
-          return (max - startV) + value; // 映射
+          return (value - startV)
+        }
+        else {
+          return (max - startV) + value // 映射
         }
         }
       }
       }
-    } else {
+    }
+    else {
       if (value > startV) {
       if (value > startV) {
-        return startV + max - value;
-      } else {
-        return startV - value;
+        return startV + max - value
+      }
+      else {
+        return startV - value
       }
       }
     }
     }
   }
   }
@@ -234,9 +244,9 @@ export const useSystemStore = defineStore('system', () => {
       clearTimeout(PTTimer.value)
       clearTimeout(PTTimer.value)
     }
     }
     PTTimer.value = setTimeout(() => {
     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, "")
+      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)
     }, 3000)
     axios.put(`https://${globalConfig.serverUrl}/api/BallCamera/Move?CameraId=${id}`, data)
     axios.put(`https://${globalConfig.serverUrl}/api/BallCamera/Move?CameraId=${id}`, data)
@@ -255,6 +265,23 @@ export const useSystemStore = defineStore('system', () => {
     axios.put(`https://${globalConfig.serverUrl}/api/BallCamera/Stop?CameraId=${id}`, data)
     axios.put(`https://${globalConfig.serverUrl}/api/BallCamera/Stop?CameraId=${id}`, data)
   }
   }
 
 
+  function delBallCamera(id: string) {
+    if (!globalConfig.serverUrl) {
+      msg('请先设置服务器地址')
+      return
+    }
+    if (!id) {
+      msg('请选择球机')
+      return
+    }
+    axios.delete(`https://${globalConfig.serverUrl}/api/BallCamera/Delete?CameraId=${id}`).then(() => {
+      getBallCameraList()?.then((res) => {
+        currentBallCamera.value = res[res.length - 1].id
+        getBallCameraInfo(res[res.length - 1].id)
+      })
+      msg('删除成功')
+    })
+  }
 
 
   onUnmounted(() => {
   onUnmounted(() => {
     workerObj?.close()
     workerObj?.close()
@@ -273,7 +300,9 @@ export const useSystemStore = defineStore('system', () => {
     ballCameraInfo,
     ballCameraInfo,
     getBallCameraInfo,
     getBallCameraInfo,
     getBallCameraList,
     getBallCameraList,
+    delBallCamera,
     currentDevice,
     currentDevice,
+    currentBallCamera,
     playBallCamera,
     playBallCamera,
     canvasVideoLoading,
     canvasVideoLoading,
     snackbar,
     snackbar,
@@ -282,7 +311,7 @@ export const useSystemStore = defineStore('system', () => {
     ballMove,
     ballMove,
     ballStop,
     ballStop,
     PT,
     PT,
-    mapping
+    mapping,
   }
   }
 })
 })
 
 

+ 5 - 0
src/styles/main.css

@@ -6,6 +6,7 @@ body,
   height: 100%;
   height: 100%;
   margin: 0;
   margin: 0;
   padding: 0;
   padding: 0;
+  overflow: hidden;
 }
 }
 
 
 
 
@@ -13,6 +14,10 @@ html.dark {
   background: #121212;
   background: #121212;
   color-scheme: dark;
   color-scheme: dark;
 }
 }
+html.light {
+  background: #ffffff;
+  color-scheme: light;
+}
 
 
 #nprogress {
 #nprogress {
   pointer-events: none;
   pointer-events: none;

+ 12 - 12
uno.config.ts

@@ -1,13 +1,13 @@
-import {
-  createLocalFontProcessor,
-} from '@unocss/preset-web-fonts/local'
+// import {
+//   createLocalFontProcessor,
+// } from '@unocss/preset-web-fonts/local'
 import {
 import {
   defineConfig,
   defineConfig,
   presetAttributify,
   presetAttributify,
   presetIcons,
   presetIcons,
   presetTypography,
   presetTypography,
   presetUno,
   presetUno,
-  presetWebFonts,
+  // presetWebFonts,
   transformerDirectives,
   transformerDirectives,
   transformerVariantGroup,
   transformerVariantGroup,
 } from 'unocss'
 } from 'unocss'
@@ -24,14 +24,14 @@ export default defineConfig({
       scale: 1.2,
       scale: 1.2,
     }),
     }),
     presetTypography(),
     presetTypography(),
-    presetWebFonts({
-      fonts: {
-        sans: 'DM Sans',
-        serif: 'DM Serif Display',
-        mono: 'DM Mono',
-      },
-      processors: createLocalFontProcessor(),
-    }),
+    // presetWebFonts({
+    //   fonts: {
+    //     sans: 'DM Sans',
+    //     serif: 'DM Serif Display',
+    //     mono: 'DM Mono',
+    //   },
+    //   processors: createLocalFontProcessor(),
+    // }),
   ],
   ],
   transformers: [
   transformers: [
     transformerDirectives(),
     transformerDirectives(),

+ 4 - 0
vite.config.ts

@@ -20,11 +20,15 @@ import generateSitemap from 'vite-ssg-sitemap'
 import 'vitest/config'
 import 'vitest/config'
 
 
 export default defineConfig({
 export default defineConfig({
+  base: '/cmt/',
   resolve: {
   resolve: {
     alias: {
     alias: {
       '~/': `${path.resolve(__dirname, 'src')}/`,
       '~/': `${path.resolve(__dirname, 'src')}/`,
     },
     },
   },
   },
+  build: {
+    outDir: 'cmt',
+  },
 
 
   plugins: [
   plugins: [
     basicSsl(),
     basicSsl(),