Browse Source

master: Fixed 预览列表完善

gitboyzcf 8 months ago
parent
commit
5afc64740e

+ 1 - 1
.env.development

@@ -1 +1 @@
-VITE_API_BASE_URL= 'https://192.168.10.114:15000'
+VITE_API_BASE_URL= 'https://36.133.244.231:15000'

+ 1 - 1
.eslintignore

@@ -1,5 +1,5 @@
 /*.json
 /*.js
-src/assets/**/*.js
+src/assets
 node_modules
 dist

+ 3 - 3
components.d.ts

@@ -5,11 +5,11 @@
 // Read more: https://github.com/vuejs/core/pull/3399
 import '@vue/runtime-core'
 
-export {};
+export {}
 
 declare module '@vue/runtime-core' {
   export interface GlobalComponents {
-    RouterLink: typeof import('vue-router')['RouterLink'];
-    RouterView: typeof import('vue-router')['RouterView'];
+    RouterLink: typeof import('vue-router')['RouterLink']
+    RouterView: typeof import('vue-router')['RouterView']
   }
 }

+ 1 - 1
package.json

@@ -30,7 +30,6 @@
     ]
   },
   "dependencies": {
-    "@arco-design/web-vue": "^2.44.7",
     "@ffmpeg/ffmpeg": "^0.12.10",
     "@ffmpeg/util": "^0.12.1",
     "@vueuse/core": "^9.3.0",
@@ -55,6 +54,7 @@
     "vue-router": "^4.0.14"
   },
   "devDependencies": {
+    "@arco-design/web-vue": "^2.45.0",
     "@arco-plugins/vite-vue": "^1.4.5",
     "@commitlint/cli": "^17.1.2",
     "@commitlint/config-conventional": "^17.1.0",

+ 18 - 16
pnpm-lock.yaml

@@ -10,9 +10,6 @@ overrides:
   gifsicle: 5.2.0
 
 dependencies:
-  '@arco-design/web-vue':
-    specifier: ^2.44.7
-    version: 2.45.0(vue@3.2.47)
   '@ffmpeg/ffmpeg':
     specifier: ^0.12.10
     version: 0.12.10
@@ -81,6 +78,9 @@ dependencies:
     version: 4.1.6(vue@3.2.47)
 
 devDependencies:
+  '@arco-design/web-vue':
+    specifier: ^2.45.0
+    version: 2.45.0(vue@3.2.47)
   '@arco-plugins/vite-vue':
     specifier: ^1.4.5
     version: 1.4.5
@@ -238,7 +238,7 @@ packages:
     resolution: {integrity: sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==}
     dependencies:
       color: 3.2.1
-    dev: false
+    dev: true
 
   /@arco-design/web-vue@2.45.0(vue@3.2.47):
     resolution: {integrity: sha512-+AkoOWTRz1ZeU2NTWXTEGmrJfV3/9uWhXpJ5hcPU41ynQ0GHilAlzCEt9B6hFd79QhGEHl2gRq6GJFki0Bo0Hg==}
@@ -254,7 +254,7 @@ packages:
       resize-observer-polyfill: 1.5.1
       scroll-into-view-if-needed: 2.2.31
       vue: 3.2.47
-    dev: false
+    dev: true
 
   /@arco-plugins/vite-vue@1.4.5:
     resolution: {integrity: sha512-2pJ9mpZP9mRD7NGZwRsZTS9C/US5ilEBBUqxN5Qgnd3Td50u9apJVKAABCZjG2K2eHiyZg7Fd9XhgHJXVJJmsw==}
@@ -2003,11 +2003,11 @@ packages:
 
   /b-tween@0.3.3:
     resolution: {integrity: sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA==}
-    dev: false
+    dev: true
 
   /b-validate@1.4.4:
     resolution: {integrity: sha512-E2tnSnxxKDyxP1G+TMTbVHA8XajfHHOJKeWm9YVRISSPtzTL7ZP/7tIYp01b+O83L5R/6i31+Su+vCOJBnQWFQ==}
-    dev: false
+    dev: true
 
   /bail@1.0.5:
     resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==}
@@ -2471,6 +2471,7 @@ packages:
     resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
     dependencies:
       color-name: 1.1.3
+    dev: true
 
   /color-convert@2.0.1:
     resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
@@ -2481,9 +2482,11 @@ packages:
 
   /color-name@1.1.3:
     resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
+    dev: true
 
   /color-name@1.1.4:
     resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+    dev: true
 
   /color-name@2.0.0:
     resolution: {integrity: sha512-SbtvAMWvASO5TE2QP07jHBMXKafgdZz8Vrsrn96fiL+O92/FN/PLARzUW5sKt013fjAprK2d2iCn2hk2Xb5oow==}
@@ -2512,14 +2515,14 @@ packages:
     dependencies:
       color-name: 1.1.4
       simple-swizzle: 0.2.2
-    dev: false
+    dev: true
 
   /color@3.2.1:
     resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
     dependencies:
       color-convert: 1.9.3
       color-string: 1.9.1
-    dev: false
+    dev: true
 
   /colord@2.9.3:
     resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
@@ -2560,7 +2563,7 @@ packages:
 
   /compute-scroll-into-view@1.0.20:
     resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==}
-    dev: false
+    dev: true
 
   /concat-map@0.0.1:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -2826,7 +2829,6 @@ packages:
 
   /dayjs@1.11.7:
     resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==}
-    dev: false
 
   /de-indent@1.0.2:
     resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
@@ -5332,7 +5334,7 @@ packages:
 
   /is-arrayish@0.3.2:
     resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
-    dev: false
+    dev: true
 
   /is-bigint@1.0.4:
     resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
@@ -6610,7 +6612,7 @@ packages:
 
   /number-precision@1.6.0:
     resolution: {integrity: sha512-05OLPgbgmnixJw+VvEh18yNPUo3iyp4BEWJcrLu4X9W05KmMifN7Mu5exYvQXqxxeNWhvIF+j3Rij+HmddM/hQ==}
-    dev: false
+    dev: true
 
   /object-assign@4.1.1:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
@@ -7637,7 +7639,7 @@ packages:
 
   /resize-observer-polyfill@1.5.1:
     resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
-    dev: false
+    dev: true
 
   /resolve-from@3.0.0:
     resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==}
@@ -8040,7 +8042,7 @@ packages:
     resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
     dependencies:
       compute-scroll-into-view: 1.0.20
-    dev: false
+    dev: true
 
   /seek-bzip@1.0.6:
     resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==}
@@ -8166,7 +8168,7 @@ packages:
     resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
     dependencies:
       is-arrayish: 0.3.2
-    dev: false
+    dev: true
 
   /slash@2.0.0:
     resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==}

+ 182 - 0
src/assets/js/useWheel.ts

@@ -0,0 +1,182 @@
+/**
+ * @Description: 放大缩小实现
+ * @Author zcf
+ * @Date 2023-09-13 13:18
+ * @E-mail boyzcf@qq.com
+ */
+import { watch } from 'vue';
+import { useAppStore } from '../../store';
+// const { API_VIEW_PREVIEW_POST } = useRequest()
+const useSystem = useAppStore();
+export default function useWheel(
+  containerEl,
+  partUrl,
+  className = '.ps-video'
+) {
+  const container = containerEl;
+  const scaleEle: any = document.querySelector(className + partUrl.UUID);
+  let x = 0;
+  let y = 0;
+  let scale = 1;
+  let scaleEleW = 806;
+  let scaleEleH = 453;
+  let minScale = 1;
+  let maxScale = 20;
+  let isDown = false; // 按下标识
+  let lastPointermove = { x: 0, y: 0 }; // 用于计算鼠标移动距离
+  let offsetX = 0;
+  let offsetY = 0;
+
+  // 滚轮缩放、放大逻辑
+  const wheelHandle = (e) => {
+    let ratio = 1.1;
+    // 缩小
+    if (e.deltaY > 0) {
+      ratio = 1 / 1.1;
+    }
+    // 限制缩放倍数
+    const onscale = scale * ratio;
+    if (onscale > maxScale) {
+      ratio = maxScale / scale;
+      scale = maxScale;
+    } else if (onscale < minScale) {
+      ratio = minScale / scale;
+      scale = minScale;
+    } else {
+      scale = onscale;
+    }
+    // 计算偏移量
+    const origin = {
+      x: (ratio - 1) * scaleEleW * 0.5,
+      y: (ratio - 1) * scaleEleH * 0.5,
+    };
+    x -=
+      (ratio - 1) *
+        (e.offsetX - x - (container.clientWidth - scaleEleW) * 0.5) -
+      origin.x;
+    y -=
+      (ratio - 1) *
+        (e.offsetY - y - (container.clientHeight - scaleEleH) * 0.5) -
+      origin.y;
+    offsetX = Math.min(
+      Math.max(
+        x,
+        container.clientWidth - (container.clientWidth * (scale + 1)) / 2
+      ),
+      (container.clientWidth * (scale - 1)) / 2
+    );
+    offsetY = Math.min(
+      Math.max(
+        y,
+        container.clientHeight - (container.clientHeight * (scale + 1)) / 2
+      ),
+      (container.clientHeight * (scale - 1)) / 2
+    );
+    x = offsetX;
+    y = offsetY;
+    scaleEle.style.transform = `matrix(${scale}, 0, 0, ${scale}, ${offsetX}, ${offsetY})`;
+  };
+  const mousemoveHandle = (e) => {
+    const transformStyle = scaleEle.style.transform;
+
+    // 解析矩阵的缩放值
+    const matrixValues = transformStyle.split('(')[1].split(')')[0].split(',');
+    const mscaleX = parseFloat(matrixValues[0]);
+    const mscaleY = parseFloat(matrixValues[3]);
+
+    // 计算实际宽度
+    scaleEleW = scaleEle.offsetWidth * mscaleX;
+    scaleEleH = scaleEle.offsetHeight * mscaleY;
+    if (isDown) {
+      if (scale === 1) return;
+      const moveX = e.offsetX - lastPointermove.x;
+      const moveY = e.offsetY - lastPointermove.y;
+      offsetX += moveX;
+      offsetY += moveY;
+      // 边界限制
+      if (offsetX > 0) {
+        if (offsetX >= (container.clientWidth * (scale - 1)) / 2) {
+          offsetX = x;
+        }
+      } else if (offsetX < 0) {
+        if (
+          offsetX <=
+          container.clientWidth - (container.clientWidth * (scale + 1)) / 2
+        ) {
+          offsetX = x;
+        }
+      }
+      if (offsetY > 0) {
+        if (offsetY >= (container.clientHeight * (scale - 1)) / 2) {
+          offsetY = y;
+        }
+      } else if (offsetY < 0) {
+        if (
+          offsetY <=
+          container.clientHeight - (container.clientHeight * (scale + 1)) / 2
+        ) {
+          offsetY = y;
+        }
+      }
+      x = offsetX;
+      y = offsetY;
+      scaleEle.style.transform = `matrix(${scale}, 0, 0, ${scale}, ${offsetX}, ${offsetY})`;
+    }
+  };
+
+  const params = {
+    ...partUrl,
+  };
+  // 全景更新细节初始化x y
+  watch(
+    () => useSystem.partObj,
+    (newV: any) => {
+      params.PartCenterX = newV.PartCenterX;
+      params.PartCenterY = newV.PartCenterY;
+    },
+    { deep: true }
+  );
+  const mouseupHandle = () => {
+    isDown = false;
+  };
+  const mousedownHandle = (e) => {
+    e.preventDefault();
+    isDown = true;
+    lastPointermove = { x: e.offsetX, y: e.offsetY };
+  };
+  // 全屏重置细节缩放
+  watch(
+    () => useSystem.isFull,
+    () => {
+      x = 0;
+      y = 0;
+      scale = 1;
+      scaleEleW = 806;
+      scaleEleH = 453;
+      minScale = 1;
+      maxScale = 20;
+      isDown = false; // 按下标识
+      lastPointermove = { x: 0, y: 0 }; // 用于计算鼠标移动距离
+      offsetX = 0;
+      offsetY = 0;
+      scaleEle.style.transform = `matrix(1, 0, 0, 1, 0, 0)`;
+    }
+  );
+
+  scaleEle.style.transform = `matrix(${scale}, 0, 0, ${scale}, ${offsetX}, ${offsetY})`;
+  // 拖拽查看
+  // 绑定鼠标点击
+  container.addEventListener('mousedown', mousedownHandle, false);
+  // 绑定鼠标抬起
+  container.addEventListener('mouseup', mouseupHandle, false);
+  // 绑定鼠标移动
+  container.addEventListener('mousemove', mousemoveHandle);
+
+  container.addEventListener('touchstart', mousedownHandle, false);
+  // 绑定鼠标抬起
+  container.addEventListener('touchend', mouseupHandle, false);
+  // 绑定鼠标移动
+  container.addEventListener('touchmove', mousemoveHandle);
+
+  scaleEle.addEventListener('wheel', wheelHandle, { passive: true });
+}

+ 3 - 1
src/assets/js/video-lib/GetVideoStreaming.js

@@ -1,4 +1,5 @@
 import MP4Box from 'mp4box';
+
 self.Lx = false;
 
 var ws = null;
@@ -133,8 +134,9 @@ function description(trak) {
 }
 
 self.addEventListener('message', (message) => {
-  if (message.data.type === 'lx') {
+    if (message.data.type === 'lx') {
     self.Lx = message.data.lx;
+    
     if (!message.data.lx) {
       self.postMessage({
         DataType: 'lx',

+ 61 - 11
src/assets/js/video-lib/omnimatrix-video-player.ts

@@ -1,6 +1,7 @@
 import { FFmpeg } from '@ffmpeg/ffmpeg';
 import { fetchFile, toBlobURL } from '@ffmpeg/util';
 import { showSaveFilePicker } from 'native-file-system-adapter';
+import workerUrl from '../../../../node_modules/@ffmpeg/ffmpeg/dist/esm/worker.js?worker&url';
 
 const ffmpeg = new FFmpeg();
 
@@ -14,11 +15,10 @@ const ffmpeg = new FFmpeg();
       new URL('./core/package/pkg/esm/ffmpeg-core.wasm', import.meta.url),
       'application/wasm'
     ),
-    classWorkerURL: await toBlobURL(
-      new URL('./ffmpeg/ffmpeg-worker.js', import.meta.url)
-    ),
+    classWorkerURL: new URL(workerUrl, import.meta.url).toString(),
   });
 })();
+
 function formatDateTime(date) {
   function padZero(num) {
     return num < 10 ? '0' + num : num;
@@ -40,15 +40,52 @@ function formatDateTime(date) {
  * @param {Blob} blob
  * @param {String} fileName - 文件名
  */
-async function DownloadStreamSaver(blob, fileName) {
+async function DownloadStreamSaver(blob, fileName, type) {
   const opts = {
     suggestedName: fileName,
     types: [{ 'image/png': ['png'] }],
   };
-  const handle = await showSaveFilePicker(opts);
-  const ws = await handle.createWritable();
-  ws.write(blob);
-  ws.close();
+  if (window.parent != window) {
+    window.parent.postMessage(
+      { type: 'native-file-system-adapter', imgOrVideo: type, opts, blob },
+      '*'
+    );
+  } else {
+    if (type === 'img') {
+      // 创建一个新的Image对象
+      const img = new Image();
+      img.src = URL.createObjectURL(blob);
+
+      // 等待图片加载完成
+      img.onload = () => {
+        // 创建Canvas元素
+        const canvas = document.createElement('canvas');
+        const ctx = canvas.getContext('2d');
+        // 设置Canvas大小
+        canvas.width = img.width;
+        canvas.height = img.height;
+        if (ctx) {
+          ctx.translate(img.width / 2, img.height / 2);
+          ctx.rotate(Math.PI);
+          ctx.translate(-img.width / 2, -img.height / 2);
+          ctx.drawImage(img, 0, 0);
+        }
+
+        // 将旋转后的Canvas转换回Blob
+        canvas.toBlob(async (rotatedBlob) => {
+          const handle = await showSaveFilePicker(opts);
+          const ws = await handle.createWritable();
+          ws.write(rotatedBlob);
+          ws.close();
+        });
+      };
+    } else {
+      const handle = await showSaveFilePicker(opts);
+      const ws = await handle.createWritable();
+      ws.write(blob);
+      ws.close();
+    }
+  }
 }
 
 /**
@@ -100,7 +137,11 @@ function useWorker(url, className, device, callback = () => {}) {
   worker.addEventListener('message', (msg) => {
     if (msg.data.type === 'img') {
       // saveAs(msg.data.img,`${formatDateTime(new Date())}.png`)
-      DownloadStreamSaver(msg.data.img, `${formatDateTime(new Date())}.png`);
+      DownloadStreamSaver(
+        msg.data.img,
+        `${formatDateTime(new Date())}.png`,
+        msg.data.type
+      );
     } else {
       callback();
     }
@@ -155,12 +196,21 @@ function useWorker(url, className, device, callback = () => {}) {
         console.log(msg);
       });
       await ffmpeg.writeFile('test.mp4', await fetchFile(message.data.Data));
-      ffmpeg.exec(['-i', 'test.mp4', '-c', 'copy', '-map', '0', 'out.mp4']);
+      ffmpeg.exec([
+        '-i',
+        'test.mp4',
+        '-c',
+        'copy',
+        '-metadata:s:v',
+        'rotate=90',
+        'out.mp4',
+      ]);
       const data = await ffmpeg.readFile('out.mp4');
       ffmpeg.deleteFile('test.mp4');
       DownloadStreamSaver(
         new Blob([data.buffer], { type: 'video/mp4' }),
-        `${formatDateTime(new Date())}.mp4`
+        `${formatDateTime(new Date())}.mp4`,
+        message.data.DataType
       );
       ffmpeg.deleteFile('out.mp4');
     }

+ 23 - 0
src/assets/style/global.scss

@@ -112,3 +112,26 @@ body,
 .arco-btn-primary[type='submit'] {
   background-color: rgb(var(--primary-8));
 }
+
+.arco-btn-secondary.arco-btn-disabled,
+.arco-btn-secondary[type='button'].arco-btn-disabled,
+.arco-btn-secondary[type='submit'].arco-btn-disabled {
+  background-color: transparent;
+}
+
+@keyframes fadeIn {
+  0% {
+    opacity: 0;
+  }
+  100% {
+    opacity: 1;
+  }
+}
+
+.fade-open {
+  animation: fadeIn 0.8s alternate infinite;
+}
+
+.arco-alert-warning {
+  background-color: var(--color-warning-light-4);
+}

+ 1 - 0
src/store/modules/app/index.ts

@@ -14,6 +14,7 @@ const useAppStore = defineStore('app', {
         ? window.location.origin
         : import.meta.env.VITE_API_BASE_URL,
     partObj: {},
+    isFull: false,
   }),
 
   getters: {

+ 163 - 0
src/utils/Intercom.ts

@@ -0,0 +1,163 @@
+import type { Ref } from 'vue';
+import { Message as $msg } from '@arco-design/web-vue';
+
+// 获取摄像头返回的MediaStream
+let localStream: MediaStream;
+
+let iceType: any;
+
+let ws: WebSocket;
+
+const audio = document.createElement('audio');
+audio.autoplay = true;
+audio.style.display = 'none';
+document.body.appendChild(audio);
+
+// 推流用的MediaStream
+const pc: RTCPeerConnection = new RTCPeerConnection({
+  iceServers: [
+    {
+      urls: ['stun:39.107.83.190:3478'],
+    },
+    {
+      urls: ['turn:39.107.83.190:3478'], // 你的TURN服务器地址和端口
+      username: 'test', // 如果需要,填入你的TURN用户名
+      credential: 'password', // 如果需要,填入你的TURN密码
+    },
+  ],
+});
+pc.addEventListener('icecandidate', (event) => {
+  if (event.candidate) {
+    ws.send(
+      JSON.stringify({
+        type: iceType,
+        candidate: event.candidate,
+      })
+    );
+  }
+});
+
+pc.addEventListener('track', (event) => {
+  // eslint-disable-next-line prefer-destructuring
+  audio.srcObject = event.streams[0];
+});
+/**
+ * 获取摄像头
+ */
+function getDevice() {
+  return new Promise((resolve, reject) => {
+    navigator.mediaDevices
+      .getUserMedia({ audio: true, video: false })
+      .then((mediaStream: MediaStream) => {
+        localStream = mediaStream;
+        // localVideo.srcObject = mediaStream;
+        resolve(mediaStream);
+      })
+      .catch((err) => {
+        reject(err);
+      });
+  });
+}
+
+function sendOffer() {
+  iceType = 'offer_ice';
+  // pc.addTrack(localStream.getVideoTracks()[0], localStream);
+  pc.addTrack(localStream.getAudioTracks()[0], localStream);
+  pc.createOffer({
+    offerToReceiveAudio: true,
+    offerToReceiveVideo: false,
+  }).then((offer) => {
+    pc.setLocalDescription(offer).then(() => {
+      ws.send(JSON.stringify(offer));
+    });
+  });
+}
+
+function recvOffer(offer: any) {
+  iceType = 'answer_ice';
+  // pc.addTrack(localStream.getVideoTracks()[0], localStream);
+  pc.addTrack(localStream.getAudioTracks()[0], localStream);
+  pc.setRemoteDescription(offer).then(() => {
+    pc.createAnswer().then((answer) => {
+      pc.setLocalDescription(answer).then(() => {
+        ws.send(JSON.stringify(answer));
+      });
+    });
+  });
+}
+
+function recvAnswer(answer: any) {
+  pc.setRemoteDescription(answer);
+}
+
+export const IntercomFn = (
+  disabled: Ref<boolean>,
+  host: string = window.location.host
+) => {
+  if (disabled.value) {
+    ws.close();
+    pc.close();
+    return;
+  }
+  ws = new WebSocket(`wss://${host}/webrtc`);
+  // websocket 连接成功消息
+  ws.addEventListener('open', () => {
+    // 向服务端发送连接消息
+    ws.send(
+      JSON.stringify({
+        type: 'connect',
+      })
+    );
+  });
+
+  // 收到服务端消息
+  ws.addEventListener('message', (event: MessageEvent) => {
+    const msg = JSON.parse(event.data);
+    switch (msg.type) {
+      case 'connect':
+        if (msg.code === 200) {
+          $msg?.success('连接成功,等待其他用户');
+          disabled.value = true;
+        } else {
+          $msg?.error('连接失败,已经满员');
+        }
+        break;
+
+      case 'create_offer':
+        sendOffer();
+        break;
+
+      case 'offer':
+        recvOffer(msg);
+        break;
+
+      case 'answer':
+        recvAnswer(msg);
+        break;
+
+      case 'offer_ice':
+      case 'answer_ice':
+        pc.addIceCandidate(msg.candidate);
+        break;
+
+      default:
+        break;
+    }
+  });
+
+  ws.addEventListener('close', () => {
+    $msg?.warning('websocket 连接断开');
+    disabled.value = false;
+  });
+  getDevice()
+    .then(() => {
+      // 获取摄像头成功
+    })
+    .catch((err) => {
+      $msg?.error('获取摄像头失败');
+      // eslint-disable-next-line no-console
+      console.error(err);
+    });
+};
+
+export default null;

+ 156 - 59
src/views/preview-list/children/preview-info.vue

@@ -32,31 +32,42 @@
           </div>
           <div class="video-right-content">
             <div class="video-right-content-tools">
-              <!-- 截图 -->
-              <a-tooltip :content="$t('previewList.jt')">
-                <a-button>
-                  <template #icon>
-                    <Icon icon="ri:screenshot-2-line" width="24" height="24" />
-                  </template>
-                </a-button>
-              </a-tooltip>
-              <!-- 录像 -->
-              <a-tooltip :content="$t('previewList.lx')">
-                <a-button>
+              <!-- 截图 录像 -->
+              <a-tooltip
+                v-for="tool in tools"
+                :key="tool.type"
+                :content="tool.title"
+              >
+                <a-button @click="toolsClick(tool)">
                   <template #icon>
-                    <Icon icon="bx:video-recording" width="24" height="24" />
+                    <Icon
+                      :icon="tool.icon"
+                      :color="tool.color"
+                      :class="[tool.iconClass]"
+                      width="24"
+                      height="24"
+                    />
                   </template>
                 </a-button>
               </a-tooltip>
               <!-- 对讲 -->
               <a-tooltip :content="$t('previewList.dj')">
-                <a-button>
+                <a-button @click="intercomClick">
                   <template #icon>
                     <Icon
+                      v-if="!intercomDisabled"
                       icon="material-symbols:record-voice-over-outline"
                       width="22"
                       height="22"
                     />
+                    <Icon
+                      v-else
+                      class="fade-open"
+                      icon="icon-park-outline:voice"
+                      width="22"
+                      height="22"
+                      color="red"
+                    />
                   </template>
                 </a-button>
               </a-tooltip>
@@ -159,9 +170,10 @@
               <a-table
                 :columns="alarmColumns"
                 :data="alarmData"
-                :scroll="{ y: 100 }"
+                :scroll="{ y: '100%' }"
                 :pagination="false"
                 :bordered="{ cell: true }"
+                :loading="alarmLoading"
                 size="small"
                 style="height: 100%"
               >
@@ -171,6 +183,12 @@
                     data-index="type"
                   >
                   </a-table-column>
+                  <a-table-column
+                    :title="t('previewList.bjsj')"
+                    data-index="timeN"
+                    :width="180"
+                  >
+                  </a-table-column>
                   <a-table-column
                     :title="t('previewList.status')"
                     data-index="status"
@@ -213,10 +231,14 @@
                         type="text"
                         size="mini"
                         @click="
-                          $modal.info({
-                            title: '提示',
-                            content: record.type,
-                          })
+                          () => (
+                            (record.isRead = true),
+                            $modal.info({
+                              title: '提示',
+                              draggable: true,
+                              content: record.type,
+                            })
+                          )
                         "
                         >{{ t('previewList.ck') }}</a-button
                       >
@@ -263,17 +285,22 @@
 <script setup lang="ts">
   import useWorker from '@/assets/js/video-lib/omnimatrix-video-player';
   import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
+  import { IntercomFn } from '@/utils/Intercom';
   import { useI18n } from 'vue-i18n';
   import { Icon } from '@iconify/vue';
   import { getCropFullInfo } from '@/api/system';
   import { useAppStore } from '@/store';
+  import dayjs from 'dayjs';
+  import { Message } from '@arco-design/web-vue';
+  // import useWheel from '@/assets/js/useWheel';
 
-  const app = useAppStore();
-
+  const { t } = useI18n();
   interface IBtn {
     label: string;
     value: number;
   }
+  const app = useAppStore();
+
   const workerObj: any = ref({ qj: null });
   const cropFullInfo = ref<{
     H: number;
@@ -282,7 +309,6 @@
     X: number;
     Y: number;
   }>({ H: 0, Rotate: 0, W: 0, X: 0, Y: 0 });
-  const { t } = useI18n();
   const emits = defineEmits(['handleBack']);
   const props = defineProps<{
     data: any;
@@ -290,6 +316,7 @@
   const qjLoading = ref(false);
   const radioActive = ref<number>(2);
   const screenActive = ref<number>(0);
+
   const btnList = computed<IBtn[]>(() => [
     {
       label: t('previewList.dp'),
@@ -312,6 +339,42 @@
     //   value: 16,
     // },
   ]);
+  const tools = ref([
+    {
+      icon: 'ri:screenshot-2-line',
+      title: t('previewList.jt'),
+      type: 'jt',
+      color: '#fff',
+      iconClass: '',
+    },
+    {
+      icon: 'bx:video-recording',
+      title: t('previewList.lx'),
+      type: 'lx',
+      color: '#fff',
+      iconClass: '',
+    },
+  ]);
+  const state = ref(false);
+  const toolsClick = (tool: any) => {
+    switch (tool.type) {
+      case 'lx':
+        state.value = !state.value;
+        tool.icon = state.value ? 'fad:armrecording' : 'bx:video-recording';
+        tool.color = state.value ? 'red' : '#fff';
+        tool.iconClass = state.value ? 'fade-open' : '';
+        workerObj.value.qj.WebSocketWork.postMessage({
+          type: 'lx',
+          lx: state.value,
+        });
+        break;
+      case 'jt':
+        workerObj.value.qj.worker.postMessage({ type: 'jt' });
+        break;
+      default:
+        break;
+    }
+  };
   const gridCount = computed<number>(() => {
     let result = 4;
     switch (radioActive.value) {
@@ -351,42 +414,13 @@
       width: 150,
     },
   ];
-  const alarmData = ref([
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: false,
-    },
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: false,
-    },
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: true,
-    },
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: false,
-    },
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: false,
-    },
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: false,
-    },
-    {
-      type: '行人闯入',
-      status: 1,
-      isRead: true,
-    },
+  const alarmLoading = ref(false);
+  const alarmData = ref<{ [key: string]: number | string | boolean }[]>([
+    // {
+    //   type: '行人闯入',
+    //   status: 0,
+    //   isRead: false,
+    // },
   ]);
   const handleClose = async (v: number): Promise<void> => {
     if (activeObj?.value[v]?.workerObj) {
@@ -453,6 +487,12 @@
     }
     // toggle();
   };
+
+  const intercomDisabled = ref(false);
+  const intercomClick = () => {
+    // (app.ip as string).split('//')[1]
+    IntercomFn(intercomDisabled, '39.107.83.190');
+  };
   watch(
     radioActive,
     (newV) => {
@@ -487,6 +527,9 @@
             () => {
               ao.loading = false;
               ao.isSignal = false;
+              // useWheel(document.querySelectorAll(`#video-canvas-part`), {
+              //   UUID: screenActive.value,
+              // });
             }
           );
         });
@@ -494,7 +537,49 @@
     },
     { deep: true }
   );
+
+  let eventSource: EventSource;
+  const alarmSSE = () => {
+    if (!props.data.id) return;
+    alarmLoading.value = true;
+    eventSource = new EventSource(`${app.ip}/rpc/AiSSE?Topic=${props.data.id}`);
+    eventSource.addEventListener(
+      'open',
+      () => {
+        alarmData.value = [];
+        alarmLoading.value = false;
+      },
+      false
+    );
+    eventSource.addEventListener(
+      'message',
+      (e) => {
+        const data = JSON.parse(e.data);
+        alarmData.value.unshift({
+          type: data.AlgorithmName,
+          status: 0,
+          isRead: false,
+          timeN: dayjs(+`${data.time}`.padEnd(13, '0')).format(
+            'YYYY-MM-DD HH:mm:ss'
+          ),
+          ...data,
+        });
+      },
+      false
+    );
+    eventSource.addEventListener(
+      'error',
+      () => {
+        Message.warning({
+          content: t('previewList.tip2'),
+          duration: 5 * 1000,
+        });
+      },
+      false
+    );
+  };
   onMounted(() => {
+    alarmSSE();
     getCropFullInfo({ GUID: props.data.id }).then((res) => {
       cropFullInfo.value = res.data;
     });
@@ -507,6 +592,7 @@
   onUnmounted(() => {
     workerObj.value.qj.close();
     workerObj.value.qj = null;
+    eventSource.close();
   });
 </script>
 
@@ -520,15 +606,18 @@
         .arco-space-item {
           height: 100%;
         }
+        .arco-grid-item {
+          aspect-ratio: 16 / 9;
+        }
       }
     }
 
     .split-screen-main {
-      height: calc(100% - 39px);
       display: flex;
       flex-flow: column;
       gap: 15px;
       .alarm-box {
+        flex: 1;
         min-height: 100px;
       }
     }
@@ -601,10 +690,16 @@
       }
     }
     .screen-active {
-      border: 2px solid red;
+      border: 1px solid red;
     }
     .alarm-box {
+      flex: 1;
       min-height: 100px;
+      :deep(.arco-table) {
+        .arco-table-scroll-y > .arco-scrollbar {
+          height: 100%;
+        }
+      }
     }
   }
   .video-box {
@@ -617,6 +712,8 @@
     justify-content: center;
     align-items: center;
     background-color: #000;
+    overflow: hidden;
+    box-sizing: content-box;
     cursor: pointer;
     .arco-spin {
       position: absolute;

+ 203 - 117
src/views/preview-list/index.vue

@@ -16,6 +16,12 @@
                   v-model="searchKey"
                   style="margin-bottom: 8px; max-width: 100%"
                 />
+                <a-alert
+                  v-if="originTreeData.some((v) => v.disabled)"
+                  type="warning"
+                  closable
+                  >{{ $t('previewList.tip3') }}</a-alert
+                >
                 <a-spin v-if="loading" />
                 <a-tree v-else block-node :data="treeData" @select="onSelect">
                   <template #icon>
@@ -129,33 +135,33 @@
                             class="video-box-tip"
                             ><span>{{ t('previewList.wxh') }}</span></div
                           >
-                          <div class="video-tools">
-                            <!-- 截图 -->
-                            <a-tooltip :content="$t('previewList.jt')">
-                              <a-button
-                                v-if="activeObj[i].isCloseBtn"
-                                type="text"
-                                size="mini"
-                              >
-                                <template #icon>
-                                  <Icon
-                                    icon="ri:screenshot-2-line"
-                                    width="24"
-                                    height="24"
-                                  />
-                                </template>
-                              </a-button>
-                            </a-tooltip>
-                            <!-- 录像 -->
-                            <a-tooltip :content="$t('previewList.lx')">
+                          <canvas
+                            v-if="activeObj[i].isReset"
+                            :id="'video-canvas-' + i"
+                            :style="{
+                              transform: `rotateZ(${rotateZ}deg)`,
+                            }"
+                          ></canvas>
+                          <div
+                            v-if="activeObj[i].isShowTools"
+                            class="video-tools"
+                          >
+                            <!-- 截图 录像 -->
+                            <a-tooltip
+                              v-for="tool in activeObj[i].tools"
+                              :key="tool.type"
+                              :content="tool.title"
+                            >
                               <a-button
-                                v-if="activeObj[i].isCloseBtn"
                                 type="text"
                                 size="mini"
+                                @click="toolsClick(tool, activeObj[i])"
                               >
                                 <template #icon>
                                   <Icon
-                                    icon="bx:video-recording"
+                                    :icon="tool.icon"
+                                    :color="tool.color"
+                                    :class="[tool.iconClass]"
                                     width="24"
                                     height="24"
                                   />
@@ -165,7 +171,6 @@
                             <!-- 关闭 -->
                             <a-tooltip :content="$t('previewList.close')">
                               <a-button
-                                v-if="activeObj[i].isCloseBtn"
                                 type="text"
                                 size="mini"
                                 @click="handleClose(i)"
@@ -180,15 +185,6 @@
                               </a-button>
                             </a-tooltip>
                           </div>
-
-                          <canvas
-                            v-if="activeObj[i].isReset"
-                            :id="'video-canvas-' + i"
-                            :style="{
-                              width: '100%',
-                              transform: `rotateZ(${rotateZ}deg)`,
-                            }"
-                          ></canvas>
                         </div>
                       </a-grid-item>
                     </a-grid>
@@ -221,33 +217,30 @@
                             class="video-box-tip"
                             ><span>{{ t('previewList.wxh') }}</span></div
                           >
-                          <div class="video-tools">
-                            <!-- 截图 -->
-                            <a-tooltip :content="$t('previewList.jt')">
-                              <a-button
-                                v-if="activeObj[i].isCloseBtn"
-                                type="text"
-                                size="mini"
-                              >
-                                <template #icon>
-                                  <Icon
-                                    icon="ri:screenshot-2-line"
-                                    width="24"
-                                    height="24"
-                                  />
-                                </template>
-                              </a-button>
-                            </a-tooltip>
-                            <!-- 录像 -->
-                            <a-tooltip :content="$t('previewList.lx')">
+                          <canvas
+                            v-if="activeObj[i].isReset"
+                            :id="'video-canvas-' + i"
+                          ></canvas>
+                          <div
+                            v-if="activeObj[i].isShowTools"
+                            class="video-tools"
+                          >
+                            <!-- 截图 录像 -->
+                            <a-tooltip
+                              v-for="tool in activeObj[i].tools"
+                              :key="tool.type"
+                              :content="tool.title"
+                            >
                               <a-button
-                                v-if="activeObj[i].isCloseBtn"
                                 type="text"
                                 size="mini"
+                                @click="toolsClick(tool, activeObj[i])"
                               >
                                 <template #icon>
                                   <Icon
-                                    icon="bx:video-recording"
+                                    :icon="tool.icon"
+                                    :color="tool.color"
+                                    :class="[tool.iconClass]"
                                     width="24"
                                     height="24"
                                   />
@@ -257,7 +250,6 @@
                             <!-- 关闭 -->
                             <a-tooltip :content="$t('previewList.close')">
                               <a-button
-                                v-if="activeObj[i].isCloseBtn"
                                 type="text"
                                 size="mini"
                                 @click="handleClose(i)"
@@ -272,12 +264,6 @@
                               </a-button>
                             </a-tooltip>
                           </div>
-
-                          <canvas
-                            v-if="activeObj[i].isReset"
-                            :id="'video-canvas-' + i"
-                            style="width: 100%"
-                          ></canvas>
                         </div>
                       </a-grid-item>
                     </a-grid>
@@ -289,15 +275,23 @@
                       :scroll="{ y: 100 }"
                       :pagination="false"
                       :bordered="{ cell: true }"
+                      :loading="alarmLoading"
                       size="small"
                       style="height: 100%"
                     >
                       <template #columns>
                         <a-table-column
                           :title="t('previewList.bjlx')"
+                          :ellipsis="true"
+                          :tooltip="true"
                           data-index="type"
                         >
                         </a-table-column>
+                        <a-table-column
+                          :title="t('previewList.bjsj')"
+                          data-index="timeN"
+                          :width="180"
+                        ></a-table-column>
                         <a-table-column
                           :title="t('previewList.status')"
                           data-index="status"
@@ -343,10 +337,14 @@
                               type="text"
                               size="mini"
                               @click="
-                                $modal.info({
-                                  title: '提示',
-                                  content: record.type,
-                                })
+                                () => (
+                                  (record.isRead = true),
+                                  $modal.info({
+                                    title: '提示',
+                                    draggable: true,
+                                    content: record.type,
+                                  })
+                                )
                               "
                               >{{ t('previewList.ck') }}</a-button
                             >
@@ -412,7 +410,6 @@
     computed,
     watch,
     nextTick,
-    reactive,
     defineAsyncComponent,
     onUnmounted,
   } from 'vue';
@@ -421,6 +418,7 @@
   import { getCropFullInfo } from '@/api/system';
   import { Message } from '@arco-design/web-vue';
   import { useDebounceFn } from '@vueuse/core';
+  import dayjs from 'dayjs';
 
   const resizeArr: any[] = [];
   const { loading, setLoading } = useLoading(true);
@@ -464,6 +462,42 @@
     }[]
   >([]);
 
+  // const tools = ref([
+  //   {
+  //     icon: 'ri:screenshot-2-line',
+  //     title: t('previewList.jt'),
+  //     type: 'jt',
+  //     color: '#8ac5ff',
+  //     iconClass: '',
+  //   },
+  //   {
+  //     icon: 'bx:video-recording',
+  //     title: t('previewList.lx'),
+  //     type: 'lx',
+  //     color: '#8ac5ff',
+  //     iconClass: '',
+  //   },
+  // ]);
+  const state = ref(false);
+  const toolsClick = (tool: any, ao: any) => {
+    switch (tool.type) {
+      case 'lx':
+        state.value = !state.value;
+        tool.icon = state.value ? 'fad:armrecording' : 'bx:video-recording';
+        tool.color = state.value ? 'red' : '#8ac5ff';
+        tool.iconClass = state.value ? '.fade-open' : '';
+        ao.workerObj.WebSocketWork.postMessage({
+          type: 'lx',
+          lx: state.value,
+        });
+        break;
+      case 'jt':
+        ao.workerObj.worker.postMessage({ type: 'jt' });
+        break;
+      default:
+        break;
+    }
+  };
   const gridCount = computed<number>(() => {
     let result = 4;
     switch (radioActive.value) {
@@ -533,42 +567,13 @@
       width: 150,
     },
   ];
-  const alarmData = reactive([
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: false,
-    },
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: false,
-    },
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: true,
-    },
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: false,
-    },
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: false,
-    },
-    {
-      type: '行人闯入',
-      status: 0,
-      isRead: false,
-    },
-    {
-      type: '行人闯入',
-      status: 1,
-      isRead: true,
-    },
+  const alarmLoading = ref(false);
+  const alarmData = ref<{ [key: string]: number | string | boolean }[]>([
+    // {
+    //   type: '行人闯入',
+    //   status: 0,
+    //   isRead: false,
+    // },
   ]);
 
   const searchKey = ref<string>('');
@@ -607,6 +612,7 @@
         title: item.alias,
         port: item.port,
         status: item.status,
+        disabled: !item.id,
       }));
     } catch (err) {
       // you can report use errorHandler or other
@@ -690,13 +696,10 @@
                   `#video-canvas-${screenActive.value}`
                 );
                 resizeArr.push(vc);
-                if (vc.offsetWidth > vc.parentNode.offsetWidth) {
-                  vc.style.width = '100%';
-                  vc.style.height = 'auto';
-                } else {
-                  vc.style.height = '100%';
-                  vc.style.width = 'auto';
-                }
+                vc.style.height = `${vc.parentNode.offsetHeight}px`;
+                vc.style.width = 'auto';
+                // eslint-disable-next-line no-use-before-define
+                resize();
               }, 50);
             }
           );
@@ -710,10 +713,10 @@
   };
 
   const handleMouseEnter = (v: number) => {
-    activeObj.value[v].isCloseBtn = true;
+    activeObj.value[v].isShowTools = true;
   };
   const handleMouseLeave = (v: number) => {
-    activeObj.value[v].isCloseBtn = false;
+    activeObj.value[v].isShowTools = false;
   };
 
   const handleRadioChange = (
@@ -762,6 +765,46 @@
     // toggle();
   };
 
+  let eventSource: EventSource;
+  const alarmSSE = () => {
+    alarmLoading.value = true;
+    eventSource = new EventSource(`${app.ip}/rpc/AiSSE?Topic=All`);
+    eventSource.addEventListener(
+      'open',
+      () => {
+        alarmData.value = [];
+        alarmLoading.value = false;
+      },
+      false
+    );
+    eventSource.addEventListener(
+      'message',
+      (e) => {
+        const data = JSON.parse(e.data);
+        alarmData.value.unshift({
+          type: data.AlgorithmName,
+          status: 0,
+          isRead: false,
+          timeN: dayjs(+`${data.time}`.padEnd(13, '0')).format(
+            'YYYY-MM-DD HH:mm:ss'
+          ),
+          ...data,
+        });
+      },
+      false
+    );
+    eventSource.addEventListener(
+      'error',
+      () => {
+        Message.warning({
+          content: t('previewList.tip2'),
+          duration: 5 * 1000,
+        });
+      },
+      false
+    );
+  };
+
   watch(
     radioActive,
     (newV) => {
@@ -773,8 +816,24 @@
           loading: false,
           isSignal: false,
           workerObj: null,
-          isCloseBtn: false,
-          isReset: false,
+          isShowTools: false,
+          isReset: true,
+          tools: ref([
+            {
+              icon: 'ri:screenshot-2-line',
+              title: t('previewList.jt'),
+              type: 'jt',
+              color: '#8ac5ff',
+              iconClass: '',
+            },
+            {
+              icon: 'bx:video-recording',
+              title: t('previewList.lx'),
+              type: 'lx',
+              color: '#8ac5ff',
+              iconClass: '',
+            },
+          ]),
         });
       }
     },
@@ -783,15 +842,34 @@
 
   const resize = useDebounceFn(() => {
     resizeArr.forEach((vc) => {
-      if (vc.offsetWidth > vc.parentNode.offsetWidth) {
-        vc.style.width = '100%';
-        vc.style.height = 'auto';
-      } else {
+      const splitScreenBox = document.querySelector('.split-screen-box');
+      if (
+        splitScreenBox &&
+        vc.offsetHeight + 2 >= (splitScreenBox as HTMLElement).offsetHeight + 2
+      ) {
         vc.style.height = '100%';
         vc.style.width = 'auto';
+      } else {
+        vc.style.width = '100%';
+        vc.style.height = 'auto';
       }
     });
-  }, 200);
+  }, 100);
+  // onMounted
+  watch(
+    comName,
+    (newV) => {
+      if (newV === 'index') {
+        alarmSSE();
+      } else {
+        eventSource.close();
+        // for (let i = 0; i < radioActive.value; i + 1) {
+        handleClose(screenActive.value);
+        // }
+      }
+    },
+    { immediate: true }
+  );
   window.addEventListener('resize', resize);
   onUnmounted(() => {
     window.removeEventListener('resize', resize);
@@ -822,7 +900,7 @@
         height: 100%;
         position: relative;
         .screen-active {
-          border: 2px solid red;
+          border: 1px solid red;
         }
         .arco-divider {
           position: absolute;
@@ -837,7 +915,14 @@
         flex-flow: column;
         gap: 15px;
         .alarm-box {
+          flex: 1;
           min-height: 100px;
+          max-height: 100px;
+          :deep(.arco-table) {
+            .arco-table-scroll-y > .arco-scrollbar {
+              height: 100%;
+            }
+          }
         }
       }
       .split-screen-box {
@@ -867,6 +952,7 @@
     justify-content: center;
     align-items: center;
     background-color: #000;
+    overflow: hidden;
     .arco-spin {
       position: absolute;
     }

+ 4 - 0
src/views/preview-list/locale/en-US.ts

@@ -12,6 +12,7 @@ export default {
   'previewList.default': 'Default',
   'previewList.sp': 'Vertical screen',
   'previewList.bjlx': 'Alarm type',
+  'previewList.bjsj': 'Alarm time',
   'previewList.status': 'Alarm status',
   'previewList.dqstatus': 'Read status',
   'previewList.ycl': 'Already processed',
@@ -26,4 +27,7 @@ export default {
   'previewList.lx': 'Recording',
   'previewList.fp': 'Split screen',
   'previewList.msqh': 'Mode switching',
+  'previewList.tip2': 'AI alarm connection failed, please check the device',
+  'previewList.tip3':
+    'Disabling indicates that there is an issue with the device',
 };

+ 3 - 0
src/views/preview-list/locale/zh-CN.ts

@@ -12,6 +12,7 @@ export default {
   'previewList.default': '默认模式',
   'previewList.sp': '竖屏模式',
   'previewList.bjlx': '报警类型',
+  'previewList.bjsj': '报警时间',
   'previewList.status': '处理状态',
   'previewList.dqstatus': '读取状态',
   'previewList.ycl': '已处理',
@@ -26,4 +27,6 @@ export default {
   'previewList.lx': '录像',
   'previewList.fp': '分屏',
   'previewList.msqh': '模式切换',
+  'previewList.tip2': 'AI 报警连接失败,请检查设备',
+  'previewList.tip3': '禁用说明设备有存在问题',
 };

+ 1 - 1
tsconfig.json

@@ -16,5 +16,5 @@
     "skipLibCheck": true
   },
   "include": ["src/**/*.ts", "src/**/*.vue"],
-  "exclude": ["node_modules","src/assets/**/*.js"]
+  "exclude": ["node_modules","src/assets"]
 }