소스 검색

[Feat 0000] 新增视频流新组件

hongrunxia 4 달 전
부모
커밋
5049e95665
50개의 변경된 파일3087개의 추가작업 그리고 2546개의 파일을 삭제
  1. 2 1
      package.json
  2. 93 1
      pnpm-lock.yaml
  3. BIN
      src/assets/images/home-container/configurable/tashanhome/workFace.png
  4. 1210 1214
      src/components/AIChat/MiniChat.vue
  5. 80 0
      src/components/vent/camera/HlsPlayer.vue
  6. 61 0
      src/components/vent/camera/LivePlayerWrapper.vue
  7. 35 0
      src/components/vent/camera/createPlayer.vue
  8. 185 0
      src/components/vent/draggableVideoPlayer.vue
  9. 6 0
      src/hooks/component/createPlayer.ts
  10. 92 29
      src/hooks/system/useCamera.ts
  11. 260 260
      src/layouts/default/header/index.vue
  12. 140 2
      src/views/vent/home/configurable/fireTS.vue
  13. 21 4
      src/views/vent/monitorManager/AllDeviceMonitor/index.vue
  14. 5 5
      src/views/vent/monitorManager/compressor/components/nitrogenHome_bd.vue
  15. 5 5
      src/views/vent/monitorManager/compressor/components/nitrogenHome_bet.vue
  16. 3 2
      src/views/vent/monitorManager/compressor/components/nitrogenHome_blt.vue
  17. 7 6
      src/views/vent/monitorManager/compressor/components/nitrogenHome_dltj.vue
  18. 7 6
      src/views/vent/monitorManager/compressor/components/nitrogenHome_hlg.vue
  19. 3 2
      src/views/vent/monitorManager/compressor/components/nitrogenHome_ln.vue
  20. 7 5
      src/views/vent/monitorManager/compressor/components/nitrogenHome_lt.vue
  21. 3 42
      src/views/vent/monitorManager/deviceCameraMonitor/index.vue
  22. 3 0
      src/views/vent/monitorManager/deviceMonitor/components/device/index.vue
  23. 8 5
      src/views/vent/monitorManager/fanLocalMonitor/index.vue
  24. 7 4
      src/views/vent/monitorManager/fanLocalMonitor1/index.vue
  25. 4 2
      src/views/vent/monitorManager/fireDoorMonitor/index.vue
  26. 172 159
      src/views/vent/monitorManager/footageMonitor/components/moduleCommon.vue
  27. 57 43
      src/views/vent/monitorManager/footageMonitor/index.vue
  28. 6 7
      src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHome.vue
  29. 0 8
      src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeBD.vue
  30. 6 7
      src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeBet.vue
  31. 6 6
      src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeCC.vue
  32. 373 371
      src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeCopy.vue
  33. 5 6
      src/views/vent/monitorManager/gateMonitor/gate.threejs.one.sp.ts
  34. 4 3
      src/views/vent/monitorManager/gateMonitor/gate.threejs.three.hsw.ts
  35. 3 3
      src/views/vent/monitorManager/gateMonitor/gate.threejs.three.tl.ts
  36. 3 2
      src/views/vent/monitorManager/gateMonitor/gate.threejs.tj.ssl.ts
  37. 69 277
      src/views/vent/monitorManager/gateMonitor/index.vue
  38. 12 5
      src/views/vent/monitorManager/groutMonitor/components/groutHomeHjt.vue
  39. 12 4
      src/views/vent/monitorManager/groutMonitor/components/groutHomeJj.vue
  40. 15 5
      src/views/vent/monitorManager/groutMonitor/components/groutHomehlg.vue
  41. 13 5
      src/views/vent/monitorManager/groutMonitor/components/groutHomelt.vue
  42. 23 6
      src/views/vent/monitorManager/mainFanMonitor/index.vue
  43. 10 10
      src/views/vent/monitorManager/nitrogen/components/nitrogenHome.vue
  44. 9 4
      src/views/vent/monitorManager/nitrogen/components/nitrogenHome1.vue
  45. 5 4
      src/views/vent/monitorManager/nitrogen/components/nitrogenHomeBLT.vue
  46. 1 1
      src/views/vent/monitorManager/obfurage1Monitor/index.vue
  47. 13 6
      src/views/vent/monitorManager/windowMonitor/index.vue
  48. 14 5
      src/views/vent/monitorManager/windowMonitorBet/index.vue
  49. 8 3
      src/views/vent/monitorManager/windrectMonitor/index.vue
  50. 1 1
      types/module.d.ts

+ 2 - 1
package.json

@@ -26,7 +26,7 @@
     "@ant-design/icons-vue": "^6.1.0",
     "@iconify/iconify": "^3.1.1",
     "@kjgl77/datav-vue3": "^1.4.2",
-    "@liveqing/liveplayer-v3": "^3.7.19",
+    "@liveqing/liveplayer-v3": "^3.7.38",
     "@logicflow/core": "^1.2.12",
     "@logicflow/extension": "^1.2.13",
     "@qiaoqiaoyun/drag-free": "^1.1.4",
@@ -56,6 +56,7 @@
     "enquire.js": "^2.1.6",
     "gsap": "^3.11.3",
     "highlight.js": "^11.11.1",
+    "hls.js": "^1.6.15",
     "html2canvas": "^1.4.1",
     "intro.js": "^7.2.0",
     "katex": "^0.16.25",

+ 93 - 1
pnpm-lock.yaml

@@ -21,7 +21,7 @@ importers:
         specifier: ^1.4.2
         version: 1.7.4(vue@3.5.24(typescript@4.9.5))
       '@liveqing/liveplayer-v3':
-        specifier: ^3.7.19
+        specifier: ^3.7.38
         version: 3.7.38
       '@logicflow/core':
         specifier: ^1.2.12
@@ -32,6 +32,15 @@ importers:
       '@qiaoqiaoyun/drag-free':
         specifier: ^1.1.4
         version: 1.1.4(@aesoper/normal-utils@0.1.5)(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)(@popperjs/core@2.11.8)(gradient-parser@1.1.1)(tinycolor2@1.6.0)(typescript@4.9.5)
+      '@vue-office/docx':
+        specifier: ^1.6.3
+        version: 1.6.3(vue-demi@0.14.10(vue@3.5.24(typescript@4.9.5)))(vue@3.5.24(typescript@4.9.5))
+      '@vue-office/excel':
+        specifier: ^1.7.14
+        version: 1.7.14(vue-demi@0.14.10(vue@3.5.24(typescript@4.9.5)))(vue@3.5.24(typescript@4.9.5))
+      '@vue-office/pdf':
+        specifier: ^2.0.10
+        version: 2.0.10(vue-demi@0.14.10(vue@3.5.24(typescript@4.9.5)))(vue@3.5.24(typescript@4.9.5))
       '@vue/runtime-core':
         specifier: ^3.3.4
         version: 3.5.24
@@ -98,12 +107,21 @@ importers:
       gsap:
         specifier: ^3.11.3
         version: 3.13.0
+      highlight.js:
+        specifier: ^11.11.1
+        version: 11.11.1
+      hls.js:
+        specifier: ^1.6.15
+        version: 1.6.15
       html2canvas:
         specifier: ^1.4.1
         version: 1.4.1
       intro.js:
         specifier: ^7.2.0
         version: 7.2.0
+      katex:
+        specifier: ^0.16.25
+        version: 0.16.25
       lodash:
         specifier: ^4.17.21
         version: 4.17.21
@@ -113,6 +131,9 @@ importers:
       lodash.get:
         specifier: ^4.4.2
         version: 4.4.2
+      marked:
+        specifier: ^17.0.1
+        version: 17.0.1
       md5:
         specifier: ^2.3.0
         version: 2.3.0
@@ -2278,6 +2299,36 @@ packages:
   '@volar/typescript@1.11.1':
     resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==}
 
+  '@vue-office/docx@1.6.3':
+    resolution: {integrity: sha512-Cs+3CAaRBOWOiW4XAhTwwxJ0dy8cPIf6DqfNvYcD3YACiLwO4kuawLF2IAXxyijhbuOeoFsfvoVbOc16A/4bZA==}
+    peerDependencies:
+      '@vue/composition-api': ^1.7.1
+      vue: ^2.0.0 || >=3.0.0
+      vue-demi: ^0.14.6
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+
+  '@vue-office/excel@1.7.14':
+    resolution: {integrity: sha512-pVUgt+emDQUnW7q22CfnQ+jl43mM/7IFwYzOg7lwOwPEbiVB4K4qEQf+y/bc4xGXz75w1/e3Kz3G6wAafmFBFg==}
+    peerDependencies:
+      '@vue/composition-api': ^1.7.1
+      vue: ^2.0.0 || >=3.0.0
+      vue-demi: ^0.14.6
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+
+  '@vue-office/pdf@2.0.10':
+    resolution: {integrity: sha512-yHVLrMAKpMPBkhBwofFyGEtEeJF0Zd7oGmf56Pe5aj/xObdRq3E1CIZqTqhWJNgHV8oLQqaX0vs4p5T1zq+GIA==}
+    peerDependencies:
+      '@vue/composition-api': ^1.7.1
+      vue: ^2.0.0 || >=3.0.0
+      vue-demi: ^0.14.6
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+
   '@vue/babel-helper-vue-transform-on@1.5.0':
     resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==}
 
@@ -4515,6 +4566,13 @@ packages:
   header-case@2.0.4:
     resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==}
 
+  highlight.js@11.11.1:
+    resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
+    engines: {node: '>=12.0.0'}
+
+  hls.js@1.6.15:
+    resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==}
+
   homedir-polyfill@1.0.3:
     resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
     engines: {node: '>=0.10.0'}
@@ -5299,6 +5357,10 @@ packages:
   jspdf@2.5.2:
     resolution: {integrity: sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==}
 
+  katex@0.16.25:
+    resolution: {integrity: sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==}
+    hasBin: true
+
   keyv@4.5.4:
     resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
 
@@ -5520,6 +5582,11 @@ packages:
     resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==}
     engines: {node: '>=0.10.0'}
 
+  marked@17.0.1:
+    resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==}
+    engines: {node: '>= 20'}
+    hasBin: true
+
   math-intrinsics@1.1.0:
     resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
     engines: {node: '>= 0.4'}
@@ -10186,6 +10253,21 @@ snapshots:
       '@volar/language-core': 1.11.1
       path-browserify: 1.0.1
 
+  '@vue-office/docx@1.6.3(vue-demi@0.14.10(vue@3.5.24(typescript@4.9.5)))(vue@3.5.24(typescript@4.9.5))':
+    dependencies:
+      vue: 3.5.24(typescript@4.9.5)
+      vue-demi: 0.14.10(vue@3.5.24(typescript@4.9.5))
+
+  '@vue-office/excel@1.7.14(vue-demi@0.14.10(vue@3.5.24(typescript@4.9.5)))(vue@3.5.24(typescript@4.9.5))':
+    dependencies:
+      vue: 3.5.24(typescript@4.9.5)
+      vue-demi: 0.14.10(vue@3.5.24(typescript@4.9.5))
+
+  '@vue-office/pdf@2.0.10(vue-demi@0.14.10(vue@3.5.24(typescript@4.9.5)))(vue@3.5.24(typescript@4.9.5))':
+    dependencies:
+      vue: 3.5.24(typescript@4.9.5)
+      vue-demi: 0.14.10(vue@3.5.24(typescript@4.9.5))
+
   '@vue/babel-helper-vue-transform-on@1.5.0': {}
 
   '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.28.5)':
@@ -12700,6 +12782,10 @@ snapshots:
       capital-case: 1.0.4
       tslib: 2.8.1
 
+  highlight.js@11.11.1: {}
+
+  hls.js@1.6.15: {}
+
   homedir-polyfill@1.0.3:
     dependencies:
       parse-passwd: 1.0.0
@@ -13940,6 +14026,10 @@ snapshots:
       dompurify: 2.5.8
       html2canvas: 1.4.1
 
+  katex@0.16.25:
+    dependencies:
+      commander: 8.3.0
+
   keyv@4.5.4:
     dependencies:
       json-buffer: 3.0.1
@@ -14161,6 +14251,8 @@ snapshots:
     dependencies:
       object-visit: 1.0.1
 
+  marked@17.0.1: {}
+
   math-intrinsics@1.1.0: {}
 
   mathml-tag-names@2.1.3: {}

BIN
src/assets/images/home-container/configurable/tashanhome/workFace.png


+ 1210 - 1214
src/components/AIChat/MiniChat.vue

@@ -149,7 +149,7 @@
               accept=".pdf,.docx,.xlsx,.xls"
             >
               <a-button class="upload-btn">
-                <UploadOutlined></UploadOutlined>
+                <UploadOutlined />
                 从本地上传
               </a-button>
             </a-upload>
@@ -172,1345 +172,1341 @@
         </div>
       </div>
       <!-- 预览内容 -->
-      <div class="pre-container">
-        <!-- PDF预览 -->
-        <VueOfficePdf v-if="fileType === 'pdf'" :src="fileUrl" @rendered="handleRendered" @error="handleError" />
-        <!-- Word文档预览 -->
-        <VueOfficeDocx v-else-if="fileType === 'docx'" :src="fileUrl" @rendered="handleRendered" @error="handleError" />
-        <!-- Excel预览 -->
-        <VueOfficeExcel
-          v-else-if="fileType === 'xlsx' || fileType === 'xls'"
-          :options="{ xls: true }"
-          :src="fileUrl"
-          @rendered="handleRendered"
-          @error="handleError"
-        />
-        <!-- 不支持的文件类型 -->
-        <div v-else class="unsupported">
-          <p>不支持预览该文件类型: {{ fileType }}</p>
-        </div>
-        <!-- 加载状态 -->
-        <div v-if="loading" class="loading">
-          <p>文件加载中...</p>
-        </div>
-      </div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import VueOfficePdf from '@vue-office/pdf';
-import VueOfficeDocx from '@vue-office/docx';
-import VueOfficeExcel from '@vue-office/excel';
-import { ref, onMounted, nextTick } from 'vue';
-import { SvgIcon } from '../Icon';
-import { Space, Button, Modal, Input, message } from 'ant-design-vue';
-// import AIChat from './index.vue';
-import { useUserStore } from '/@/store/modules/user';
-import {
-  EditOutlined,
-  DeleteOutlined,
-  UploadOutlined,
-  CopyOutlined,
-  RightOutlined,
-  DownOutlined,
-  FastForwardFilled,
-  RedoOutlined,
-} from '@ant-design/icons-vue';
-import { createVNode } from 'vue';
-import { marked } from 'marked';
-import katex from 'katex';
-import 'katex/dist/katex.min.css';
-import '@vue-office/excel/lib/index.css';
-import '@vue-office/docx/lib/index.css';
-const TextArea = Input.TextArea; // 直接导入TextArea组件使用时打包报错
-const inputText = ref(''); // 输入框内容
-const refreshText = ref(''); //重新生成文本
-const sessionHistory = ref([]);
-const isShowChatBroad = ref(false);
-const editingId = ref<number | null>(null);
-const editText = ref('');
-const currentSessionID = ref('');
-const taskID = ref('');
-const messageID = ref('');
-const open = ref<boolean>(false);
-const isThinking = ref(false); //深度思考是否开启
-const Thinking = ref(false);
-const isShowDoc = ref(false);
-const fileType = ref('');
-const fileUrl = ref('');
-const APIKEY = ref('Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd');
-interface ListItem {
-  id: number;
-  name?: string;
-}
-interface Message {
-  id: string; // 唯一标识(可用时间戳生成)
-  type: 'user' | 'system' | 'response';
-  content: String; // 原始 Markdown 字符串(用于拼接)
-  parsedContent: String; // 解析后的 HTML(用于渲染)
-  contentR1: String; // 原始思考过程 Markdown
-  parsedContentR1: String; // 解析后的思考过程 HTML
-  timestamp: number; // 排序依据
-  isShowThink: boolean; //深度思考展示
-}
-// 定义消息历史数组类型
-const messageHistory = ref<Message[]>([]);
-const isFold = ref(true); // 是否折叠
-const userid = useUserStore().getUserInfo.id as string;
-const filePath = ref(''); // 绑定输入框值
-const uploadedFiles = ref([]);
-const showConfirmBtn = ref(false); // 控制确认按钮显示状态
-const fileList = ref([]);
-const suggestList = ref([]); //建议列表
-const loading = ref(false);
-// 文件预览
-const handleRendered = () => {
-  loading.value = false;
-  console.log('文件渲染完成');
-};
-const handleError = (error) => {
-  loading.value = false;
-  console.error('文件预览错误:', error);
-};
-function previewFile(data) {
-  fileType.value = data.name.split('.').pop().toLowerCase();
-  fileUrl.value = data.source_url;
-}
-function deleteFile(fileId) {
-  // 确认删除
-  if (confirm('确定要删除这个文件吗?')) {
-    uploadedFiles.value = uploadedFiles.value.filter((file) => file.id !== fileId);
-  }
-}
-//启用深度思考
-const toggleThinking = () => {
-  isThinking.value = !isThinking.value;
-  if (isThinking.value) {
-    APIKEY.value = 'Bearer app-kprgsFKtySM4Wjxs0ZGzaNFN';
-  } else {
-    APIKEY.value = 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd';
-  }
-};
-// 折叠思考过程
-const isShow = (message) => {
-  message.isShowThink = !message.isShowThink;
-};
-const dialogAreaRef = ref(null);
-// 滚动到底部的方法
-const scrollToBottom = async () => {
-  // 等待 DOM 更新(如消息渲染完成)
-  await nextTick();
-  if (dialogAreaRef.value) {
-    const el = dialogAreaRef.value;
-    // 关键:scrollTop = scrollHeight(滚动内容总高度)
-    el.scrollTop = el.scrollHeight;
-  }
-};
-
-// 点击建议项时的处理函数
-const handleSuggestClick = (text) => {
-  // 将选中的建议填充到文本框
-  inputText.value = text;
-};
-function showAIChat() {
-  isShowChatBroad.value = !isShowChatBroad.value;
-  if (isShowChatBroad) {
-    isShowDoc.value = false;
+  // import VueOfficePdf from '@vue-office/pdf';
+  // import VueOfficeDocx from '@vue-office/docx';
+  // import VueOfficeExcel from '@vue-office/excel';
+  import { ref, onMounted, nextTick } from 'vue';
+  import { SvgIcon } from '../Icon';
+  import { Space, Button, Modal, Input, message } from 'ant-design-vue';
+  // import AIChat from './index.vue';
+  import { useUserStore } from '/@/store/modules/user';
+  import {
+    EditOutlined,
+    DeleteOutlined,
+    UploadOutlined,
+    CopyOutlined,
+    RightOutlined,
+    DownOutlined,
+    FastForwardFilled,
+    RedoOutlined,
+  } from '@ant-design/icons-vue';
+  import { createVNode } from 'vue';
+  import { marked } from 'marked';
+  import katex from 'katex';
+  import 'katex/dist/katex.min.css';
+  // import '@vue-office/excel/lib/index.css';
+  // import '@vue-office/docx/lib/index.css';
+  const TextArea = Input.TextArea; // 直接导入TextArea组件使用时打包报错
+  const inputText = ref(''); // 输入框内容
+  const refreshText = ref(''); //重新生成文本
+  const sessionHistory = ref([]);
+  const isShowChatBroad = ref(false);
+  const editingId = ref<number | null>(null);
+  const editText = ref('');
+  const currentSessionID = ref('');
+  const taskID = ref('');
+  const messageID = ref('');
+  const open = ref<boolean>(false);
+  const isThinking = ref(false); //深度思考是否开启
+  const Thinking = ref(false);
+  const isShowDoc = ref(false);
+  const fileType = ref('');
+  const fileUrl = ref('');
+  const APIKEY = ref('Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd');
+  interface ListItem {
+    id: number;
+    name?: string;
+  }
+  interface Message {
+    id: string; // 唯一标识(可用时间戳生成)
+    type: 'user' | 'system' | 'response';
+    content: String; // 原始 Markdown 字符串(用于拼接)
+    parsedContent: String; // 解析后的 HTML(用于渲染)
+    contentR1: String; // 原始思考过程 Markdown
+    parsedContentR1: String; // 解析后的思考过程 HTML
+    timestamp: number; // 排序依据
+    isShowThink: boolean; //深度思考展示
+  }
+  // 定义消息历史数组类型
+  const messageHistory = ref<Message[]>([]);
+  const isFold = ref(true); // 是否折叠
+  const userid = useUserStore().getUserInfo.id as string;
+  const filePath = ref(''); // 绑定输入框值
+  const uploadedFiles = ref([]);
+  const showConfirmBtn = ref(false); // 控制确认按钮显示状态
+  const fileList = ref([]);
+  const suggestList = ref([]); //建议列表
+  const loading = ref(false);
+  // 文件预览
+  const handleRendered = () => {
+    loading.value = false;
+    console.log('文件渲染完成');
+  };
+  const handleError = (error) => {
+    loading.value = false;
+    console.error('文件预览错误:', error);
+  };
+  function previewFile(data) {
+    fileType.value = data.name.split('.').pop().toLowerCase();
+    fileUrl.value = data.source_url;
   }
-}
-//复制消息
-function copyToClipboard(text) {
-  if (!text || text.trim() === '') {
-    message.warn('没有可复制的内容');
-    return;
-  }
-  // 2. 创建临时textarea 元素
-  const textarea = document.createElement('textarea');
-  textarea.value = text;
-  textarea.style.position = 'fixed';
-  textarea.style.top = '-999px';
-  textarea.style.left = '-999px';
-  textarea.style.width = '200px';
-  textarea.style.height = '200px';
-  document.body.appendChild(textarea);
-  try {
-    textarea.select();
-    textarea.setSelectionRange(0, text.length);
-    const isSuccessful = document.execCommand('copy');
-    if (isSuccessful) {
-      message.success('复制成功!');
+  function deleteFile(fileId) {
+    // 确认删除
+    if (confirm('确定要删除这个文件吗?')) {
+      uploadedFiles.value = uploadedFiles.value.filter((file) => file.id !== fileId);
+    }
+  }
+  //启用深度思考
+  const toggleThinking = () => {
+    isThinking.value = !isThinking.value;
+    if (isThinking.value) {
+      APIKEY.value = 'Bearer app-kprgsFKtySM4Wjxs0ZGzaNFN';
     } else {
-      throw new Error('复制命令执行失败');
+      APIKEY.value = 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd';
     }
-  } catch (err) {
-    console.error('复制失败:', err);
-    message.error('复制失败,请手动复制');
-  } finally {
-    document.body.removeChild(textarea);
-  }
-}
-const initMarked = () => {
-  marked.setOptions({
-    gfm: true, // 启用GitHub风格的Markdown,包含表格
-    breaks: false, // 禁用换行符转换
-  });
-};
-// LaTeX 公式渲染(内部使用)
-const renderLatexInHtml = (html) => {
-  // 匹配块级公式($$...$$)
-  const blockRegex = /\$\$(.*?)\$\$/gs;
-  // 匹配行内公式($...$)
-  const inlineRegex = /\$(.*?)\$/g;
-  // // 替换块级公式(居中显示)
-  html = html.replace(blockRegex, (match, formula) => {
-    matchCount++;
-    console.log(`块级公式匹配第 ${matchCount} 次:`, formula); // 查看匹配次数和内容
-    return katex.renderToString(formula.trim(), {
-      displayMode: true,
-      throwOnError: false,
-      strict: false,
-      trust: true,
-    });
-  });
-  // 替换行内公式(行内显示)
-  html = html.replace(inlineRegex, (match, formula) => {
-    return katex.renderToString(formula.trim(), {
-      displayMode: false,
-      throwOnError: false,
-      strict: false,
-    });
-  });
-  return html;
-};
-// Markdown + LaTeX 解析
-const parseMarkdownWithLatex = (mdStr) => {
-  if (!mdStr) return '';
-  try {
-    let html = marked(mdStr); // Markdown → HTML
-    html = renderLatexInHtml(html); // 替换公式
-    return html;
-  } catch (error) {
-    console.error('解析失败:', error);
-    return mdStr; // 降级显示原始字符串
-  }
-};
-//重新生成
-const refresh = () => {
-  handleSend(refreshText.value);
-};
-//重新编辑
-const editAsk = (data) => {
-  inputText.value = data;
-};
-//获取消息列表
-async function handleSend(data) {
-  refreshText.value = data;
-  inputText.value = '';
-  if (isThinking) {
-    messageHistory.value.push({
-      id: `user_${Date.now()}`,
-      type: 'user',
-      content: '',
-      parsedContent: data,
-      contentR1: '',
-      parsedContentR1: '',
-      timestamp: Date.now(),
-      isShowThink: true,
+  };
+  // 折叠思考过程
+  const isShow = (message) => {
+    message.isShowThink = !message.isShowThink;
+  };
+  const dialogAreaRef = ref(null);
+  // 滚动到底部的方法
+  const scrollToBottom = async () => {
+    // 等待 DOM 更新(如消息渲染完成)
+    await nextTick();
+    if (dialogAreaRef.value) {
+      const el = dialogAreaRef.value;
+      // 关键:scrollTop = scrollHeight(滚动内容总高度)
+      el.scrollTop = el.scrollHeight;
+    }
+  };
+
+  // 点击建议项时的处理函数
+  const handleSuggestClick = (text) => {
+    // 将选中的建议填充到文本框
+    inputText.value = text;
+  };
+  function showAIChat() {
+    isShowChatBroad.value = !isShowChatBroad.value;
+    if (isShowChatBroad.value) {
+      isShowDoc.value = false;
+    }
+  }
+  //复制消息
+  function copyToClipboard(text) {
+    if (!text || text.trim() === '') {
+      message.warn('没有可复制的内容');
+      return;
+    }
+    // 2. 创建临时textarea 元素
+    const textarea = document.createElement('textarea');
+    textarea.value = text;
+    textarea.style.position = 'fixed';
+    textarea.style.top = '-999px';
+    textarea.style.left = '-999px';
+    textarea.style.width = '200px';
+    textarea.style.height = '200px';
+    document.body.appendChild(textarea);
+    try {
+      textarea.select();
+      textarea.setSelectionRange(0, text.length);
+      const isSuccessful = document.execCommand('copy');
+      if (isSuccessful) {
+        message.success('复制成功!');
+      } else {
+        throw new Error('复制命令执行失败');
+      }
+    } catch (err) {
+      console.error('复制失败:', err);
+      message.error('复制失败,请手动复制');
+    } finally {
+      document.body.removeChild(textarea);
+    }
+  }
+  const initMarked = () => {
+    marked.setOptions({
+      gfm: true, // 启用GitHub风格的Markdown,包含表格
+      breaks: false, // 禁用换行符转换
     });
-  } else {
-    messageHistory.value.push({
-      id: `user_${Date.now()}`,
-      type: 'user',
-      content: '',
-      parsedContent: data,
-      contentR1: '',
-      parsedContentR1: '',
-      timestamp: Date.now(),
-      isShowThink: false,
+  };
+  // LaTeX 公式渲染(内部使用)
+  const renderLatexInHtml = (html) => {
+    // 匹配块级公式($$...$$)
+    const blockRegex = /\$\$(.*?)\$\$/gs;
+    // 匹配行内公式($...$)
+    const inlineRegex = /\$(.*?)\$/g;
+    // // 替换块级公式(居中显示)
+    html = html.replace(blockRegex, (match, formula) => {
+      matchCount++;
+      console.log(`块级公式匹配第 ${matchCount} 次:`, formula); // 查看匹配次数和内容
+      return katex.renderToString(formula.trim(), {
+        displayMode: true,
+        throwOnError: false,
+        strict: false,
+        trust: true,
+      });
     });
-  }
-  try {
-    const response = await fetch('http://39.97.59.228:8000/v1/chat-messages', {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json',
-        Authorization: APIKEY.value,
-      },
-      body: JSON.stringify({
-        conversation_id: currentSessionID.value ? currentSessionID.value : '',
-        query: data,
-        response_mode: 'streaming',
-        user: userid,
-        inputs: {},
-      }),
+    // 替换行内公式(行内显示)
+    html = html.replace(inlineRegex, (match, formula) => {
+      return katex.renderToString(formula.trim(), {
+        displayMode: false,
+        throwOnError: false,
+        strict: false,
+      });
     });
-
-    if (!response.ok) {
-      throw new Error(`HTTP error! status: ${response.status}`);
+    return html;
+  };
+  // Markdown + LaTeX 解析
+  const parseMarkdownWithLatex = (mdStr) => {
+    if (!mdStr) return '';
+    try {
+      let html = marked(mdStr); // Markdown → HTML
+      html = renderLatexInHtml(html); // 替换公式
+      return html;
+    } catch (error) {
+      console.error('解析失败:', error);
+      return mdStr; // 降级显示原始字符串
     }
-
-    const decoder = new TextDecoder('utf-8');
-    const reader = response.body.getReader();
-    let textBuffer = ''; // 使用字符串缓冲区来累积数据
-    // 在组件中定义
-    const currentProcessingMessage = ref(null);
-    if (isThinking) {
-      const newMessage = {
-        id: `response_${Date.now()}`,
-        type: 'response',
+  };
+  //重新生成
+  const refresh = () => {
+    handleSend(refreshText.value);
+  };
+  //重新编辑
+  const editAsk = (data) => {
+    inputText.value = data;
+  };
+  //获取消息列表
+  async function handleSend(data) {
+    refreshText.value = data;
+    inputText.value = '';
+    if (isThinking.value) {
+      messageHistory.value.push({
+        id: `user_${Date.now()}`,
+        type: 'user',
         content: '',
-        parsedContent: '',
+        parsedContent: data,
         contentR1: '',
         parsedContentR1: '',
         timestamp: Date.now(),
         isShowThink: true,
-      };
-      messageHistory.value.push(newMessage);
-      currentProcessingMessage.value = newMessage; // 保存引用
+      });
     } else {
-      const newMessage = {
-        id: `response_${Date.now()}`,
-        type: 'response',
+      messageHistory.value.push({
+        id: `user_${Date.now()}`,
+        type: 'user',
         content: '',
-        parsedContent: '',
+        parsedContent: data,
         contentR1: '',
         parsedContentR1: '',
         timestamp: Date.now(),
         isShowThink: false,
-      };
-      messageHistory.value.push(newMessage);
-      currentProcessingMessage.value = newMessage; // 保存引用
+      });
     }
-    while (true) {
-      const { done, value } = await reader.read();
-      if (done) {
-        if (textBuffer) {
-          processLine(textBuffer);
-        }
-        break;
+    try {
+      const response = await fetch('http://39.97.59.228:8000/v1/chat-messages', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+          Authorization: APIKEY.value,
+        },
+        body: JSON.stringify({
+          conversation_id: currentSessionID.value ? currentSessionID.value : '',
+          query: data,
+          response_mode: 'streaming',
+          user: userid,
+          inputs: {},
+        }),
+      });
+
+      if (!response.ok) {
+        throw new Error(`HTTP error! status: ${response.status}`);
       }
-      textBuffer += decoder.decode(value, { stream: true });
-      // 处理每一行数据
-      let lineIndex;
-      while ((lineIndex = textBuffer.indexOf('\n')) !== -1) {
-        const line = textBuffer.substring(0, lineIndex).trim();
-        textBuffer = textBuffer.substring(lineIndex + 1);
 
-        if (line) {
-          processLine(line);
+      const decoder = new TextDecoder('utf-8');
+      const reader = response.body.getReader();
+      let textBuffer = ''; // 使用字符串缓冲区来累积数据
+      // 在组件中定义
+      const currentProcessingMessage = ref(null);
+      if (isThinking.value) {
+        const newMessage = {
+          id: `response_${Date.now()}`,
+          type: 'response',
+          content: '',
+          parsedContent: '',
+          contentR1: '',
+          parsedContentR1: '',
+          timestamp: Date.now(),
+          isShowThink: true,
+        };
+        messageHistory.value.push(newMessage);
+        currentProcessingMessage.value = newMessage; // 保存引用
+      } else {
+        const newMessage = {
+          id: `response_${Date.now()}`,
+          type: 'response',
+          content: '',
+          parsedContent: '',
+          contentR1: '',
+          parsedContentR1: '',
+          timestamp: Date.now(),
+          isShowThink: false,
+        };
+        messageHistory.value.push(newMessage);
+        currentProcessingMessage.value = newMessage; // 保存引用
+      }
+      while (true) {
+        const { done, value } = await reader.read();
+        if (done) {
+          if (textBuffer) {
+            processLine(textBuffer);
+          }
+          break;
+        }
+        textBuffer += decoder.decode(value, { stream: true });
+        // 处理每一行数据
+        let lineIndex;
+        while ((lineIndex = textBuffer.indexOf('\n')) !== -1) {
+          const line = textBuffer.substring(0, lineIndex).trim();
+          textBuffer = textBuffer.substring(lineIndex + 1);
+
+          if (line) {
+            processLine(line);
+          }
         }
       }
-    }
-    function processLine(line) {
-      if (line.startsWith('data: ')) {
-        try {
-          const jsonStr = line.substring('data: '.length);
-          const data = JSON.parse(jsonStr);
-          switch (data.event) {
-            case 'message':
-              if (data.answer) {
-                const targetMessage = messageHistory.value.find((msg) => msg.id === currentProcessingMessage.value.id);
+      function processLine(line) {
+        if (line.startsWith('data: ')) {
+          try {
+            const jsonStr = line.substring('data: '.length);
+            const data = JSON.parse(jsonStr);
+            switch (data.event) {
+              case 'message':
+                if (data.answer) {
+                  const targetMessage = messageHistory.value.find((msg) => msg.id === currentProcessingMessage.value.id);
 
-                if (!targetMessage) break;
-                let currentChunk = data.answer;
+                  if (!targetMessage) break;
+                  let currentChunk = data.answer;
 
-                // 检查是否包含起始标签
-                const startIndex = currentChunk.indexOf('<think>');
-                if (startIndex !== -1) {
-                  // 找到起始标签:将标签前的内容作为正文
-                  if (startIndex > 0) {
-                    targetMessage.content += currentChunk.substring(0, startIndex);
-                    targetMessage.parsedContent = parseMarkdownWithLatex(targetMessage.content);
-                  }
-                  // 进入思考模式,将标签后的内容追加到contentR1
-                  Thinking.value = true;
-                  const remainingContent = currentChunk.substring(startIndex + '<think>'.length);
-                  if (remainingContent) {
-                    targetMessage.contentR1 += remainingContent;
-                    targetMessage.parsedContentR1 = parseMarkdownWithLatex(targetMessage.contentR1);
-                  }
-                } else if (Thinking.value) {
-                  // 结束标签
-                  const endIndex = currentChunk.indexOf('</think>');
-                  if (endIndex !== -1) {
-                    // 找到结束标签:标签前的内容追加到contentR1
-                    if (endIndex > 0) {
-                      targetMessage.contentR1 += currentChunk.substring(0, endIndex);
-                      targetMessage.parsedContentR1 = parseMarkdownWithLatex(targetMessage.contentR1);
+                  // 检查是否包含起始标签
+                  const startIndex = currentChunk.indexOf('<think>');
+                  if (startIndex !== -1) {
+                    // 找到起始标签:将标签前的内容作为正文
+                    if (startIndex > 0) {
+                      targetMessage.content += currentChunk.substring(0, startIndex);
+                      targetMessage.parsedContent = parseMarkdownWithLatex(targetMessage.content);
                     }
-                    // 将标签后的内容作为正文
-                    const remainingContent = currentChunk.substring(endIndex + '</think>'.length);
+                    // 进入思考模式,将标签后的内容追加到contentR1
+                    Thinking.value = true;
+                    const remainingContent = currentChunk.substring(startIndex + '<think>'.length);
                     if (remainingContent) {
-                      targetMessage.content += remainingContent;
-                      targetMessage.parsedContent = parseMarkdownWithLatex(targetMessage.content);
+                      targetMessage.contentR1 += remainingContent;
+                      targetMessage.parsedContentR1 = parseMarkdownWithLatex(targetMessage.contentR1);
+                    }
+                  } else if (Thinking.value) {
+                    // 结束标签
+                    const endIndex = currentChunk.indexOf('</think>');
+                    if (endIndex !== -1) {
+                      // 找到结束标签:标签前的内容追加到contentR1
+                      if (endIndex > 0) {
+                        targetMessage.contentR1 += currentChunk.substring(0, endIndex);
+                        targetMessage.parsedContentR1 = parseMarkdownWithLatex(targetMessage.contentR1);
+                      }
+                      // 将标签后的内容作为正文
+                      const remainingContent = currentChunk.substring(endIndex + '</think>'.length);
+                      if (remainingContent) {
+                        targetMessage.content += remainingContent;
+                        targetMessage.parsedContent = parseMarkdownWithLatex(targetMessage.content);
+                      }
+                      targetMessage.content += currentChunk;
+                      Thinking.value = false;
+                      targetMessage.isShowThink = false;
+                    } else {
+                      // 没有结束标签,继续追加到contentR1
+                      targetMessage.contentR1 += currentChunk;
+                      targetMessage.parsedContentR1 = parseMarkdownWithLatex(targetMessage.contentR1);
                     }
-                    targetMessage.content += currentChunk;
-                    Thinking.value = false;
-                    targetMessage.isShowThink = false;
                   } else {
-                    // 没有结束标签,继续追加到contentR1
-                    targetMessage.contentR1 += currentChunk;
-                    targetMessage.parsedContentR1 = parseMarkdownWithLatex(targetMessage.contentR1);
+                    targetMessage.content += currentChunk;
+                    targetMessage.parsedContent = parseMarkdownWithLatex(targetMessage.content);
                   }
-                } else {
-                  targetMessage.content += currentChunk;
-                  targetMessage.parsedContent = parseMarkdownWithLatex(targetMessage.content);
+                  scrollToBottom(); // 每次接收消息都滚动
+                }
+                if (data.task_id && !taskID.value) taskID.value = data.task_id;
+                if (data.conversation_id && !currentSessionID.value) currentSessionID.value = data.conversation_id;
+                if (data.message_id && !messageID.value) {
+                  messageID.value = data.message_id;
                 }
-                scrollToBottom(); // 每次接收消息都滚动
-              }
-              if (data.task_id && !taskID.value) taskID.value = data.task_id;
-              if (data.conversation_id && !currentSessionID.value) currentSessionID.value = data.conversation_id;
-              if (data.message_id && !messageID.value) {
-                messageID.value = data.message_id;
-              }
-              break;
+                break;
+            }
+          } catch (error) {
+            console.warn('Error parsing stream chunk:', error, 'Chunk:', line);
           }
-        } catch (error) {
-          console.warn('Error parsing stream chunk:', error, 'Chunk:', line);
         }
       }
+      getNextSuggest();
+    } catch (error) {
+      console.error('Error in handleSend:', error);
+      // 在 UI 上显示错误信息
+      messageHistory.value.push({
+        id: `system_${Date.now()}`,
+        type: 'system',
+        content: '',
+        parsedContent: '请求错误',
+        contentR1: '',
+        parsedContentR1: '',
+        timestamp: Date.now(),
+        isShowThink: false,
+      });
     }
-    getNextSuggest();
-  } catch (error) {
-    console.error('Error in handleSend:', error);
-    // 在 UI 上显示错误信息
-    messageHistory.value.push({
-      id: `system_${Date.now()}`,
-      type: 'system',
-      content: '',
-      parsedContent: '请求错误',
-      contentR1: '',
-      parsedContentR1: '',
-      timestamp: Date.now(),
-      isShowThink: false,
-    });
   }
-}
-//创建新对话
-async function addNew() {
-  messageHistory.value = [];
-  currentSessionID.value = '';
-  taskID.value = '';
-  messageID.value = '';
-}
-// 上传文件
-const showModal = (data) => {
-  open.value = data;
-};
-const close = () => {
-  isShowDoc.value = false;
-};
-// 上传文件
-const handleBeforeUpload = async (file) => {
-  const formData = new FormData();
-  formData.append('file', file);
-  formData.append('user', userid);
+  //创建新对话
+  async function addNew() {
+    messageHistory.value = [];
+    currentSessionID.value = '';
+    taskID.value = '';
+    messageID.value = '';
+  }
+  // 上传文件
+  const showModal = (data) => {
+    open.value = data;
+  };
+  const close = () => {
+    isShowDoc.value = false;
+  };
+  // 上传文件
+  const handleBeforeUpload = async (file) => {
+    const formData = new FormData();
+    formData.append('file', file);
+    formData.append('user', userid);
 
-  try {
-    const response = await fetch(`http://39.97.59.228:8000/v1/files/upload`, {
-      method: 'POST',
-      headers: {
-        Authorization: APIKEY.value,
-      },
-      body: formData,
-    });
+    try {
+      const response = await fetch(`http://39.97.59.228:8000/v1/files/upload`, {
+        method: 'POST',
+        headers: {
+          Authorization: APIKEY.value,
+        },
+        body: formData,
+      });
 
-    const result = await response.json();
-    if (response.ok) {
-      const linkText = `[${result.name}](${result.source_url})`;
-      // inputText.value += `\n${linkText}`;
-      uploadedFiles.value.push(result);
-      isShowDoc.value = true;
-      previewFile(result);
-    } else {
-      message.error(`上传失败: ${result.message || '网络错误'}`);
+      const result = await response.json();
+      if (response.ok) {
+        const linkText = `[${result.name}](${result.source_url})`;
+        // inputText.value += `\n${linkText}`;
+        uploadedFiles.value.push(result);
+        isShowDoc.value = true;
+        previewFile(result);
+      } else {
+        message.error(`上传失败: ${result.message || '网络错误'}`);
+      }
+    } catch (error) {
+      console.error('保存失败:', error);
+      message.error('上传失败,请重试');
     }
-  } catch (error) {
-    console.error('保存失败:', error);
-    message.error('上传失败,请重试');
-  }
-  return false;
-};
+    return false;
+  };
 
-const handleRemove = (file) => {
-  const index = fileList.value.findIndex((item) => item.uid === file.uid);
-  if (index !== -1) {
-    fileList.value.splice(index, 1);
-  }
-};
-//停止响应
-async function stopReq() {
-  try {
-    let response = await fetch(`http://39.97.59.228:8000/v1/chat-messages/${taskID.value}/stop`, {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json',
-        Authorization: APIKEY.value,
-      },
-      body: JSON.stringify({
-        user: userid,
-      }),
-    });
-    if (!response) {
-      throw new Error('Network response was not ok');
-    }
-  } catch (error) {
-    console.error('保存失败:', error);
-  }
-}
-//获取具体会话记录
-async function sessionsHistory(id: string, retryCount = 0) {
-  const API_KEYS = ['Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd', 'Bearer app-kprgsFKtySM4Wjxs0ZGzaNFN'];
-  // 最大重试次数
-  const maxRetries = API_KEYS.length - 1;
-  try {
-    let response = await fetch(`http://39.97.59.228:8000/v1/messages?conversation_id=${id}&user=${userid}`, {
-      method: 'GET',
-      headers: {
-        'Content-Type': 'application/json',
-        Authorization: API_KEYS[retryCount],
-      },
-    });
-    if (!response.ok) {
-      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+  const handleRemove = (file) => {
+    const index = fileList.value.findIndex((item) => item.uid === file.uid);
+    if (index !== -1) {
+      fileList.value.splice(index, 1);
     }
-    const data = await response.json();
-    if (data.data.length > 0) {
-      messageHistory.value = [];
-      data.data.forEach((item: any) => {
-        currentSessionID.value = item.conversation_id;
-        messageHistory.value.push({
-          id: `user_${Date.now()}`,
-          type: 'user',
-          content: '',
-          parsedContent: item.query,
-          contentR1: '',
-          parsedContentR1: '',
-          timestamp: Date.now(),
-          isShowThink: false,
-        });
-        const answer = item.answer;
-        // 查找<think>
-        const startIndex = answer.indexOf('<think>');
-        const endIndex = answer.indexOf('</think>');
-        let content = '';
-        let contentR1 = '';
-        // 根据标签判断
-        if (startIndex !== -1 && endIndex !== -1) {
-          content = answer.substring(0, startIndex) + answer.substring(endIndex + '</think>'.length);
-          contentR1 = answer.substring(startIndex + '<think>'.length, endIndex);
-        } else if (startIndex !== -1) {
-          // 只有起始标签
-          content = answer.substring(0, startIndex);
-          contentR1 = answer.substring(startIndex + '<think>'.length);
-        } else if (endIndex !== -1) {
-          // 只有结束标签
-          content = answer.substring(endIndex + '</think>'.length);
-          contentR1 = answer.substring(0, endIndex);
-        } else {
-          // 没有标签
-          content = answer;
-        }
-        // 添加到消息历史
-        messageHistory.value.push({
-          id: `system_${Date.now()}`,
-          type: 'system',
-          content: '',
-          parsedContent: parseMarkdownWithLatex(content.trim()),
-          contentR1: '',
-          parsedContentR1: parseMarkdownWithLatex(contentR1.trim()),
-          timestamp: Date.now(),
-          isShowThink: contentR1.length > 0, // 如果有思考内容则显示
-        });
+  };
+  //停止响应
+  async function stopReq() {
+    try {
+      let response = await fetch(`http://39.97.59.228:8000/v1/chat-messages/${taskID.value}/stop`, {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+          Authorization: APIKEY.value,
+        },
+        body: JSON.stringify({
+          user: userid,
+        }),
       });
-    }
-  } catch (error) {
-    // 判断是否达到最大重试次数
-    if (retryCount < maxRetries) {
-      console.warn(`请求失败,正在尝试第 ${retryCount + 2} 个 API Key...`);
-      return sessionsHistory(id, retryCount + 1);
-    } else {
-      console.error('所有 API Key 均尝试失败:', error);
-      throw error;
+      if (!response) {
+        throw new Error('Network response was not ok');
+      }
+    } catch (error) {
+      console.error('保存失败:', error);
     }
   }
-  editingId.value = null;
-}
-//获取下一轮建议问题列表
-//停止响应
-async function getNextSuggest() {
-  console.log(messageID.value, '123');
-  try {
-    let response = await fetch(`http://39.97.59.228:8000/v1/messages/${messageID.value}/suggested?user=${userid}`, {
-      method: 'GET',
-      headers: {
-        'Content-Type': 'application/json',
-        Authorization: APIKEY.value,
-      },
-    });
-    const data = await response.json();
-    suggestList.value = data.data;
-    if (!response) {
-      throw new Error('Network response was not ok');
-    }
-  } catch (error) {
-    console.error('保存失败:', error);
-  }
-}
-//编辑标题
-const startEditing = (item: ListItem) => {
-  editingId.value = item.id;
-  editText.value = item.name || '';
-};
-// 保存修改
-const handleSave = async (item: ListItem) => {
-  try {
-    let response = await fetch(`http://39.97.59.228:8000/v1/conversations/${item.id}/name`, {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json',
-        Authorization: APIKEY.value,
-      },
-      body: JSON.stringify({
-        name: editText.value,
-        user: userid,
-      }),
-    });
-    if (!response.ok) {
-      throw new Error('Network response was not ok');
-    }
-    item.name = editText.value;
-  } catch (error) {
-    console.error('保存失败:', error);
-  }
-  editingId.value = null;
-};
-// 删除会话
-const startDelete = async (item: ListItem) => {
-  Modal.confirm({
-    title: '确认删除',
-    content: `确定要删除会话 "${item.name || '新会话'}" 吗?此操作不可撤销。`,
-    okText: '确认',
-    cancelText: '取消',
-    onOk: async () => {
-      // 原有删除逻辑不变
-      try {
-        let response = await fetch(`http://39.97.59.228:8000/v1/conversations/${item.id}`, {
-          method: 'DELETE',
-          headers: {
-            'Content-Type': 'application/json',
-            Authorization: APIKEY.value,
-          },
-          body: JSON.stringify({
-            user: userid,
-          }),
-        });
-        if (!response.ok) {
-          throw new Error('Network response was not ok');
-        }
-        getHistoryList();
-      } catch (error) {
-        console.error('删除失败:', error);
-        Modal.error({
-          title: '删除失败',
-          content: '删除会话时出现错误,请稍后重试。',
+  //获取具体会话记录
+  async function sessionsHistory(id: string, retryCount = 0) {
+    const API_KEYS = ['Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd', 'Bearer app-kprgsFKtySM4Wjxs0ZGzaNFN'];
+    // 最大重试次数
+    const maxRetries = API_KEYS.length - 1;
+    try {
+      let response = await fetch(`http://39.97.59.228:8000/v1/messages?conversation_id=${id}&user=${userid}`, {
+        method: 'GET',
+        headers: {
+          'Content-Type': 'application/json',
+          Authorization: API_KEYS[retryCount],
+        },
+      });
+      if (!response.ok) {
+        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+      }
+      const data = await response.json();
+      if (data.data.length > 0) {
+        messageHistory.value = [];
+        data.data.forEach((item: any) => {
+          currentSessionID.value = item.conversation_id;
+          messageHistory.value.push({
+            id: `user_${Date.now()}`,
+            type: 'user',
+            content: '',
+            parsedContent: item.query,
+            contentR1: '',
+            parsedContentR1: '',
+            timestamp: Date.now(),
+            isShowThink: false,
+          });
+          const answer = item.answer;
+          // 查找<think>
+          const startIndex = answer.indexOf('<think>');
+          const endIndex = answer.indexOf('</think>');
+          let content = '';
+          let contentR1 = '';
+          // 根据标签判断
+          if (startIndex !== -1 && endIndex !== -1) {
+            content = answer.substring(0, startIndex) + answer.substring(endIndex + '</think>'.length);
+            contentR1 = answer.substring(startIndex + '<think>'.length, endIndex);
+          } else if (startIndex !== -1) {
+            // 只有起始标签
+            content = answer.substring(0, startIndex);
+            contentR1 = answer.substring(startIndex + '<think>'.length);
+          } else if (endIndex !== -1) {
+            // 只有结束标签
+            content = answer.substring(endIndex + '</think>'.length);
+            contentR1 = answer.substring(0, endIndex);
+          } else {
+            // 没有标签
+            content = answer;
+          }
+          // 添加到消息历史
+          messageHistory.value.push({
+            id: `system_${Date.now()}`,
+            type: 'system',
+            content: '',
+            parsedContent: parseMarkdownWithLatex(content.trim()),
+            contentR1: '',
+            parsedContentR1: parseMarkdownWithLatex(contentR1.trim()),
+            timestamp: Date.now(),
+            isShowThink: contentR1.length > 0, // 如果有思考内容则显示
+          });
         });
       }
-    },
-  });
-};
-const fold = () => {
-  isFold.value = !isFold.value;
-  if (!isFold.value) {
-    getHistoryList();
+    } catch (error) {
+      // 判断是否达到最大重试次数
+      if (retryCount < maxRetries) {
+        console.warn(`请求失败,正在尝试第 ${retryCount + 2} 个 API Key...`);
+        return sessionsHistory(id, retryCount + 1);
+      } else {
+        console.error('所有 API Key 均尝试失败:', error);
+        throw error;
+      }
+    }
+    editingId.value = null;
   }
-};
-// 获取历史会话列表
-async function getHistoryList() {
-  sessionHistory.value = [];
-  try {
-    // 并行请求两个接口,提升效率
-    const [response1, response2] = await Promise.all([
-      // 第一个请求
-      fetch(`http://39.97.59.228:8000/v1/conversations?user=${userid}`, {
-        method: 'get',
+  //获取下一轮建议问题列表
+  //停止响应
+  async function getNextSuggest() {
+    console.log(messageID.value, '123');
+    try {
+      let response = await fetch(`http://39.97.59.228:8000/v1/messages/${messageID.value}/suggested?user=${userid}`, {
+        method: 'GET',
         headers: {
           'Content-Type': 'application/json',
-          Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
+          Authorization: APIKEY.value,
         },
-      }),
-      // 第二个请求
-      fetch(`http://39.97.59.228:8000/v1/conversations?user=${userid}`, {
-        method: 'get',
+      });
+      const data = await response.json();
+      suggestList.value = data.data;
+      if (!response) {
+        throw new Error('Network response was not ok');
+      }
+    } catch (error) {
+      console.error('保存失败:', error);
+    }
+  }
+  //编辑标题
+  const startEditing = (item: ListItem) => {
+    editingId.value = item.id;
+    editText.value = item.name || '';
+  };
+  // 保存修改
+  const handleSave = async (item: ListItem) => {
+    try {
+      let response = await fetch(`http://39.97.59.228:8000/v1/conversations/${item.id}/name`, {
+        method: 'POST',
         headers: {
           'Content-Type': 'application/json',
-          Authorization: 'Bearer app-kprgsFKtySM4Wjxs0ZGzaNFN',
+          Authorization: APIKEY.value,
         },
-      }),
-    ]);
-
-    // 检查响应是否成功
-    if (!response1.ok || !response2.ok) {
-      throw new Error('接口请求失败');
+        body: JSON.stringify({
+          name: editText.value,
+          user: userid,
+        }),
+      });
+      if (!response.ok) {
+        throw new Error('Network response was not ok');
+      }
+      item.name = editText.value;
+    } catch (error) {
+      console.error('保存失败:', error);
     }
-
-    // 解析两个响应的JSON数据
-    const [data1, data2] = await Promise.all([response1.json(), response2.json()]);
-
-    // 合并两个数组(核心修改:用扩展运算符合并)
-    // 确保 data1.data 和 data2.data 都是数组(防止接口返回异常)
-    const list1 = Array.isArray(data1.data) ? data1.data : [];
-    const list2 = Array.isArray(data2.data) ? data2.data : [];
-
-    // 合并数组并赋值给 sessionHistory
-    sessionHistory.value = [...list1, ...list2];
-    sessionHistory.value.forEach((item) => {
-      currentSessionID.value = item.id;
+    editingId.value = null;
+  };
+  // 删除会话
+  const startDelete = async (item: ListItem) => {
+    Modal.confirm({
+      title: '确认删除',
+      content: `确定要删除会话 "${item.name || '新会话'}" 吗?此操作不可撤销。`,
+      okText: '确认',
+      cancelText: '取消',
+      onOk: async () => {
+        // 原有删除逻辑不变
+        try {
+          let response = await fetch(`http://39.97.59.228:8000/v1/conversations/${item.id}`, {
+            method: 'DELETE',
+            headers: {
+              'Content-Type': 'application/json',
+              Authorization: APIKEY.value,
+            },
+            body: JSON.stringify({
+              user: userid,
+            }),
+          });
+          if (!response.ok) {
+            throw new Error('Network response was not ok');
+          }
+          getHistoryList();
+        } catch (error) {
+          console.error('删除失败:', error);
+          Modal.error({
+            title: '删除失败',
+            content: '删除会话时出现错误,请稍后重试。',
+          });
+        }
+      },
     });
-    console.log(sessionHistory.value, '历史数据');
-  } catch (error) {
-    console.error('获取历史数据失败:', error);
-    // 错误处理(比如提示用户)
-  }
-}
-//格式化消息
-const formatMessage = (text) => {
-  if (!text) return '';
-  let formatted = text
-    // 处理换行
-    .replace(/\n\n/g, '<br>')
-    .replace(/\n###/g, '<br>')
-    .replace(/###/g, '')
-    .replace(/---/g, '')
-    .replace(/^- /gm, '<br> - ')
-    // 处理粗体
-    .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
-    // 处理斜体
-    .replace(/\*(.*?)\*/g, '<em>$1</em>')
-    // 处理行内代码
-    .replace(/`([^`]+)`/g, '<code>$1</code>');
-  return formatted;
-};
-// 初始化按钮定位
-onMounted(() => {
-  getHistoryList();
-  // 初始化(仅执行一次)
-  initMarked();
-});
-</script>
-
-<style lang="less" scoped>
-.btn-header {
-  width: 40px;
-  height: 40px;
-  margin-right: 5px;
-  margin-bottom: 5px;
-}
-.container {
-  display: flex;
-  flex-direction: row;
-}
-.mini-chat {
-  display: flex;
-  min-width: none;
-  width: 950px;
-  height: 85%;
-  border-radius: 4px;
-  position: fixed;
-  top: 50px;
-  right: 20px;
-  padding: 10px;
-  background-color: #0a1a2f;
-  background-image: linear-gradient(to bottom, #0b69b6 1%, #0a1a2f 100%), linear-gradient(to left, #0b69b6, #0a1a2f),
-    linear-gradient(to top, #0b69b6, #0a1a2f), linear-gradient(to right, #0b69b6, #0a1a2f);
-  background-size: 100% 5px, 5px 100%, 100% 5px, 5px 100%;
-  background-position: top left, top right, bottom right, bottom left;
-  background-repeat: no-repeat;
-  z-index: 9999999;
-  color: #fff;
-}
-.doc {
-  display: flex;
-  flex-direction: column;
-  min-width: 600px;
-  height: 85%;
-  border-radius: 4px;
-  position: fixed;
-  top: 50px;
-  right: 51%;
-  padding: 10px;
-  background-color: #0a1a2f;
-  background-image: linear-gradient(to bottom, #0b69b6 1%, #0a1a2f 100%), linear-gradient(to left, #0b69b6, #0a1a2f),
-    linear-gradient(to top, #0b69b6, #0a1a2f), linear-gradient(to right, #0b69b6, #0a1a2f);
-  background-size: 100% 5px, 5px 100%, 100% 5px, 5px 100%;
-  background-position: top left, top right, bottom right, bottom left;
-  background-repeat: no-repeat;
-  z-index: 9999999;
-  color: #fff;
-}
+  };
+  const fold = () => {
+    isFold.value = !isFold.value;
+    if (!isFold.value) {
+      getHistoryList();
+    }
+  };
+  // 获取历史会话列表
+  async function getHistoryList() {
+    sessionHistory.value = [];
+    try {
+      // 并行请求两个接口,提升效率
+      const [response1, response2] = await Promise.all([
+        // 第一个请求
+        fetch(`http://39.97.59.228:8000/v1/conversations?user=${userid}`, {
+          method: 'get',
+          headers: {
+            'Content-Type': 'application/json',
+            Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
+          },
+        }),
+        // 第二个请求
+        fetch(`http://39.97.59.228:8000/v1/conversations?user=${userid}`, {
+          method: 'get',
+          headers: {
+            'Content-Type': 'application/json',
+            Authorization: 'Bearer app-kprgsFKtySM4Wjxs0ZGzaNFN',
+          },
+        }),
+      ]);
 
-.close {
-  display: flex;
-  justify-content: flex-end;
-  align-items: center;
-  width: 100%;
-  padding: 1px;
-  border-radius: 8px;
-}
+      // 检查响应是否成功
+      if (!response1.ok || !response2.ok) {
+        throw new Error('接口请求失败');
+      }
 
-.closeBtn {
-  background-color: #007bff;
-  color: white;
-  border: none;
-  border-radius: 4px;
-  cursor: pointer;
-  transition: all 0.3s ease;
-}
+      // 解析两个响应的JSON数据
+      const [data1, data2] = await Promise.all([response1.json(), response2.json()]);
 
-.closeBtn:hover {
-  background-color: #0056b3;
-}
-.left-side {
-  background: #0c2842;
-  transition: width 0.5s ease; /* 平滑过渡动画 */
-  width: 140px; /* 展开时宽度 */
-  position: relative; /* 用于按钮定位 */
-}
-.left-side.collapsed {
-  width: 40px; /* 折叠时宽度 */
-}
+      // 合并两个数组(核心修改:用扩展运算符合并)
+      // 确保 data1.data 和 data2.data 都是数组(防止接口返回异常)
+      const list1 = Array.isArray(data1.data) ? data1.data : [];
+      const list2 = Array.isArray(data2.data) ? data2.data : [];
 
-.addBtn {
-  height: 30px;
-  position: absolute;
-  background-size: 100% 100%;
-  background-position: center;
-  padding: 2px;
-  right: 10px;
-  bottom: 40px;
-  left: 10px;
-  align-items: center;
-  border-radius: 3px;
-  cursor: pointer;
-}
-.custom-list {
-  height: 650px;
-  overflow-y: auto;
-}
-.text-container {
-  display: flex;
-  justify-content: space-between;
-  width: 100%;
-  overflow: hidden;
-}
-.btn-container {
-  display: flex;
-}
-.jeecg-layout-header-action span[role='img'] {
-  padding: 0;
-}
-.text-ellipsis {
-  flex: 1;
-}
-.edit-text {
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-  width: 90px;
-  color: #fff;
-  font-size: 12px;
-  cursor: pointer;
-}
-.edit-icon {
-  flex-shrink: 0;
-  margin-left: auto;
-  line-height: 23px;
-}
-.delete-icon {
-  flex-shrink: 0;
-  margin-left: auto;
-  line-height: 23px;
-}
-.edit-icon:hover {
-  color: #1890ff !important;
-  cursor: pointer;
-}
-.delete-icon:hover {
-  color: #1890ff !important;
-  cursor: pointer;
-}
-.edit-input {
-  font-size: 10px;
-}
-.btn-text-bg {
-  width: 14px;
-  height: 14px;
-  position: absolute;
-  background-size: 100% 100%;
-  right: 10px;
-  top: 10px;
-  left: 10px;
-  bottom: 10px;
-}
-.btn-text {
-  margin-left: 3px;
-  font-size: 12px;
-  color: #fff;
-  white-space: nowrap;
-  margin-left: 30px;
-  line-height: 29px;
-}
-.historyBtn {
-  width: 20px;
-  height: 20px;
-  position: absolute;
-  background-size: 100% 100%;
-  background-position: center;
-  padding: 2px;
-  right: 10px;
-  top: 10px;
-}
-.historyBtn1 {
-  width: 20px;
-  height: 20px;
-  position: absolute;
-  background-size: 100% 100%;
-  background-position: center;
-  left: 3px;
-}
-.divider0 {
-  border-bottom: 1px solid #1074c1;
-  width: auto;
-  margin: 0 10px;
-  height: 13%;
-  display: block;
-  background: transparent;
-}
-.foldBtn {
-  width: 20px;
-  height: 20px;
-  position: absolute;
-  background-size: 100% 100%;
-  background-position: center;
-  padding: 2px;
-  right: 10px;
-  bottom: 10px;
-  cursor: pointer;
-}
+      // 合并数组并赋值给 sessionHistory
+      sessionHistory.value = [...list1, ...list2];
+      sessionHistory.value.forEach((item) => {
+        currentSessionID.value = item.id;
+      });
+      console.log(sessionHistory.value, '历史数据');
+    } catch (error) {
+      console.error('获取历史数据失败:', error);
+      // 错误处理(比如提示用户)
+    }
+  }
+  //格式化消息
+  const formatMessage = (text) => {
+    if (!text) return '';
+    let formatted = text
+      // 处理换行
+      .replace(/\n\n/g, '<br>')
+      .replace(/\n###/g, '<br>')
+      .replace(/###/g, '')
+      .replace(/---/g, '')
+      .replace(/^- /gm, '<br> - ')
+      // 处理粗体
+      .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
+      // 处理斜体
+      .replace(/\*(.*?)\*/g, '<em>$1</em>')
+      // 处理行内代码
+      .replace(/`([^`]+)`/g, '<code>$1</code>');
+    return formatted;
+  };
+  // 初始化按钮定位
+  onMounted(() => {
+    getHistoryList();
+    // 初始化(仅执行一次)
+    initMarked();
+  });
+</script>
 
-.right-side {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  width: calc(100% - 134px) !important;
-  .title {
-    text-align: center;
-    font-size: 14px;
-    padding: 5px;
+<style lang="less" scoped>
+  .btn-header {
+    width: 40px;
+    height: 40px;
+    margin-right: 5px;
+    margin-bottom: 5px;
   }
-  .dialog-area {
-    flex: 1;
-    gap: 30px;
-    overflow-y: auto;
-    padding: 5px;
+  .container {
     display: flex;
-    flex-direction: column;
-    color: #fff;
-
-    .ask-message {
-      padding: 10px;
-      border-radius: 5px;
-      background: #0c2842;
-      max-width: 300px;
-      min-width: 50px;
-      white-space: pre-wrap;
-      word-wrap: break-word;
-      overflow-wrap: break-word;
-      overflow: auto;
-      line-height: 1.5;
-    }
-    .answer-message {
-      padding: 10px;
-      border-radius: 5px;
-      background: #0c2842;
-      max-width: 90%;
-    }
+    flex-direction: row;
   }
-
-  .input-area {
-    margin: 10px 10px 20px 10px;
+  .mini-chat {
+    display: flex;
+    min-width: none;
+    width: 950px;
+    height: 85%;
+    border-radius: 4px;
+    position: fixed;
+    top: 50px;
+    right: 20px;
     padding: 10px;
-    background-color: #043256;
-    border: 1px solid #2cb6ff;
-    border-radius: 5px;
+    background-color: #0a1a2f;
+    background-image:
+      linear-gradient(to bottom, #0b69b6 1%, #0a1a2f 100%), linear-gradient(to left, #0b69b6, #0a1a2f), linear-gradient(to top, #0b69b6, #0a1a2f),
+      linear-gradient(to right, #0b69b6, #0a1a2f);
+    background-size:
+      100% 5px,
+      5px 100%,
+      100% 5px,
+      5px 100%;
+    background-position:
+      top left,
+      top right,
+      bottom right,
+      bottom left;
+    background-repeat: no-repeat;
+    z-index: 9999999;
+    color: #fff;
+  }
+  .doc {
     display: flex;
     flex-direction: column;
-    justify-content: space-between;
-    gap: 5px;
-    height: 25%;
-  }
-  /* 文件列表容器 */
-  .uploaded-files {
-    padding: 8px;
-    background-color: #f5f5f5;
+    min-width: 600px;
+    height: 85%;
     border-radius: 4px;
-    min-height: 40px;
+    position: fixed;
+    top: 50px;
+    right: 51%;
+    padding: 10px;
+    background-color: #0a1a2f;
+    background-image:
+      linear-gradient(to bottom, #0b69b6 1%, #0a1a2f 100%), linear-gradient(to left, #0b69b6, #0a1a2f), linear-gradient(to top, #0b69b6, #0a1a2f),
+      linear-gradient(to right, #0b69b6, #0a1a2f);
+    background-size:
+      100% 5px,
+      5px 100%,
+      100% 5px,
+      5px 100%;
+    background-position:
+      top left,
+      top right,
+      bottom right,
+      bottom left;
+    background-repeat: no-repeat;
+    z-index: 9999999;
+    color: #fff;
   }
 
-  /* 单个文件项 */
-  .file-item {
-    display: inline-flex;
+  .close {
+    display: flex;
+    justify-content: flex-end;
     align-items: center;
-    padding: 4px 8px;
-    margin-right: 8px;
-    margin-bottom: 8px;
-    background-color: #fff;
-    border: 1px solid #e9e9e9;
+    width: 100%;
+    padding: 1px;
+    border-radius: 8px;
+  }
+
+  .closeBtn {
+    background-color: #007bff;
+    color: white;
+    border: none;
     border-radius: 4px;
+    cursor: pointer;
+    transition: all 0.3s ease;
   }
 
-  /* 文件名 */
-  .file-name {
-    margin-left: 8px;
-    margin-right: 8px;
-    max-width: 150px;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
+  .closeBtn:hover {
+    background-color: #0056b3;
+  }
+  .left-side {
+    background: #0c2842;
+    transition: width 0.5s ease; /* 平滑过渡动画 */
+    width: 140px; /* 展开时宽度 */
+    position: relative; /* 用于按钮定位 */
+  }
+  .left-side.collapsed {
+    width: 40px; /* 折叠时宽度 */
   }
 
-  .ant-input {
-    border: none;
-    background-color: rgba(255, 255, 255, 0) !important;
-    color: #fff;
+  .addBtn {
+    height: 30px;
+    position: absolute;
+    background-size: 100% 100%;
+    background-position: center;
+    padding: 2px;
+    right: 10px;
+    bottom: 40px;
+    left: 10px;
+    align-items: center;
+    border-radius: 3px;
+    cursor: pointer;
   }
-  .ant-input:focus {
-    border: none; /* 聚焦时无边框 */
-    outline: none; /* 聚焦时无轮廓 */
-    box-shadow: none; /* 移除可能存在的阴影效果 */
+  .custom-list {
+    height: 650px;
+    overflow-y: auto;
   }
-  .ctrl-btn {
+  .text-container {
     display: flex;
-    flex-direction: row;
     justify-content: space-between;
+    width: 100%;
+    overflow: hidden;
   }
-  .question-input {
-    background-color: #1e293b !important;
-    border-color: #334155 !important;
-    color: #e2e8f0 !important;
-    border-radius: 8px !important;
-    padding: 12px 16px !important;
-    font-size: 14px !important;
+  .btn-container {
+    display: flex;
   }
-
-  .question-input::placeholder {
-    color: #64748b !important;
+  .jeecg-layout-header-action span[role='img'] {
+    padding: 0;
   }
-
-  .control-btn {
-    height: 25px;
-    background-color: #043256;
-    border: 1px solid #2cb6ff;
+  .text-ellipsis {
+    flex: 1;
+  }
+  .edit-text {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    width: 90px;
     color: #fff;
-    font-size: 10px;
-    margin-right: 10px;
+    font-size: 12px;
     cursor: pointer;
-    transition: background-color 0.2s ease; /* 平滑过渡效果 */
   }
-  /* 激活状态样式(点击后) */
-  .control-btn.active {
-    background-color: #2cb6ff; /* 蓝色背景(Ant Design 主色) */
-    color: white; /* 白色文字 */
+  .edit-icon {
+    flex-shrink: 0;
+    margin-left: auto;
+    line-height: 23px;
   }
-  .control-btn1 {
-    height: 20px;
-    background-color: #234a6b;
-    border: 1px solid #234a6b;
-    color: #fff;
-    font-size: 10px;
-    margin-right: 10px;
+  .delete-icon {
+    flex-shrink: 0;
+    margin-left: auto;
+    line-height: 23px;
+  }
+  .edit-icon:hover {
+    color: #1890ff !important;
     cursor: pointer;
-    transition: all 0.2s;
   }
-  .control-btn2 {
-    height: 20px;
-    background-color: #2cb6ff;
-    border: 1px solid #2cb6ff;
-    color: #fff;
-    font-size: 10px;
-    margin-right: 10px;
+  .delete-icon:hover {
+    color: #1890ff !important;
     cursor: pointer;
-    transition: all 0.2s;
   }
-  /* 文件上传区 */
-  .file-upload {
+  .edit-input {
+    font-size: 10px;
+  }
+  .btn-text-bg {
+    width: 14px;
+    height: 14px;
     position: absolute;
-    right: 20px;
-    bottom: 70px;
-    width: 180px;
-    display: flex;
-    flex-direction: column;
-    gap: 10px;
-    border: 1px solid #2cb6ff;
-    background-color: #234a6b;
-    border-radius: 6px;
-    padding: 10px;
+    background-size: 100% 100%;
+    right: 10px;
+    top: 10px;
+    left: 10px;
+    bottom: 10px;
   }
-
-  .input-container {
-    position: relative;
-    display: flex;
-    align-items: center;
-    width: 100%;
+  .btn-text {
+    margin-left: 3px;
+    font-size: 12px;
+    color: #fff;
+    white-space: nowrap;
+    margin-left: 30px;
+    line-height: 29px;
   }
-
-  .file-input {
-    flex: 1;
-    background-color: #234a6b;
-    border-color: #2cb6ff !important;
-    color: #e2e8f0 !important;
-    border-radius: 6px !important;
-    font-size: 10px !important;
-    padding-right: 70px !important;
-    height: 36px !important;
-    width: 100% !important;
+  .historyBtn {
+    width: 20px;
+    height: 20px;
+    position: absolute;
+    background-size: 100% 100%;
+    background-position: center;
+    padding: 2px;
+    right: 10px;
+    top: 10px;
   }
-
-  .confirm-btn {
+  .historyBtn1 {
+    width: 20px;
+    height: 20px;
     position: absolute;
-    right: 5px;
-    background-color: #2cb6ff;
-    border: none;
-    color: #fff;
-    border-radius: 4px;
-    font-size: 12px;
-    padding: 4px 10px;
+    background-size: 100% 100%;
+    background-position: center;
+    left: 3px;
+  }
+  .divider0 {
+    border-bottom: 1px solid #1074c1;
+    width: auto;
+    margin: 0 10px;
+    height: 13%;
+    display: block;
+    background: transparent;
+  }
+  .foldBtn {
+    width: 20px;
+    height: 20px;
+    position: absolute;
+    background-size: 100% 100%;
+    background-position: center;
+    padding: 2px;
+    right: 10px;
+    bottom: 10px;
     cursor: pointer;
-    transition: all 0.2s;
-    height: 28px;
   }
 
-  .confirm-btn:hover {
-    background-color: #2cb6ff;
-  }
+  .right-side {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    width: calc(100% - 134px) !important;
+    .title {
+      text-align: center;
+      font-size: 14px;
+      padding: 5px;
+    }
+    .dialog-area {
+      flex: 1;
+      gap: 30px;
+      overflow-y: auto;
+      padding: 5px;
+      display: flex;
+      flex-direction: column;
+      color: #fff;
 
-  .custom-upload {
-    width: 100%;
-    padding: 0 !important;
-  }
+      .ask-message {
+        padding: 10px;
+        border-radius: 5px;
+        background: #0c2842;
+        max-width: 300px;
+        min-width: 50px;
+        white-space: pre-wrap;
+        word-wrap: break-word;
+        overflow-wrap: break-word;
+        overflow: auto;
+        line-height: 1.5;
+      }
+      .answer-message {
+        padding: 10px;
+        border-radius: 5px;
+        background: #0c2842;
+        max-width: 90%;
+      }
+    }
 
-  .upload-btn {
-    background-color: #234a6b !important;
-    border: 1px solid #2188c3 !important;
-    color: #dbeafe !important;
-    border-radius: 6px !important;
-    font-size: 12px !important;
-    cursor: pointer;
-    transition: all 0.2s;
-    padding: 8px 0 !important;
-    width: 190% !important;
-    height: 36px !important;
-    box-sizing: border-box !important;
-  }
-  .upload-btn:hover {
-    background-color: #1f84bd !important;
-    color: #fff !important;
+    .input-area {
+      margin: 10px 10px 20px 10px;
+      padding: 10px;
+      background-color: #043256;
+      border: 1px solid #2cb6ff;
+      border-radius: 5px;
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      gap: 5px;
+      height: 25%;
+    }
+    /* 文件列表容器 */
+    .uploaded-files {
+      padding: 8px;
+      background-color: #f5f5f5;
+      border-radius: 4px;
+      min-height: 40px;
+    }
+
+    /* 单个文件项 */
+    .file-item {
+      display: inline-flex;
+      align-items: center;
+      padding: 4px 8px;
+      margin-right: 8px;
+      margin-bottom: 8px;
+      background-color: #fff;
+      border: 1px solid #e9e9e9;
+      border-radius: 4px;
+    }
+
+    /* 文件名 */
+    .file-name {
+      margin-left: 8px;
+      margin-right: 8px;
+      max-width: 150px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+    .ant-input {
+      border: none;
+      background-color: rgba(255, 255, 255, 0) !important;
+      color: #fff;
+    }
+    .ant-input:focus {
+      border: none; /* 聚焦时无边框 */
+      outline: none; /* 聚焦时无轮廓 */
+      box-shadow: none; /* 移除可能存在的阴影效果 */
+    }
+    .ctrl-btn {
+      display: flex;
+      flex-direction: row;
+      justify-content: space-between;
+    }
+    .question-input {
+      background-color: #1e293b !important;
+      border-color: #334155 !important;
+      color: #e2e8f0 !important;
+      border-radius: 8px !important;
+      padding: 12px 16px !important;
+      font-size: 14px !important;
+    }
+
+    .question-input::placeholder {
+      color: #64748b !important;
+    }
+
+    .control-btn {
+      height: 25px;
+      background-color: #043256;
+      border: 1px solid #2cb6ff;
+      color: #fff;
+      font-size: 10px;
+      margin-right: 10px;
+      cursor: pointer;
+      transition: background-color 0.2s ease; /* 平滑过渡效果 */
+    }
+    /* 激活状态样式(点击后) */
+    .control-btn.active {
+      background-color: #2cb6ff; /* 蓝色背景(Ant Design 主色) */
+      color: white; /* 白色文字 */
+    }
+    .control-btn1 {
+      height: 20px;
+      background-color: #234a6b;
+      border: 1px solid #234a6b;
+      color: #fff;
+      font-size: 10px;
+      margin-right: 10px;
+      cursor: pointer;
+      transition: all 0.2s;
+    }
+    .control-btn2 {
+      height: 20px;
+      background-color: #2cb6ff;
+      border: 1px solid #2cb6ff;
+      color: #fff;
+      font-size: 10px;
+      margin-right: 10px;
+      cursor: pointer;
+      transition: all 0.2s;
+    }
+    /* 文件上传区 */
+    .file-upload {
+      position: absolute;
+      right: 20px;
+      bottom: 70px;
+      width: 180px;
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+      border: 1px solid #2cb6ff;
+      background-color: #234a6b;
+      border-radius: 6px;
+      padding: 10px;
+    }
+
+    .input-container {
+      position: relative;
+      display: flex;
+      align-items: center;
+      width: 100%;
+    }
+
+    .file-input {
+      flex: 1;
+      background-color: #234a6b;
+      border-color: #2cb6ff !important;
+      color: #e2e8f0 !important;
+      border-radius: 6px !important;
+      font-size: 10px !important;
+      padding-right: 70px !important;
+      height: 36px !important;
+      width: 100% !important;
+    }
+
+    .confirm-btn {
+      position: absolute;
+      right: 5px;
+      background-color: #2cb6ff;
+      border: none;
+      color: #fff;
+      border-radius: 4px;
+      font-size: 12px;
+      padding: 4px 10px;
+      cursor: pointer;
+      transition: all 0.2s;
+      height: 28px;
+    }
+
+    .confirm-btn:hover {
+      background-color: #2cb6ff;
+    }
+
+    .custom-upload {
+      width: 100%;
+      padding: 0 !important;
+    }
+
+    .upload-btn {
+      background-color: #234a6b !important;
+      border: 1px solid #2188c3 !important;
+      color: #dbeafe !important;
+      border-radius: 6px !important;
+      font-size: 12px !important;
+      cursor: pointer;
+      transition: all 0.2s;
+      padding: 8px 0 !important;
+      width: 190% !important;
+      height: 36px !important;
+      box-sizing: border-box !important;
+    }
+    .upload-btn:hover {
+      background-color: #1f84bd !important;
+      color: #fff !important;
+    }
+    .custom-upload .ant-upload-select:hover .ant-btn {
+      border-color: #1f84bd !important;
+    }
+    .message-wrapper.ai-message-wrapper {
+      display: flex;
+      align-items: flex-start;
+    }
+    .answerIcon {
+      flex: 0 0 45px;
+    }
+
+    .suggestion-item {
+      height: 30px;
+      margin-left: 45px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 10px 16px;
+      border: 1px solid #1890ff;
+      color: white;
+      border-radius: 4px;
+      cursor: pointer;
+      width: 33%;
+      font-size: 12px;
+    }
+    .thinking-section {
+      border-left: 3px solid #e5e7eb;
+      padding-left: 12px;
+      margin-bottom: 16px;
+    }
+
+    .thinking-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 8px 0;
+      cursor: pointer;
+      user-select: none;
+    }
+
+    .thinking-title {
+      font-size: 14px;
+      font-weight: 500;
+      color: #6b7280;
+    }
   }
-  .custom-upload .ant-upload-select:hover .ant-btn {
-    border-color: #1f84bd !important;
+</style>
+<style scoped>
+  .zxm-popover-inner-content {
+    padding: 1px;
   }
-  .message-wrapper.ai-message-wrapper {
+  .message-wrapper {
     display: flex;
     align-items: flex-start;
+    position: relative;
   }
-  .answerIcon {
-    flex: 0 0 45px;
+  .user-message-wrapper {
+    flex-direction: row-reverse;
   }
-
-  .suggestion-item {
-    height: 30px;
-    margin-left: 45px;
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    padding: 10px 16px;
-    border: 1px solid #1890ff;
-    color: white;
-    border-radius: 4px;
-    cursor: pointer;
-    width: 33%;
-    font-size: 12px;
+  .ai-message-wrapper {
+    flex-direction: row;
   }
-  .thinking-section {
-    border-left: 3px solid #e5e7eb;
-    padding-left: 12px;
-    margin-bottom: 16px;
+  /* 鼠标滑过 .message-wrapper 时,显示图标 */
+  .ai-message-wrapper:hover .copy-icon-container {
+    opacity: 1;
+    visibility: visible;
   }
-
-  .thinking-header {
+  .user-message-wrapper:hover .copy-icon-container {
+    opacity: 1;
+    visibility: visible;
+  }
+  .copy-icon {
+    font-size: 16px;
+    color: #666;
+    cursor: pointer;
+  }
+  /* 默认隐藏,hover 时显示 */
+  .copy-icon-container {
+    position: absolute;
     display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 8px 0;
+    flex-direction: row;
+    bottom: -20px;
+    right: 15%;
+    opacity: 0;
+    visibility: hidden;
+    transition: all 0.3s ease;
+    z-index: 10;
+  }
+  /* 复制图标样式 */
+  .copy-icon {
+    font-size: 16px;
     cursor: pointer;
-    user-select: none;
+    margin-right: 20%;
+    float: right;
+    color: #fff;
   }
-
-  .thinking-title {
-    font-size: 14px;
-    font-weight: 500;
-    color: #6b7280;
+  .message-wrapper:hover .copy-icon {
+    opacity: 1;
+  }
+  .copy-icon:hover {
+    color: #1890ff;
+  }
+  ::v-deep table {
+    border-collapse: collapse;
+    width: 100%;
+    margin: 10px 0;
+    border: 1px solid #333;
+  }
+  ::v-deep th {
+    border: 1px solid #333;
+    background-color: #234a6b;
+    padding: 5px;
+    text-align: center;
+  }
+  ::v-deep td {
+    border: 1px solid #ddd;
+    padding: 8px 12px;
+    text-align: center;
   }
-}
-</style>
-<style scoped>
-.zxm-popover-inner-content {
-  padding: 1px;
-}
-.message-wrapper {
-  display: flex;
-  align-items: flex-start;
-  position: relative;
-}
-.user-message-wrapper {
-  flex-direction: row-reverse;
-}
-.ai-message-wrapper {
-  flex-direction: row;
-}
-/* 鼠标滑过 .message-wrapper 时,显示图标 */
-.ai-message-wrapper:hover .copy-icon-container {
-  opacity: 1;
-  visibility: visible;
-}
-.user-message-wrapper:hover .copy-icon-container {
-  opacity: 1;
-  visibility: visible;
-}
-.copy-icon {
-  font-size: 16px;
-  color: #666;
-  cursor: pointer;
-}
-/* 默认隐藏,hover 时显示 */
-.copy-icon-container {
-  position: absolute;
-  display: flex;
-  flex-direction: row;
-  bottom: -20px;
-  right: 15%;
-  opacity: 0;
-  visibility: hidden;
-  transition: all 0.3s ease;
-  z-index: 10;
-}
-/* 复制图标样式 */
-.copy-icon {
-  font-size: 16px;
-  cursor: pointer;
-  margin-right: 20%;
-  float: right;
-  color: #fff;
-}
-.message-wrapper:hover .copy-icon {
-  opacity: 1;
-}
-.copy-icon:hover {
-  color: #1890ff;
-}
-::v-deep table {
-  border-collapse: collapse;
-  width: 100%;
-  margin: 10px 0;
-  border: 1px solid #333;
-}
-::v-deep th {
-  border: 1px solid #333;
-  background-color: #234a6b;
-  padding: 5px;
-  text-align: center;
-}
-::v-deep td {
-  border: 1px solid #ddd;
-  padding: 8px 12px;
-  text-align: center;
-}
 </style>
 <style scoped>
-/* 已上传文件列表 */
-.file-list {
-  margin-top: 20px;
-}
-.pre-container {
-  flex: 1;
-  width: 100%;
-  height: 100%;
-  overflow: auto;
-}
-.vue-office-excel {
-  background: #fff !important;
-}
+  /* 已上传文件列表 */
+  .file-list {
+    margin-top: 20px;
+  }
+  .pre-container {
+    flex: 1;
+    width: 100%;
+    height: 100%;
+    overflow: auto;
+  }
+  .vue-office-excel {
+    background: #fff !important;
+  }
 
-.file-item {
-  display: flex;
-  align-items: center;
-  padding: 12px 15px;
-  background-color: #ddd;
-  border-radius: 6px;
-  margin-bottom: 10px;
-  transition: background-color 0.2s ease;
-}
+  .file-item {
+    display: flex;
+    align-items: center;
+    padding: 12px 15px;
+    background-color: #ddd;
+    border-radius: 6px;
+    margin-bottom: 10px;
+    transition: background-color 0.2s ease;
+  }
 
-.file-item:hover {
-  background-color: #f0f2f5;
-}
+  .file-item:hover {
+    background-color: #f0f2f5;
+  }
 
-.file-info {
-  flex: 1;
-  overflow: hidden;
-}
+  .file-info {
+    flex: 1;
+    overflow: hidden;
+  }
 
-.file-name {
-  font-size: 15px;
-  color: #1d2129;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  margin-bottom: 3px;
-}
+  .file-name {
+    font-size: 15px;
+    color: #1d2129;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin-bottom: 3px;
+  }
 
-.file-actions {
-  display: flex;
-  gap: 10px;
-}
+  .file-actions {
+    display: flex;
+    gap: 10px;
+  }
 
-.btn {
-  padding: 0px 15px;
-  margin-left: 10px;
-  border-radius: 4px;
-  border: none;
-  font-size: 14px;
-  cursor: pointer;
-  transition: all 0.2s ease;
-}
+  .btn {
+    padding: 0px 15px;
+    margin-left: 10px;
+    border-radius: 4px;
+    border: none;
+    font-size: 14px;
+    cursor: pointer;
+    transition: all 0.2s ease;
+  }
 
-.btn-preview {
-  background-color: #165dff;
-  color: white;
-}
+  .btn-preview {
+    background-color: #165dff;
+    color: white;
+  }
 
-.btn-preview:hover {
-  background-color: #0d47a1;
-}
+  .btn-preview:hover {
+    background-color: #0d47a1;
+  }
 
-.btn-delete {
-  background-color: #f2f3f5;
-  color: #4e5969;
-}
+  .btn-delete {
+    background-color: #f2f3f5;
+    color: #4e5969;
+  }
 
-.btn-delete:hover {
-  background-color: #e5e6eb;
-  color: #1d2129;
-}
-</style>
+  .btn-delete:hover {
+    background-color: #e5e6eb;
+    color: #1d2129;
+  }
+</style>

+ 80 - 0
src/components/vent/camera/HlsPlayer.vue

@@ -0,0 +1,80 @@
+<!-- components/HlsPlayer.vue -->
+<template>
+  <video ref="videoEl" controls style="width: 100%; height: auto"></video>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref, onBeforeUnmount } from 'vue';
+  import Hls from 'hls.js';
+  import { useDrag } from '/@/hooks/event/useDrag';
+  import videojs from 'video.js';
+
+  const props = defineProps<{ src: string }>();
+  const videoEl = ref<HTMLVideoElement | null>(null);
+  let hls: Hls | null = null;
+  let flv: any = null;
+  let rtspServer: any = null;
+
+  onMounted(async () => {
+    const video = videoEl.value;
+    if (!video) return;
+    useDrag(video);
+
+    if (props.src.includes('.m3u8') && Hls.isSupported()) {
+      hls = new Hls();
+      hls.loadSource(props.src);
+      hls.attachMedia(video);
+    } else if (props.src.startsWith('rtsp://')) {
+      video?.setAttribute('class', 'liveVideo');
+      video?.setAttribute('muted', 'muted');
+      video.autoplay = true;
+      rtspServer = new window['WebRtcStreamer'](
+        videoEl.value,
+        VUE_APP_URL.webRtcUrl.startsWith('/') ? location.protocol + VUE_APP_URL.webRtcUrl : VUE_APP_URL.webRtcUrl
+      );
+      await rtspServer.connect(props.src);
+      if (rtspServer && video.play) video.play();
+    } else if (props.src.includes('.flv') && video.canPlayType('application/x-mpegURL')) {
+      flv = videojs(
+        video,
+        {
+          controls: true,
+          autoplay: true,
+          preload: 'auto',
+          sources: [
+            {
+              src: props.src,
+              type: 'application/x-mpegURL',
+            },
+          ],
+        },
+        () => {
+          videojs.log('播放器准备好了!');
+          flv.play();
+        }
+      );
+      flv.play();
+    } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
+      video.src = props.src;
+    }
+  });
+
+  onBeforeUnmount(() => {
+    if (hls) {
+      hls.destroy();
+      hls = null;
+    }
+    if (flv) {
+      if (flv.dispose) flv.dispose();
+      flv = null;
+    }
+    if (rtspServer) {
+      rtspServer.disconnect();
+      rtspServer = null;
+    }
+    if (videoEl.value) {
+      videoEl.value.pause();
+      videoEl.value.remove();
+    }
+  });
+</script>

+ 61 - 0
src/components/vent/camera/LivePlayerWrapper.vue

@@ -0,0 +1,61 @@
+<template>
+  <div ref="videoEl" style="width: 100%; height: 100%">
+    <live-player ref="player" :video-url="src" live stretch autoplay muted controls style="width: 100%; height: 100%" @error="handleError" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import LivePlayer from '@liveqing/liveplayer-v3';
+  import { useDrag } from '/@/hooks/event/useDrag';
+  import { onBeforeUnmount, onMounted, onUnmounted, ref } from 'vue';
+  // 注册组件
+  defineProps<{
+    src: string;
+  }>();
+
+  const videoEl = ref<HTMLElement | null>(null);
+
+  const player = ref<LivePlayer | null>(null);
+  let retryCount = 0;
+  const maxRetry = 30; // 最大重试次数
+  const retryInterval = 3000; // 重试间隔(毫秒)
+  let timer: NodeJS.Timeout | null = null;
+
+  function handleError(err) {
+    if (retryCount < maxRetry) {
+      retryCount++;
+      console.log(`第 ${retryCount} 次重试...`);
+      timer = setTimeout(() => {
+        if (player.value && player.value.play) player.value.play();
+      }, retryInterval);
+    } else {
+      console.error('重试次数已达上限,停止重连');
+      // 可在此处显示“连接失败,请刷新”提示
+      showReconnectButton();
+    }
+  }
+
+  function showReconnectButton() {
+    if (player.value)
+      player.value.innerHTML = `
+    <div style="color: white; text-align: center; padding: 20px;">
+      视频连接失败<br>
+      <button onclick="location.reload()" style="margin-top: 10px; padding: 8px 16px;">点击重试</button>
+    </div>
+  `;
+  }
+
+  onMounted(() => {
+    if (videoEl.value) useDrag(videoEl.value);
+  });
+
+  onBeforeUnmount(() => {
+    if (timer) clearTimeout(timer);
+    if (player.value && player.value.destroy) player.value.destroy();
+    if (player.value && player.value.remove) player.value.remove();
+  });
+</script>
+
+<style scoped>
+  /* 添加scoped样式避免影响其他组件 */
+</style>

+ 35 - 0
src/components/vent/camera/createPlayer.vue

@@ -0,0 +1,35 @@
+<template>
+  <div id="LivePlayerBox">
+    <component v-for="(item, index) in streamList" :key="index" :is="item.zj" :src="item.src" class="liveVideo" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted, h, onBeforeMount, onUnmounted, onBeforeUnmount } from 'vue';
+  import HlsPlayer from '@/components/vent/camera/HlsPlayer.vue';
+  import LivePlayerWrapper from '@/components/vent/camera/LivePlayerWrapper.vue';
+
+  const props = defineProps<{
+    srcList: any[];
+  }>();
+
+  const streamList = ref<any[]>([]);
+
+  onMounted(() => {
+    const componentList: any[] = [];
+    for (let i = 0; i < props.srcList.length; i++) {
+      const stream = props.srcList[i];
+      if (stream.addr.toLowerCase().includes('.m3u8') || stream.addr.toLowerCase().startsWith('rtsp://')) {
+        componentList.push({ zj: HlsPlayer, src: stream.addr });
+      } else if (stream.addr.startsWith('rtmp://')) {
+        componentList.push({ zj: LivePlayerWrapper, src: stream.addr });
+      } else {
+        componentList.push({ zj: LivePlayerWrapper, src: stream.addr });
+      }
+    }
+    streamList.value = componentList;
+  });
+  onBeforeUnmount(() => {
+    streamList.value = [];
+  });
+</script>

+ 185 - 0
src/components/vent/draggableVideoPlayer.vue

@@ -0,0 +1,185 @@
+<template>
+  <div
+    ref="playerContainer"
+    class="draggable-video-player"
+    :style="{ transform: `translate(${x}px, ${y}px)`, zIndex }"
+    @mousedown="handleMouseDown"
+    @touchstart="handleTouchStart"
+  >
+    <div class="drag-header">
+      {{ title }}
+      <button @click="onClose" class="close-btn">×</button>
+    </div>
+    <div ref="videoElRef" class="video-wrapper"></div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
+  import videojs from 'video.js';
+  import 'video.js/dist/video-js.css';
+
+  interface Props {
+    src: string;
+    title?: string;
+    initialX?: number;
+    initialY?: number;
+    zIndex?: number;
+  }
+
+  const props = withDefaults(defineProps<Props>(), {
+    title: '视频',
+    initialX: 0,
+    initialY: 0,
+    zIndex: 10,
+  });
+
+  const emit = defineEmits<{
+    (e: 'close'): void;
+    (e: 'drag-start'): void;
+    (e: 'drag-move', pos: { x: number; y: number }): void;
+  }>();
+
+  const x = ref(props.initialX);
+  const y = ref(props.initialY);
+  const playerContainer = ref(null);
+  const videoElRef = ref(null);
+  const player = ref(null);
+  const isDragging = ref(false);
+  const dragOffset = ref({ x: 0, y: 0 });
+
+  // 初始化 Video.js
+  onMounted(async () => {
+    await nextTick();
+    if (!videoElRef.value) return;
+
+    const videoEl = document.createElement('video');
+    videoEl.classList.add('video-js', 'vjs-default-skin');
+    videoEl.setAttribute('controls', '');
+    videoEl.setAttribute('preload', 'auto');
+    videoEl.setAttribute('width', '320');
+    videoEl.setAttribute('height', '180');
+    videoEl.setAttribute('playsinline', '');
+
+    const source = document.createElement('source');
+    source.src = props.src;
+    source.type = props.src.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4';
+    videoEl.appendChild(source);
+
+    videoElRef.value.innerHTML = '';
+    videoElRef.value.appendChild(videoEl);
+
+    player.value = videojs(videoEl, {
+      controls: true,
+      autoplay: false,
+      preload: 'auto',
+      responsive: false,
+      fluid: false,
+    });
+  });
+
+  // 销毁播放器
+  onBeforeUnmount(() => {
+    if (player.value && !player.value.isDisposed()) {
+      player.value.dispose();
+    }
+  });
+
+  // 拖拽逻辑(仅限标题栏)
+  const handleMouseDown = (e) => {
+    if (!e.target.closest('.drag-header')) return;
+    startDrag(e.clientX, e.clientY);
+  };
+
+  const handleTouchStart = (e) => {
+    if (!e.target.closest('.drag-header')) return;
+    const touch = e.touches[0];
+    startDrag(touch.clientX, touch.clientY, true);
+    e.preventDefault();
+  };
+
+  const startDrag = (clientX, clientY, isTouch = false) => {
+    isDragging.value = true;
+    dragOffset.value = {
+      x: clientX - x.value,
+      y: clientY - y.value,
+    };
+    emit('drag-start');
+    document.addEventListener('mousemove', handleMouseMove);
+    document.addEventListener('mouseup', handleMouseUp);
+    if (isTouch) {
+      document.addEventListener('touchmove', handleTouchMove, { passive: false });
+      document.addEventListener('touchend', handleMouseUp);
+    }
+  };
+
+  const handleMouseMove = (e) => move(e.clientX, e.clientY);
+  const handleTouchMove = (e) => {
+    const touch = e.touches[0];
+    move(touch.clientX, touch.clientY);
+  };
+
+  const move = (clientX, clientY) => {
+    x.value = clientX - dragOffset.value.x;
+    y.value = clientY - dragOffset.value.y;
+    emit('drag-move', { x: x.value, y: y.value });
+  };
+
+  const handleMouseUp = () => {
+    isDragging.value = false;
+    document.removeEventListener('mousemove', handleMouseMove);
+    document.removeEventListener('mouseup', handleMouseUp);
+    document.removeEventListener('touchmove', handleTouchMove);
+    document.removeEventListener('touchend', handleMouseUp);
+  };
+
+  const onClose = () => {
+    emit('close');
+  };
+</script>
+
+<style scoped>
+  .draggable-video-player {
+    position: fixed;
+    top: 0;
+    left: 0;
+    background: #000;
+    border-radius: 6px;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
+    cursor: move;
+    user-select: none;
+    width: 320px;
+    z-index: v-bind(zIndex);
+  }
+
+  .drag-header {
+    background: #212121;
+    color: white;
+    padding: 6px 10px;
+    font-size: 14px;
+    border-top-left-radius: 6px;
+    border-top-right-radius: 6px;
+    cursor: move;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  .close-btn {
+    background: none;
+    border: none;
+    color: white;
+    font-size: 18px;
+    cursor: pointer;
+    padding: 0;
+    width: 20px;
+    height: 20px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .video-wrapper {
+    width: 100%;
+  }
+</style>

+ 6 - 0
src/hooks/component/createPlayer.ts

@@ -0,0 +1,6 @@
+import { h, VNode } from 'vue';
+import createPlayer from '@/components/vent/camera/createPlayer.vue';
+
+export function createPlayerVNode(streamList: []): VNode {
+  return h(createPlayer, { srcList: streamList });
+}

+ 92 - 29
src/hooks/system/useCamera.ts

@@ -1,6 +1,5 @@
 import { defHttp } from '/@/utils/http/axios';
-// import { render, h, nextTick } from 'vue';
-// import LivePlayer from '@liveqing/liveplayer-v3';
+import { render, h, nextTick, VNode, ref } from 'vue';
 import { useDrag } from '../event/useDrag';
 import Player, { I18N } from 'xgplayer';
 import HlsPlugin from 'xgplayer-hls';
@@ -8,6 +7,7 @@ import FlvPlugin from 'xgplayer-flv';
 import 'xgplayer/dist/index.min.css';
 import ZH from 'xgplayer/es/lang/zh-cn';
 import videojs from 'video.js';
+import { createPlayerVNode } from '../component/createPlayer';
 I18N.use(ZH);
 
 export function useCamera() {
@@ -20,12 +20,9 @@ export function useCamera() {
   const playerDoms = <(HTMLVideoElement | undefined | null)[]>[];
   const videoParentDomList: (HTMLElement | [string, { name: string; addr: string; cameraRate: number; devicekind: string }])[] = [];
 
-  async function getCamera(deviceid, parentPlayerDom?, devKind?, isCustom = false) {
-    removeCamera();
-    if (!parentPlayerDom) {
-      parentPlayerDom = document.createElement('div');
-      parentPlayerDom.setAttribute('style', `top:0px; left: 0px; width: 100%; height: 100%; position: fixed; z-index: 999;`);
-    }
+  async function getCamera(deviceid, parentPlayerDom, renderPlayer, devKind?, isCustom = false) {
+    await removeCameraRef(parentPlayerDom, renderPlayer);
+
     let res;
     if (!devKind) {
       res = await cameraList({ deviceid });
@@ -76,7 +73,7 @@ export function useCamera() {
     if (isCustom) {
       return cameraAddrs;
     } else {
-      await deviceCameraInit(cameraAddrs, parentPlayerDom);
+      await deviceCameraInit1(cameraAddrs, parentPlayerDom);
     }
   }
 
@@ -193,7 +190,6 @@ export function useCamera() {
           if (videoParent[1] && videoParent[1].addr) {
             let player;
             const fileExtension = videoParent[1].addr.split('.').pop();
-            // const player = getPlayer(fileExtension, videoParent[0], videoParent[1].devicekind, videoParent[1].addr, videoParent[1].cameraRate);
             if (fileExtension === 'flv' || videoParent[1].devicekind == 'flv') {
               const videoDom: HTMLElement = document.createElement('div');
               videoDom.setAttribute('id', videoParent[0]);
@@ -234,6 +230,72 @@ export function useCamera() {
                   maxJumpDistance: 10,
                 },
               });
+            } else if (fileExtension === 'm3u8' || videoParent[1].devicekind == 'm3u8') {
+              const videoDom: HTMLElement = document.createElement('video');
+              videoDom.setAttribute('id', videoParent[0]);
+              videoDom.style = 'width: 100%; height: 100%;';
+              videoParentDom.appendChild(videoDom);
+              if (videoDom.canPlayType('application/vnd.apple.mpegurl')) {
+                // 原生支持 hls 播放
+                player = new Player({
+                  lang: 'zh',
+                  id: videoParent[0],
+                  url: videoParent[1].addr,
+                  width: 294,
+                  height: 188,
+                  isLive: true,
+                  autoplay: true,
+                  autoplayMuted: true,
+                  cors: true,
+                  ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
+                  poster: '/src/assets/images/vent/noSinge.png',
+                  defaultPlaybackRate: videoParent[1].cameraRate || 1,
+                  controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
+                  hls: {
+                    retryCount: 10, // 重试 3 次,默认值
+                    retryDelay: 30000, // 每次重试间隔 1 秒,默认值
+                    loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
+                    disconnectTime: 30, //直播断流时间,
+                    fetchOptions: {
+                      // 该参数会透传给 fetch,默认值为 undefined
+                      mode: 'cors',
+                    },
+                    targetLatency: 10, // 直播目标延迟,默认 10 秒
+                    maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
+                    maxJumpDistance: 10,
+                  },
+                });
+              } else if (HlsPlugin.isSupported()) {
+                // 第一步
+                player = new Player({
+                  lang: 'zh',
+                  id: videoParent[0],
+                  url: videoParent[1].addr,
+                  width: 294,
+                  height: 188,
+                  isLive: true,
+                  autoplay: true,
+                  autoplayMuted: true,
+                  ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
+                  plugins: [HlsPlugin], // 第二步
+                  poster: '/src/assets/images/vent/noSinge.png',
+                  defaultPlaybackRate: videoParent[1].cameraRate || 1,
+                  controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
+                  hls: {
+                    retryCount: 10, // 重试 3 次,默认值
+                    retryDelay: 30000, // 每次重试间隔 1 秒,默认值
+                    loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
+                    disconnectTime: 30,
+                    fetchOptions: {
+                      // 该参数会透传给 fetch,默认值为 undefined
+                      mode: 'cors',
+                    },
+                    targetLatency: 10, // 直播目标延迟,默认 10 秒
+                    maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
+                    maxJumpDistance: 10,
+                  },
+                });
+              }
             } else {
               const videoDom: HTMLElement = document.createElement('video');
               videoDom.setAttribute('id', videoParent[0]);
@@ -261,7 +323,7 @@ export function useCamera() {
               );
               player.play();
             }
-            playerList.push(player);
+            if (player) playerList.push(player);
           }
         } else {
           useDrag(videoParent as HTMLElement);
@@ -298,6 +360,11 @@ export function useCamera() {
     });
   }
 
+  function deviceCameraInit1(cameraAddrs, player) {
+    const vnode = createPlayerVNode(cameraAddrs);
+    render(vnode, player.value);
+  }
+
   function getPlayer(fileExtension, playerDomId, camerakind, cameraUrl, cameraRate, option = { width: '100%', height: '100%' }) {
     let player;
     if (fileExtension === 'flv' || camerakind == 'flv') {
@@ -405,27 +472,22 @@ export function useCamera() {
     return player;
   }
 
-  function removeCamera() {
-    if (webRtcServer.length > 0) {
-      webRtcServer.forEach((item) => {
-        item.disconnect();
-      });
-      webRtcServer = [];
+  function removeCamera(playerRef) {
+    if (playerRef.value) {
+      render(null, playerRef.value);
     }
-    playerDoms.forEach((dom) => {
-      dom?.remove();
-    });
-    playerList.forEach((player) => {
-      if (player) {
-        if (player.destroy) player.destroy();
-        if (player.dispose) player.dispose();
-        if (player.remove) player.remove();
+  }
+  function removeCameraRef(playerRef, isShowPlayerRef) {
+    return new Promise((resolve) => {
+      isShowPlayerRef.value = false;
+      if (playerRef.value) {
+        render(null, playerRef.value);
       }
-      player = null;
+      nextTick(() => {
+        isShowPlayerRef.value = true;
+        resolve(null);
+      });
     });
-    videoParentDomList.length = 0;
-    playerDoms.length = 0;
-    playerList.length = 0;
   }
 
   return {
@@ -435,5 +497,6 @@ export function useCamera() {
     deviceCameraInit,
     removeCamera,
     getPlayer,
+    removeCameraRef,
   };
 }

+ 260 - 260
src/layouts/default/header/index.vue

@@ -68,281 +68,281 @@
   </div>
 </template>
 <script lang="ts">
-import { defineComponent, unref, computed, ref, onMounted, toRaw } from 'vue';
-import { useGlobSetting } from '/@/hooks/setting';
-import { propTypes } from '/@/utils/propTypes';
-
-import { Layout } from 'ant-design-vue';
-import { AppLogo } from '/@/components/Application';
-import LayoutMenu from '../menu/index.vue';
-import LayoutTrigger from '../trigger/index.vue';
-
-import { AppSearch } from '/@/components/Application';
-
-import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
-import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
-import { useRootSetting } from '/@/hooks/setting/useRootSetting';
-
-import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
-import { SettingButtonPositionEnum } from '/@/enums/appEnum';
-import { AppLocalePicker } from '/@/components/Application';
-
-import { UserDropDown, LayoutBreadcrumb, FullScreen, Notify, ErrorAction, LockScreen } from './components';
-import { useAppInject } from '/@/hooks/web/useAppInject';
-import { useDesign } from '/@/hooks/web/useDesign';
-
-import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
-import { useLocale } from '/@/locales/useLocale';
-
-import WeatherBroadcast from './components/weatherBroadcast.vue';
-import VoiceBroadcast from './components/VoiceBroadcast.vue';
-import VoiceBroadcastGsd from './components/VoiceBroadcastGsd.vue';
-import AIChat from '/@/components/AIChat/MiniChat.vue';
-
-import LoginSelect from '/@/views/sys/login/LoginSelect.vue';
-import { useUserStore } from '/@/store/modules/user';
-import { useRouter } from 'vue-router';
-
-import { noHeadeLink } from '../layout.data';
-import { usePermission } from '/@/hooks/web/usePermission';
-
-export default defineComponent({
-  name: 'LayoutHeader',
-  components: {
-    Header: Layout.Header,
-    AppLogo,
-    LayoutTrigger,
-    LayoutBreadcrumb,
-    LayoutMenu,
-    UserDropDown,
-    AppLocalePicker,
-    FullScreen,
-    Notify,
-    AppSearch,
-    ErrorAction,
-    LockScreen,
-    LoginSelect,
-    VoiceBroadcast,
-    AIChat,
-    VoiceBroadcastGsd,
-    WeatherBroadcast,
-    SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue'), {
-      loading: true,
-    }),
-  },
-  props: {
-    fixed: propTypes.bool,
-  },
-  setup(props) {
-    const { prefixCls } = useDesign('layout-header');
-    const userStore = useUserStore();
-    const { currentRoute } = useRouter();
-    console.log(currentRoute);
-    const { hasPermission } = usePermission();
-    const isShowQy = VENT_PARAM['isShowQy'];
-    const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting();
-    const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition } = useRootSetting();
-    const { title, sysOrgCode, homePath } = useGlobSetting();
-    const {
-      getHeaderTheme,
-      getShowFullScreen,
-      getShowNotice,
-      getShowContent,
-      getShowBread,
-      getShowHeaderLogo,
-      getShowHeader,
-      getShowSearch,
-      getUseLockPage,
-      getShowBreadTitle,
-      getShowFullHeaderRef,
-    } = useHeaderSetting();
-
-    const { getShowLocalePicker } = useLocale();
-
-    const { getIsMobile } = useAppInject();
-
-    const getHeaderClass = computed(() => {
-      const theme = unref(getHeaderTheme);
-      return [
-        prefixCls,
-        {
-          [`${prefixCls}--fixed`]: props.fixed,
-          [`${prefixCls}--mobile`]: unref(getIsMobile),
-          [`${prefixCls}--${theme}`]: theme,
-        },
-      ];
-    });
-
-    const getShowFullHeader = computed(() => {
-      const route = unref(currentRoute);
-      return getShowFullHeaderRef && route.path.startsWith('/micro-');
-    });
-
-    const getShowSetting = computed(() => {
-      if (!unref(getShowSettingButton)) {
-        return false;
+  import { defineComponent, unref, computed, ref, onMounted, toRaw } from 'vue';
+  import { useGlobSetting } from '/@/hooks/setting';
+  import { propTypes } from '/@/utils/propTypes';
+
+  import { Layout } from 'ant-design-vue';
+  import { AppLogo } from '/@/components/Application';
+  import LayoutMenu from '../menu/index.vue';
+  import LayoutTrigger from '../trigger/index.vue';
+
+  import { AppSearch } from '/@/components/Application';
+
+  import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+
+  import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
+  import { SettingButtonPositionEnum } from '/@/enums/appEnum';
+  import { AppLocalePicker } from '/@/components/Application';
+
+  import { UserDropDown, LayoutBreadcrumb, FullScreen, Notify, ErrorAction, LockScreen } from './components';
+  import { useAppInject } from '/@/hooks/web/useAppInject';
+  import { useDesign } from '/@/hooks/web/useDesign';
+
+  import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
+  import { useLocale } from '/@/locales/useLocale';
+
+  import WeatherBroadcast from './components/weatherBroadcast.vue';
+  import VoiceBroadcast from './components/VoiceBroadcast.vue';
+  import VoiceBroadcastGsd from './components/VoiceBroadcastGsd.vue';
+  import AIChat from '/@/components/AIChat/MiniChat.vue';
+
+  import LoginSelect from '/@/views/sys/login/LoginSelect.vue';
+  import { useUserStore } from '/@/store/modules/user';
+  import { useRouter } from 'vue-router';
+
+  import { noHeadeLink } from '../layout.data';
+  import { usePermission } from '/@/hooks/web/usePermission';
+
+  export default defineComponent({
+    name: 'LayoutHeader',
+    components: {
+      Header: Layout.Header,
+      AppLogo,
+      LayoutTrigger,
+      LayoutBreadcrumb,
+      LayoutMenu,
+      UserDropDown,
+      AppLocalePicker,
+      FullScreen,
+      Notify,
+      AppSearch,
+      ErrorAction,
+      LockScreen,
+      LoginSelect,
+      VoiceBroadcast,
+      AIChat,
+      VoiceBroadcastGsd,
+      WeatherBroadcast,
+      SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue'), {
+        loading: true,
+      }),
+    },
+    props: {
+      fixed: propTypes.bool,
+    },
+    setup(props) {
+      const { prefixCls } = useDesign('layout-header');
+      const userStore = useUserStore();
+      const { currentRoute } = useRouter();
+      console.log(currentRoute);
+      const { hasPermission } = usePermission();
+      const isShowQy = VENT_PARAM['isShowQy'];
+      const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting();
+      const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition } = useRootSetting();
+      const { title, sysOrgCode, homePath } = useGlobSetting();
+      const {
+        getHeaderTheme,
+        getShowFullScreen,
+        getShowNotice,
+        getShowContent,
+        getShowBread,
+        getShowHeaderLogo,
+        getShowHeader,
+        getShowSearch,
+        getUseLockPage,
+        getShowBreadTitle,
+        getShowFullHeaderRef,
+      } = useHeaderSetting();
+
+      const { getShowLocalePicker } = useLocale();
+
+      const { getIsMobile } = useAppInject();
+
+      const getHeaderClass = computed(() => {
+        const theme = unref(getHeaderTheme);
+        return [
+          prefixCls,
+          {
+            [`${prefixCls}--fixed`]: props.fixed,
+            [`${prefixCls}--mobile`]: unref(getIsMobile),
+            [`${prefixCls}--${theme}`]: theme,
+          },
+        ];
+      });
+
+      const getShowFullHeader = computed(() => {
+        const route = unref(currentRoute);
+        return getShowFullHeaderRef && route.path.startsWith('/micro-');
+      });
+
+      const getShowSetting = computed(() => {
+        if (!unref(getShowSettingButton)) {
+          return false;
+        }
+        const settingButtonPosition = unref(getSettingButtonPosition);
+
+        if (settingButtonPosition === SettingButtonPositionEnum.AUTO) {
+          return unref(getShowHeader);
+        }
+        return settingButtonPosition === SettingButtonPositionEnum.HEADER;
+      });
+
+      const getLogoWidth = computed(() => {
+        if (!unref(getIsMixMode) || unref(getIsMobile)) {
+          return {};
+        }
+        const width = unref(getMenuWidth) < 180 ? 180 : unref(getMenuWidth);
+        return { width: `${width}px` };
+      });
+
+      const getSplitType = computed(() => {
+        return unref(getSplit) ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE;
+      });
+
+      const getMenuMode = computed(() => {
+        return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null;
+      });
+
+      // /**
+      //  * 首页多租户部门弹窗逻辑
+      //  */
+      const loginSelectRef = ref();
+      const portValue = ref('');
+      portValue.value = window.location.port;
+      function showLoginSelect() {
+        //update-begin---author:liusq  Date:20220101  for:判断登录进来是否需要弹窗选择租户----
+        //判断是否是登陆进来
+        const loginInfo = toRaw(userStore.getLoginInfo) || {};
+        if (!!loginInfo.isLogin) {
+          loginSelectRef.value.show(loginInfo);
+        }
+        //update-end---author:liusq  Date:20220101  for:判断登录进来是否需要弹窗选择租户----
       }
-      const settingButtonPosition = unref(getSettingButtonPosition);
 
-      if (settingButtonPosition === SettingButtonPositionEnum.AUTO) {
-        return unref(getShowHeader);
+      function loginSelectOk() {
+        console.log('成功。。。。。');
       }
-      return settingButtonPosition === SettingButtonPositionEnum.HEADER;
-    });
 
-    const getLogoWidth = computed(() => {
-      if (!unref(getIsMixMode) || unref(getIsMobile)) {
-        return {};
-      }
-      const width = unref(getMenuWidth) < 180 ? 180 : unref(getMenuWidth);
-      return { width: `${width}px` };
-    });
-
-    const getSplitType = computed(() => {
-      return unref(getSplit) ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE;
-    });
-
-    const getMenuMode = computed(() => {
-      return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null;
-    });
-
-    // /**
-    //  * 首页多租户部门弹窗逻辑
-    //  */
-    const loginSelectRef = ref();
-    const portValue = ref('');
-    portValue.value = window.location.port;
-    function showLoginSelect() {
-      //update-begin---author:liusq  Date:20220101  for:判断登录进来是否需要弹窗选择租户----
-      //判断是否是登陆进来
-      const loginInfo = toRaw(userStore.getLoginInfo) || {};
-      if (!!loginInfo.isLogin) {
-        loginSelectRef.value.show(loginInfo);
-      }
-      //update-end---author:liusq  Date:20220101  for:判断登录进来是否需要弹窗选择租户----
-    }
+      // 用户下拉框应该在以下情况中隐藏:
+      // 1. 本页面是由其他页面的 iframe 嵌入的页面
+      const showUserDropdown = computed(() => {
+        return window.self === window.top;
+      });
 
-    function loginSelectOk() {
-      console.log('成功。。。。。');
-    }
+      onMounted(() => {
+        showLoginSelect();
+      });
 
-    // 用户下拉框应该在以下情况中隐藏:
-    // 1. 本页面是由其他页面的 iframe 嵌入的页面
-    const showUserDropdown = computed(() => {
-      return window.self === window.top;
-    });
-
-    onMounted(() => {
-      showLoginSelect();
-    });
-
-    return {
-      portValue,
-      prefixCls,
-      getHeaderClass,
-      getShowHeaderLogo,
-      getHeaderTheme,
-      getShowHeaderTrigger,
-      getIsMobile,
-      getShowBreadTitle,
-      getShowBread,
-      getShowContent,
-      getSplitType,
-      getSplit,
-      getMenuMode,
-      getShowTopMenu,
-      getShowLocalePicker,
-      getShowFullScreen,
-      getShowNotice,
-      getUseErrorHandle,
-      getLogoWidth,
-      getIsMixSidebar,
-      getShowSettingButton,
-      getShowSetting,
-      getShowSearch,
-      getUseLockPage,
-      loginSelectOk,
-      loginSelectRef,
-      currentRoute,
-      title,
-      getShowFullHeader,
-      noHeadeLink,
-      showUserDropdown,
-      sysOrgCode,
-      homePath,
-      hasPermission,
-      isShowQy,
-    };
-  },
-});
+      return {
+        portValue,
+        prefixCls,
+        getHeaderClass,
+        getShowHeaderLogo,
+        getHeaderTheme,
+        getShowHeaderTrigger,
+        getIsMobile,
+        getShowBreadTitle,
+        getShowBread,
+        getShowContent,
+        getSplitType,
+        getSplit,
+        getMenuMode,
+        getShowTopMenu,
+        getShowLocalePicker,
+        getShowFullScreen,
+        getShowNotice,
+        getUseErrorHandle,
+        getLogoWidth,
+        getIsMixSidebar,
+        getShowSettingButton,
+        getShowSetting,
+        getShowSearch,
+        getUseLockPage,
+        loginSelectOk,
+        loginSelectRef,
+        currentRoute,
+        title,
+        getShowFullHeader,
+        noHeadeLink,
+        showUserDropdown,
+        sysOrgCode,
+        homePath,
+        hasPermission,
+        isShowQy,
+      };
+    },
+  });
 </script>
 <style lang="less">
-@import './index.less';
-//update-begin---author:scott ---date:2022-09-30  for:默认隐藏顶部菜单面包屑-----------
-//顶部欢迎语展示样式
-@prefix-cls: ~'@{namespace}-layout-header';
-
-.@{prefix-cls} {
-  display: flex;
-  padding: 0 8px;
-  // align-items: center;
-
-  .headerIntroductionClass {
-    margin-right: 4px;
-    margin-bottom: 2px;
-    border-bottom: 0px;
-    border-left: 0px;
-  }
+  @import './index.less';
+  //update-begin---author:scott ---date:2022-09-30  for:默认隐藏顶部菜单面包屑-----------
+  //顶部欢迎语展示样式
+  @prefix-cls: ~'@{namespace}-layout-header';
+
+  .@{prefix-cls} {
+    display: flex;
+    padding: 0 8px;
+    // align-items: center;
 
-  &--light {
     .headerIntroductionClass {
-      color: @breadcrumb-item-normal-color;
+      margin-right: 4px;
+      margin-bottom: 2px;
+      border-bottom: 0px;
+      border-left: 0px;
     }
-  }
 
-  &--dark {
-    .headerIntroductionClass {
-      color: rgba(255, 255, 255, 0.6);
+    &--light {
+      .headerIntroductionClass {
+        color: @breadcrumb-item-normal-color;
+      }
     }
 
-    .anticon {
-      color: rgba(255, 255, 255, 0.8);
+    &--dark {
+      .headerIntroductionClass {
+        color: rgba(255, 255, 255, 0.6);
+      }
+
+      .anticon {
+        color: rgba(255, 255, 255, 0.8);
+      }
     }
+
+    //update-end---author:scott ---date::2022-09-30  for:默认隐藏顶部菜单面包屑--------------
+  }
+
+  // background: linear-gradient(#003f77, #0a134c);
+  //   // background: linear-gradient(#02050c 0%, #03114c 100%);
+  //   // border: none;
+  //   border-bottom: 1px solid #81aabf01;
+  //   padding-bottom: 2px;
+  //   box-shadow: 0 0 20px #44caff55 inset;
+  .normal-header {
+    height: 52px !important;
+    line-height: 52px !important;
+    background: var(--vent-header-bg-color) !important;
+    // background: linear-gradient(#005177,#0a344c) !important;
+    border-bottom: 1px solid #81aabf01 !important;
+    padding-bottom: 2px !important;
+    box-shadow: 0 0 20px #44caff55 inset !important;
+    padding: 0 8px !important;
   }
 
-  //update-end---author:scott ---date::2022-09-30  for:默认隐藏顶部菜单面包屑--------------
-}
-
-// background: linear-gradient(#003f77, #0a134c);
-//   // background: linear-gradient(#02050c 0%, #03114c 100%);
-//   // border: none;
-//   border-bottom: 1px solid #81aabf01;
-//   padding-bottom: 2px;
-//   box-shadow: 0 0 20px #44caff55 inset;
-.normal-header {
-  height: 52px !important;
-  line-height: 52px !important;
-  background: var(--vent-header-bg-color) !important;
-  // background: linear-gradient(#005177,#0a344c) !important;
-  border-bottom: 1px solid #81aabf01 !important;
-  padding-bottom: 2px !important;
-  box-shadow: 0 0 20px #44caff55 inset !important;
-  padding: 0 8px !important;
-}
-
-.no-header {
-  height: 0px !important;
-  display: none !important;
-}
-.zaihai-header {
-  position: absolute;
-  background: transparent !important;
-}
-
-.header-nav-title {
-  background-image: linear-gradient(#ffffff 50%, #60f4ff);
-  -webkit-background-clip: text;
-  color: transparent;
-  font-weight: 600;
-}
+  .no-header {
+    height: 0px !important;
+    display: none !important;
+  }
+  .zaihai-header {
+    position: absolute;
+    background: transparent !important;
+  }
+
+  .header-nav-title {
+    background-image: linear-gradient(#ffffff 50%, #60f4ff);
+    -webkit-background-clip: text;
+    color: transparent;
+    font-weight: 600;
+  }
 </style>

+ 140 - 2
src/views/vent/home/configurable/fireTS.vue

@@ -70,12 +70,15 @@
     <!-- <Three3D :modal-name="modalName" /> -->
     <!-- 工作面二维图像 -->
     <div class="workFace-content">
-      <div class="workFace-bg"></div>
+      <div class="workFace-bg">
+        <canvas class="canvas-box" ref="canvas"></canvas>
+        <div style="font-size: 30px; margin-left: 50px; margin-top: 120px">{{ fireSgWarnInfo.workFaceName }}</div>
+      </div>
     </div>
   </div>
 </template>
 <script lang="ts" setup>
-  import { computed, onMounted, onUnmounted, ref } from 'vue';
+  import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
   import { useInitConfigs, useInitPage } from './hooks/useInit';
   import { getAlarmRecord, getDeviceSys } from './configurable.api';
   // import { testConfigTSFire } from './configurable.data.tashan';
@@ -85,6 +88,7 @@
 
   const { title = '采空区无线监测预警及防灭火平台' } = useGlobSetting();
   const { data, updateData, mainTitle } = useInitPage(title);
+  const canvas = ref<HTMLCanvasElement>();
   // import Three3D from './components/three3D.vue';
 
   // const modalName = ref('workFace11');
@@ -96,6 +100,7 @@
   let interval: ReturnType<typeof setInterval> | undefined;
   const commonTitle = '实时监测与预警';
   const fireSgWarnInfo = ref({
+    workFaceName: '',
     aqfxLevelStr: '-',
     nyAlarmLevel: 0,
     nyAlarmLevelStr: '-',
@@ -105,6 +110,13 @@
     sgZbAlarmNum: 0,
     yjjbLevelStr: '-',
   });
+  const ckqData = reactive({
+    A: 0, // A
+    B: 0, // B
+    C: 0, // C
+    D: 0, // D
+    len: 0, // 总米数
+  });
 
   onMounted(() => {
     let alarmLogData = [];
@@ -219,6 +231,16 @@
         chartData: chartData,
       };
     }
+
+    if (processedData.sysInfo) {
+      ckqData.A = Number(processedData.sysInfo.meterCount1);
+      ckqData.B = Number(processedData.sysInfo.meterCount2);
+      ckqData.C = Number(processedData.sysInfo.meterCount3);
+      ckqData.D = Number(processedData.sysInfo.meterCount4);
+      ckqData.len = Number(processedData.sysInfo.meterCount5);
+      fireSgWarnInfo.value.workFaceName = processedData.sysInfo.goafName;
+    }
+
     return processedData;
   };
   interface BundletubeHistoryItem {
@@ -262,6 +284,114 @@
   };
   // 最终生成的图表数据
   const chartData = ref<ChartDataItem[]>([]);
+
+  function drawCanvas(A, B, D, C, len) {
+    if (canvas.value && A && B && C && D && len) {
+      const ctx = canvas.value.getContext('2d');
+      if (!ctx) return;
+
+      // 矩形尺寸
+      let a = canvas.value.width; // 长(宽度)
+      let b = canvas.value.height; // 宽(高度)
+
+      const ratio = len / a;
+
+      // 起始位置(居中绘制)
+      const offsetX = 0;
+      const offsetY = 0;
+
+      // 定义上下边上的点(x 坐标)
+      // 假设 A 和 D 对齐,B 和 C 对齐
+      const xA = A / ratio; // A 在上边的 x
+      const xB = B / ratio; // B 在上边的 x
+      const xD = C / ratio; // D 在下边的 x(与 A 对齐)
+      const xC = D / ratio; // C 在下边的 x(与 B 对齐)
+
+      // 颜色
+      const color1 = '#FF000099'; // 左
+      const color2 = '#FCFF0099'; // 右
+      const color3 = '#1700FF99'; // 中
+
+      // 坐标转换函数:从逻辑坐标 → canvas 像素坐标
+      function pt(x, y) {
+        return [offsetX + x, offsetY + y];
+      }
+
+      // 绘制填充四边形
+      function fillQuad(p1, p2, p3, p4, color) {
+        if (!ctx) return;
+        ctx.beginPath();
+        ctx.moveTo(p1[0], p1[1]);
+        ctx.lineTo(p2[0], p2[1]);
+        ctx.lineTo(p3[0], p3[1]);
+        ctx.lineTo(p4[0], p4[1]);
+        ctx.closePath();
+        ctx.fillStyle = color;
+        ctx.fill();
+      }
+
+      // === 绘制三个区域 ===
+      fillQuad(pt(0, 0), pt(xA, 0), pt(xD, b), pt(0, b), color1);
+      fillQuad(pt(xA, 0), pt(xB, 0), pt(xC, b), pt(xD, b), color2);
+      fillQuad(pt(xB, 0), pt(a, 0), pt(a, b), pt(xC, b), color3);
+
+      // === 标记四个点 ===
+      const points = [
+        { name: 'A', x: xA, x1: A, y: 0 },
+        { name: 'B', x: xB, x1: B, y: 0 },
+        { name: 'C', x: xC, x1: D, y: b },
+        { name: 'D', x: xD, x1: C, y: b },
+      ];
+
+      ctx.font = '10px Arial';
+      ctx.textAlign = 'center'; // 文本水平居中对齐
+      ctx.textBaseline = 'middle'; // 文本垂直居中对齐,方便调整上下位置
+
+      points.forEach((ptInfo) => {
+        const label = `${ptInfo.x1}m`;
+
+        // 根据点在上边还是下边调整文字位置
+        let textY =
+          ptInfo.name === 'A' || ptInfo.name === 'B'
+            ? 0 + 10 // 上边:文字在点上方
+            : 150 - 10; // 下边:文字在点下方
+
+        ctx.fillStyle = '#000';
+        ctx.fillText(label, offsetX + ptInfo.x, textY);
+      });
+
+      // 绘制三带名称
+      const texts = [
+        { text: '散热带', x: xA / 2, y: 50 },
+        { text: '氧化带', x: xA + (xB - xA) / 2, y: 50 },
+        { text: '窒息带', x: xB + (b - xD) / 2, y: 50 },
+      ];
+
+      ctx.font = '12px Arial'; // 设置字体大小和样式
+      ctx.fillStyle = '#fff'; // 设置文本颜色
+
+      texts.forEach((textInfo) => {
+        const chars = textInfo.text.split(''); // 将字符串拆分为单个字符
+        const charWidth = 14; // 估计每个字符的大致宽度
+
+        chars.forEach((char, index) => {
+          // 计算每个字符的x和y坐标,使得字符垂直排列
+          const xOffset = (-(chars.length - 1) * charWidth) / 2; // 中心偏移量,使文本垂直居中对齐
+          ctx.fillText(char, textInfo.x + xOffset, textInfo.y + index * charWidth);
+        });
+      });
+    }
+  }
+  watch(
+    ckqData,
+    (newVal, oldVal) => {
+      const { A, B, C, D, len } = newVal;
+      drawCanvas(A, B, C, D, len);
+    },
+    { deep: true }
+  );
+  onMounted(() => {});
+
   // 数据处理函数
   onUnmounted(() => {
     clearInterval(interval);
@@ -469,6 +599,14 @@
         background: url('/src/assets/images/home-container/configurable/tashanhome/workFace.png') no-repeat center;
         background-size: 100% 100%;
         z-index: 0;
+        .canvas-box {
+          width: 610px;
+          height: 315px;
+          position: absolute;
+          top: 0px;
+          left: 390px;
+          z-index: 9999;
+        }
       }
     }
   }

+ 21 - 4
src/views/vent/monitorManager/AllDeviceMonitor/index.vue

@@ -49,7 +49,7 @@
               :isShowActionColumn="true"
               :dataSource="dataSource"
               design-scope="gate-monitor"
-              @selectRow="getSelectRow"
+              @select-row="getSelectRow"
               :scroll="{ y: scroll.y - 40 }"
               title="风门监测"
               :isShowPagination="true"
@@ -159,7 +159,23 @@
       </dv-border-box8>
     </div>
   </div>
-  <div ref="playerRef" style="z-index: 999; position: absolute; top: 100px; right: 15px; width: 300px; height: 280px; margin: auto"> </div>
+  <div
+    v-if="renderPlayer"
+    ref="playerRef"
+    style="
+      z-index: 999;
+      position: absolute;
+      top: 100px;
+      right: 15px;
+      width: 100%;
+      height: 800px;
+      margin: auto;
+      pointer-events: none;
+      overflow-y: auto;
+      flex-direction: column;
+    "
+  >
+  </div>
   <HandleModal
     v-if="!globalConfig?.simulatedPassword"
     :modal-is-show="modalIsShow"
@@ -199,6 +215,7 @@
   const MonitorDataTable = ref();
   let contrlValue = '';
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const deviceType = ref('door');
   // const deviceType = ref('firedoor');
   const activeKey = ref('1'); // tab
@@ -309,7 +326,7 @@
     Object.assign(selectData, initData, selectRow, baseData);
     doorDeviceState = 1; //记录设备状态,为了与下一次监测数据做比较
     isdoorOpenRunning = true; //开关门动作是否在进行
-    await getCamera(selectRow.deviceID, playerRef.value);
+    await getCamera(selectRow.deviceID, playerRef, renderPlayer);
   }
 
   function playAnimation(handlerState, data: any = null) {
@@ -427,11 +444,11 @@
   });
 
   onBeforeUnmount(() => {
+    removeCamera(playerRef);
     getDeviceBaseList();
   });
 
   onUnmounted(() => {
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 5 - 5
src/views/vent/monitorManager/compressor/components/nitrogenHome_bd.vue

@@ -298,7 +298,7 @@
               />
             </template>
           </ventBox1>
-          <div ref="playerRef" class="player-box vent-margin-t-10"></div>
+          <div v-if="renderPlayer" ref="playerRef" class="player-box vent-margin-t-10"></div>
         </div>
       </div>
     </div>
@@ -336,6 +336,7 @@
   const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
   const modalType = ref(''); // 模态框内容显示类型,设备操作类型
   const modalIsShow = ref<boolean>(false); // 是否显示模态框
+  const renderPlayer = ref(true);
   const loading = ref(true);
   let kzParam = reactive<any>({
     data: {},
@@ -923,13 +924,13 @@
     () => props.deviceId,
     async (deviceId) => {
       if (deviceId) {
-        await getCamera(deviceId, playerRef.value);
+        await getCamera(deviceId, playerRef, renderPlayer);
       }
     }
   );
 
   onBeforeUnmount(() => {
-    removeCamera();
+    removeCamera(playerRef);
   });
 
   onMounted(async () => {
@@ -937,12 +938,11 @@
     await mountedThree().then(() => {
       loading.value = false;
     });
-    await getCamera(props.deviceId, playerRef.value);
+    await getCamera(props.deviceId, playerRef, renderPlayer);
   });
 
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 5 - 5
src/views/vent/monitorManager/compressor/components/nitrogenHome_bet.vue

@@ -254,7 +254,7 @@
             </template>
           </ventBox1>
         </div>
-        <div ref="playerRef" class="player-box"></div>
+        <div v-if="renderPlayer" ref="playerRef" class="player-box"></div>
       </div>
     </div>
   </div>
@@ -289,6 +289,7 @@
       require: true,
     },
   });
+  const renderPlayer = ref(true);
   const playerRef = ref();
   const refresh = ref(false);
   const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
@@ -496,13 +497,13 @@
     () => props.deviceId,
     async (deviceId) => {
       if (deviceId) {
-        await getCamera(deviceId, playerRef.value);
+        await getCamera(deviceId, playerRef, renderPlayer);
         getDataSource(props.deviceId, true);
       }
     }
   );
   onBeforeUnmount(() => {
-    removeCamera();
+    removeCamera(playerRef);
   });
 
   onMounted(async () => {
@@ -510,12 +511,11 @@
     await mountedThree().then(() => {
       loading.value = false;
     });
-    await getCamera(props.deviceId, playerRef.value);
+    await getCamera(deviceId, playerRef, renderPlayer);
   });
 
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 3 - 2
src/views/vent/monitorManager/compressor/components/nitrogenHome_blt.vue

@@ -120,7 +120,7 @@
         </div>
       </div>
     </div>
-    <div ref="playerRef" class="player-box"></div>
+    <div v-if="renderPlayer" ref="playerRef" class="player-box"></div>
   </div>
   <HandleModal :modal-is-show="modalIsShow" :modal-title="modalTitle" :modal-type="modalType" @handle-ok="handleOK" @handle-cancel="handleCancel" />
 </template>
@@ -153,6 +153,7 @@
       require: true,
     },
   });
+  const renderPlayer = ref(true);
   const playerRef = ref();
   const { getCamera, removeCamera } = useCamera();
   const refresh = ref(false);
@@ -362,7 +363,7 @@
     () => props.deviceId,
     async (deviceId) => {
       if (deviceId) {
-        await getCamera(deviceId, playerRef.value, 'nitrogen');
+        await getCamera(deviceId, playerRef, renderPlayer, 'nitrogen');
         getDataSource(props.deviceId, true);
       }
     }

+ 7 - 6
src/views/vent/monitorManager/compressor/components/nitrogenHome_dltj.vue

@@ -179,7 +179,7 @@
           </ventBox1>
         </div>
       </div>
-      <div ref="playerRef" class="player-box" style=""> </div>
+      <div v-if="renderPlayer" ref="playerRef" class="player-box" style=""> </div>
     </div>
     <div class="modal-monitor">
       <fourBorderBg id="downWindMonitor">
@@ -212,6 +212,7 @@
   import { message } from 'ant-design-vue';
   import lodash from 'lodash';
   import { getTableHeaderColumns } from '/@/hooks/web/useWebColumns';
+  import { onBeforeUnmount } from 'vue';
 
   const globalConfig = inject('globalConfig');
 
@@ -225,7 +226,7 @@
       require: true,
     },
   });
-
+  const renderPlayer = ref(true);
   const playerRef = ref();
   const refresh = ref(false);
   const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
@@ -724,7 +725,7 @@
   watch(
     () => props.deviceId,
     async (deviceId) => {
-      if (deviceId) await getCamera(deviceId, playerRef.value);
+      if (deviceId) await getCamera(deviceId, playerRef, renderPlayer);
     }
   );
 
@@ -733,16 +734,16 @@
     await mountedThree().then(() => {
       loading.value = false;
     });
-    // await getCamera(props.deviceId, playerRef.value);
   });
-
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
+  });
   onUnmounted(() => {
     destroy();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;
     }
-    removeCamera();
   });
 </script>
 

+ 7 - 6
src/views/vent/monitorManager/compressor/components/nitrogenHome_hlg.vue

@@ -179,7 +179,7 @@
           </ventBox1>
         </div>
       </div>
-      <div ref="playerRef" class="player-box" style=""> </div>
+      <div v-if="renderPlayer" ref="playerRef" class="player-box" style=""> </div>
     </div>
     <div class="modal-monitor">
       <fourBorderBg id="downWindMonitor">
@@ -198,7 +198,7 @@
   </div>
 </template>
 <script lang="ts" setup name="nitrogenHome">
-  import { onMounted, onUnmounted, ref, watch, reactive, defineProps, nextTick, inject, unref } from 'vue';
+  import { onMounted, onUnmounted, ref, watch, reactive, defineProps, nextTick, inject, onBeforeUnmount } from 'vue';
   import ventBox1 from '/@/components/vent/ventBox1.vue';
   import fourBorderBg from '../../../comment/components/fourBorderBg.vue';
   import { mountedThree, destroy, setModelType } from '../nitrogen.threejs';
@@ -225,7 +225,7 @@
       require: true,
     },
   });
-
+  const renderPlayer = ref(true);
   const playerRef = ref();
   const refresh = ref(false);
   const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
@@ -704,16 +704,17 @@
     await mountedThree().then(() => {
       loading.value = false;
     });
-    await getCamera(props.deviceId, playerRef.value);
+    await getCamera(props.deviceId, playerRef, renderPlayer);
+  });
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
   });
-
   onUnmounted(() => {
     destroy();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;
     }
-    removeCamera();
   });
 </script>
 

+ 3 - 2
src/views/vent/monitorManager/compressor/components/nitrogenHome_ln.vue

@@ -126,7 +126,7 @@
               />
             </template>
           </ventBox1>
-          <div ref="playerRef" style="height: auto; width: 100%; margin-top: 10px"></div>
+          <div v-if="renderPlayer" ref="playerRef" style="height: auto; width: 100%; margin-top: 10px"></div>
         </div>
       </div>
     </div>
@@ -159,6 +159,7 @@
       require: true,
     },
   });
+  const renderPlayer = ref(true);
   const playerRef = ref();
   const refresh = ref(false);
   const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
@@ -335,7 +336,7 @@
     await mountedThree().then(() => {
       loading.value = false;
     });
-    await getCamera(props.deviceId, playerRef.value);
+    await getCamera(props.deviceId, playerRef, renderPlayer);
   });
 
   onUnmounted(() => {

+ 7 - 5
src/views/vent/monitorManager/compressor/components/nitrogenHome_lt.vue

@@ -83,12 +83,12 @@
           </ventBox1>
         </div>
       </div>
-      <div ref="playerRef" style="position: absolute; height: auto; width: 100%; left: 1600px; top: 480px; z-index: 9999"></div>
+      <div v-if="renderPlayer" ref="playerRef" style="position: absolute; height: auto; width: 100%; left: 1600px; top: 480px; z-index: 9999"></div>
     </div>
   </div>
 </template>
 <script lang="ts" setup name="nitrogenHome">
-  import { onMounted, onUnmounted, ref, watch, reactive, defineProps, nextTick, inject } from 'vue';
+  import { onMounted, onUnmounted, ref, watch, reactive, defineProps, nextTick, onBeforeUnmount } from 'vue';
   import ventBox1 from '/@/components/vent/ventBox1.vue';
   import fourBorderBg from '../../../comment/components/fourBorderBg.vue';
   import { mountedThree, destroy, setModelType } from '../nitrogen.threejs';
@@ -110,6 +110,7 @@
       require: true,
     },
   });
+  const renderPlayer = ref(true);
   const playerRef = ref();
   const refresh = ref(false);
   const loading = ref(true);
@@ -207,12 +208,13 @@
     await mountedThree().then(() => {
       loading.value = false;
     });
-    await getCamera(props.deviceId, playerRef.value, 'nitrogen');
+    await getCamera(props.deviceId, playerRef, renderPlayer, 'nitrogen');
+  });
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
   });
-
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 3 - 42
src/views/vent/monitorManager/deviceCameraMonitor/index.vue

@@ -56,23 +56,7 @@
         </template>
       </div>
     </div>
-    <div ref="playerRef" class="player-List">
-      <template v-if="cameraAddrs.length > 0">
-        <div v-for="(item, index) in cameraAddrs" :key="index" class="player-box">
-          <div class="player-name">{{ item.name }}</div>
-          <div style="padding-top: 3px">
-            <template v-if="item.addr.startsWith('rtsp://')">
-              <video :id="`video${index}`" muted autoplay></video>
-              <div class="click-box" @dblclick="goFullScreen(`video${index}`)"></div>
-            </template>
-            <template v-else>
-              <div :id="'player' + index"></div>
-            </template>
-          </div>
-        </div>
-      </template>
-      <div class="no-player" v-else>暂无视频</div>
-    </div>
+    <div v-if="renderPlayer" ref="playerRef" class="player-List"> </div>
     <div
       class="tabs-box bottom-tabs-box"
       :class="{ 'table-hide': !tableShow, 'table-show': tableShow }"
@@ -362,6 +346,7 @@
   const { hasPermission } = usePermission();
   const { getCamera, removeCamera, getPlayer } = useCamera();
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const router = useRouter();
   const monitorTable = ref();
   const historyTable = ref();
@@ -618,31 +603,7 @@
     selectRowIndex.value = index;
     selectData.value = selectRow;
 
-    cameraAddrs.value = await getCamera(selectRow.deviceID, playerRef.value, null, true);
-    if (cameraAddrs.value && cameraAddrs.value.length > 0) {
-      nextTick(async () => {
-        for (let i = 0; i < cameraAddrs.value.length; i++) {
-          const item = cameraAddrs.value[i];
-          const fileExtension = item.addr.split('.').pop();
-          if (item['addr'] && item['addr'].includes('0.0.0.0')) {
-            item['addr'] = item['addr'].replace('0.0.0.0', window.location.hostname);
-          }
-          if (item['addr'].startsWith('rtsp://')) {
-            const videoDom = document.getElementById('video' + i);
-            if (videoDom) {
-              const server = new window['WebRtcStreamer'](
-                videoDom,
-                VUE_APP_URL.webRtcUrl.startsWith('/') ? location.protocol + VUE_APP_URL.webRtcUrl : VUE_APP_URL.webRtcUrl
-              );
-              await server.connect(item['addr']);
-              videoDom.play();
-            }
-          } else {
-            getPlayer(fileExtension, 'player' + i, item['devicekind'], item['addr'], item['cameraRate'], { width: 755, height: 490 });
-          }
-        }
-      });
-    }
+    await getCamera(selectRow.deviceID, playerRef, renderPlayer, null, true);
   }
 
   const playAnimation = (title, flag) => {

+ 3 - 0
src/views/vent/monitorManager/deviceMonitor/components/device/index.vue

@@ -523,6 +523,9 @@
                       record.deviceConnect_str
                     }}</a-tag>
                   </template>
+                  <a-tag v-if="column.dataIndex === 'runRoRecondition'" :color="record.runRoRecondition == '1' ? '#f00' : 'green'">{{
+                    record.runRoRecondition == '1' ? '检修' : '运行'
+                  }}</a-tag>
                   <a-tag v-if="column.dataIndex === 'warnFlag'" :color="record.warnFlag == 0 ? 'green' : record.warnFlag == 1 ? '#FF5812' : 'gray'">
                     {{ record.warnFlag == 0 ? '正常' : record.warnFlag == 1 ? '报警' : record.warnFlag == 2 ? '断开' : '未监测' }}</a-tag
                   >

+ 8 - 5
src/views/vent/monitorManager/fanLocalMonitor/index.vue

@@ -371,8 +371,9 @@
     </div>
   </div>
   <div
-    v-show="showPlay"
+    v-if="renderPlayer"
     ref="playerRef"
+    v-show="showPlay"
     :class="{ 'to-right': rightColumns.length < 1 || leftColumns.length < 1, 'to-no-right': rightColumns.length > 0 && leftColumns.length > 0 }"
     style="z-index: 999; position: absolute; top: 100px; right: 15px; width: 100%; height: 100%; margin: auto; pointer-events: none"
   >
@@ -556,6 +557,7 @@
     computed,
     shallowReactive,
     shallowRef,
+    onBeforeUnmount,
   } from 'vue';
   // import BarSingle from '../../../../components/chart/BarSingle.vue';
   import FanDeviceEcharts from '../comment/FanDeviceEcharts.vue';
@@ -782,6 +784,7 @@
   const MonitorDataTable = ref();
   const modalSensor = ref(null);
   const loading = ref(false);
+  const renderPlayer = ref(true);
   const activeKey = ref('1');
   const player1 = ref();
   const modalIsShow = ref<boolean>(false); // 是否显示模态框
@@ -1150,8 +1153,7 @@
       }
       setModelType(mainModelType.value, modalType.value, fanDualArray.value);
     });
-
-    await getCamera(id, playerRef.value);
+    await getCamera(id, playerRef, renderPlayer);
     return;
   }
 
@@ -1623,10 +1625,11 @@
     }
     document.body.addEventListener('mousedown', addPlayVideo, false);
   });
-
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
+  });
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 7 - 4
src/views/vent/monitorManager/fanLocalMonitor1/index.vue

@@ -301,6 +301,7 @@
     </div>
   </div>
   <div
+    v-if="renderPlayer"
     v-show="activeBtn == 'fanLocal-ssjc'"
     ref="playerRef"
     :class="{ 'to-right': rightColumns.length < 1 || leftColumns.length < 1, 'to-no-right': rightColumns.length > 0 && leftColumns.length > 0 }"
@@ -439,7 +440,7 @@
 
 <script setup lang="ts">
   import { ExclamationCircleFilled, ArrowRightOutlined } from '@ant-design/icons-vue';
-  import { onBeforeMount, ref, watch, onMounted, nextTick, defineAsyncComponent, reactive, onUnmounted, inject, unref } from 'vue';
+  import { onBeforeMount, ref, watch, onMounted, nextTick, defineAsyncComponent, reactive, onUnmounted, inject, unref, onBeforeUnmount } from 'vue';
   import BottomMenu from '/@/views/vent/comment/components/bottomMenu.vue';
   import fanlocalHistory from './components/fanlocal-history.vue';
   import fanlocalWarnHistory from './components/fanlocal-warn-history.vue';
@@ -625,6 +626,7 @@
   const deviceTypeDicts = getDictItemsByCode('fanlocaltype');
   const gasWarningVal = ref(0.6); // 瓦斯最大报警值
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const modalSensor = ref(null);
   const loading = ref(false);
   const player1 = ref();
@@ -1201,13 +1203,14 @@
         addCssText();
       });
     });
-    await getCamera(deviceId.value, playerRef.value);
+    await getCamera(deviceId.value, playerRef, renderPlayer);
     document.body.addEventListener('mousedown', addPlayVideo, false);
   });
-
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
+  });
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 4 - 2
src/views/vent/monitorManager/fireDoorMonitor/index.vue

@@ -177,6 +177,7 @@
     </div>
   </div>
   <div
+    v-if="renderPlayer"
     :class="{ 'dfc-box': sysOrgCode == 'bcjtdfsmk' }"
     ref="playerRef"
     style="
@@ -252,6 +253,7 @@
   const MonitorDataTable = ref();
   let contrlValue = '';
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const deviceType = ref('door');
   // const deviceType = ref('gate');
   // const deviceType = ref('firedoor');
@@ -377,7 +379,7 @@
     await setModelType(type);
     loading.value = false;
 
-    await getCamera(selectRow.deviceID, playerRef.value);
+    await getCamera(selectRow.deviceID, playerRef, renderPlayer);
   }
 
   const setControl = (flag, title, value?) => {
@@ -489,10 +491,10 @@
 
   onBeforeUnmount(() => {
     getDeviceBaseList();
+    removeCamera(playerRef);
   });
 
   onUnmounted(() => {
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 172 - 159
src/views/vent/monitorManager/footageMonitor/components/moduleCommon.vue

@@ -7,10 +7,10 @@
         </template>
         <template #container>
           <div class="container-t">
-            <ModuleHead  :timeDate="timeDate" @changeTime="changeTime"></ModuleHead>
+            <ModuleHead :timeDate="timeDate" @change-time="changeTime" />
           </div>
           <div class="container-b">
-            <SingLineArea :option="option" :chartData="chartData" height="280px"></SingLineArea>
+            <SingLineArea :option="option" :chartData="chartData" height="280px" />
           </div>
         </template>
       </ventBox1>
@@ -20,17 +20,27 @@
         </template>
         <template #container>
           <div class="container-t">
-            <ModuleHead :isShowSelect="true" :menuData="gasMenuList" :timeDate="timeDate" :devLabel="gasDevLabel" @changeMenu="changeGasMenu"
-              @changeTime="changeTime"></ModuleHead>
+            <ModuleHead
+              :isShowSelect="true"
+              :menuData="gasMenuList"
+              :timeDate="timeDate"
+              :devLabel="gasDevLabel"
+              @change-menu="changeGasMenu"
+              @change-time="changeTime"
+            />
           </div>
           <div class="container-b">
-            <BarAndLine class="echarts-line" :xAxisPropType="xAxisPropType" :dataSource="gasList" height="280px"
-              :chartsColumns="chartsColumns" :option="echatsOption" />
+            <BarAndLine
+              class="echarts-line"
+              :xAxisPropType="xAxisPropType"
+              :dataSource="gasList"
+              height="280px"
+              :chartsColumns="chartsColumns"
+              :option="echatsOption"
+            />
           </div>
-
         </template>
       </ventBox1>
-
     </div>
     <div class="lr right-box">
       <div class="item-box sensor-container">
@@ -39,180 +49,183 @@
             <div>工作面进尺及瓦斯涌出量</div>
           </template>
           <template #container>
-            <SingLineArea :option="optionGas" :chartData="chartGasData" height="280px"></SingLineArea>
+            <SingLineArea :option="optionGas" :chartData="chartGasData" height="280px" />
           </template>
         </ventBox1>
-
       </div>
     </div>
   </div>
-
 </template>
 
 <script setup lang="ts">
-
-import {  ref, onMounted, onUnmounted, reactive, defineProps,watchEffect } from 'vue';
-import ventBox1 from '/@/components/vent/ventBox1.vue'
-import SingLineArea from '@/components/chart/SingLineArea.vue'
-import BarAndLine from '@/components/chart/BarAndLine.vue';
-import ModuleHead from './moduleHead.vue'
-import { option, gasMenuList, chartsColumns, echatsOption, optionGas } from '../footage.data'
-import { list, getCurveGraphData } from '../footage.api';
-import dayjs from 'dayjs';
-
-let props=defineProps({
-  optionValue:{
-    type:String,
-    default:''
+  import { ref, onMounted, onUnmounted, reactive, defineProps, watchEffect } from 'vue';
+  import ventBox1 from '/@/components/vent/ventBox1.vue';
+  import SingLineArea from '@/components/chart/SingLineArea.vue';
+  import BarAndLine from '@/components/chart/BarAndLine.vue';
+  import ModuleHead from './moduleHead.vue';
+  import { option, gasMenuList, chartsColumns, echatsOption, optionGas } from '../footage.data';
+  import { list, getCurveGraphData } from '../footage.api';
+  import dayjs from 'dayjs';
+
+  let props = defineProps({
+    optionValue: {
+      type: String,
+      default: '',
+    },
+  });
+  let paramData = ref<any[]>([]);
+  let timeDate = reactive({
+    startTime: dayjs().subtract(7, 'day').format('YYYY-MM-DD'),
+    endTime: dayjs().format('YYYY-MM-DD'),
+  });
+  let gasDevLabel = ref('T0(上隅角)');
+  let xAxisPropType = ref('ttime');
+  let chartData = ref<any[]>([]);
+  let gasList = ref<any[]>([]);
+  let chartGasData = ref<any[]>([]);
+
+  // https获取监测数据
+  let timer: null | NodeJS.Timeout = null;
+  function getMonitor(flag?) {
+    if (Object.prototype.toString.call(timer) === '[object Null]') {
+      timer = setTimeout(
+        async () => {
+          getCurveGraphDataList();
+          if (timer) {
+            timer = null;
+          }
+          await getMonitor();
+        },
+        flag ? 0 : 5000
+      );
+    }
   }
-})
-let paramData = ref<any[]>([])
-let timeDate = reactive({
-  startTime: dayjs().subtract(7, 'day').format('YYYY-MM-DD'),
-  endTime: dayjs().format('YYYY-MM-DD'),
-})
-let gasDevLabel = ref('T0(上隅角)')
-let xAxisPropType = ref('ttime');
-let chartData = ref<any[]>([]);
-let gasList = ref<any[]>([]);
-let chartGasData = ref<any[]>([])
-
-// https获取监测数据
-let timer: null | NodeJS.Timeout = null;
-function getMonitor(flag?) {
-  if (Object.prototype.toString.call(timer) === '[object Null]') {
-    timer = setTimeout(async () => {
-      getCurveGraphDataList()
-      if (timer) {
-        timer = null;
-      }
-      await getMonitor();
-    }, flag ? 0 : 5000);
+  async function getCurveGraphDataList() {
+    let res = await getCurveGraphData({ devId: props.optionValue, startTime: timeDate.startTime, endTime: timeDate.endTime });
+    if (res.length) {
+      paramData.value = res;
+      chartData.value =
+        res.map((el) => {
+          return {
+            name: el.fteTime,
+            val: el.planFte,
+            val1: el.ateFte,
+            val2: el.sumFte,
+          };
+        }) || [];
+      gasList.value =
+        res.map((d) => {
+          let data = JSON.parse(d.gasInfo);
+          return {
+            ttime: d.fteTime,
+            wsnd: data.gasVal0Ave,
+            xdnd: data.gasVal0Rel,
+            jdnd: data.gasVal0Abs,
+            m3Ave: data.m3Ave,
+          };
+        }) || [];
+      chartGasData.value =
+        res.map((c) => {
+          let data = JSON.parse(c.gasInfo);
+          return {
+            name: c.fteTime,
+            val: c.ateFte,
+            val1: data.gasVal0Ave,
+          };
+        }) || [];
+    }
   }
-};
-async function getCurveGraphDataList() {
-  let res = await getCurveGraphData({ devId: props.optionValue, startTime: timeDate.startTime, endTime: timeDate.endTime })
-  if (res.length) {
-    paramData.value = res
-    chartData.value = res.map(el => {
-      return {
-        name: el.fteTime,
-        val: el.planFte,
-        val1: el.ateFte,
-        val2: el.sumFte
-      }
-    }) || []
-    gasList.value = res.map(d => {
-      let data = JSON.parse(d.gasInfo)
-      return {
-        ttime: d.fteTime,
-        wsnd: data.gasVal0Ave,
-        xdnd: data.gasVal0Rel,
-        jdnd: data.gasVal0Abs,
-        m3Ave: data.m3Ave
-      }
-    }) || []
-    chartGasData.value = res.map(c => {
-      let data = JSON.parse(c.gasInfo)
-      return {
-        name: c.fteTime,
-        val: c.ateFte,
-        val1: data.gasVal0Ave,
-      }
-    }) || []
-  }
-}
 
-function changeGasMenu(param) {
-  gasDevLabel.value = param.label
-  switch (param.label) {
-    case 'T0(上隅角)':
-      gasList.value = paramData.value.map(d => {
-        let data = JSON.parse(d.gasInfo)
-        return {
-          ttime: d.fteTime,
-          wsnd: data.gasVal0Ave,
-          xdnd: data.gasVal0Rel,
-          jdnd: data.gasVal0Abs,
-          m3Ave: data.m3Ave
-        }
-      })
-      break;
-    case 'T1(回风流距工作面割煤线10m范围内)':
-      gasList.value = paramData.value.map(d => {
-        let data = JSON.parse(d.gasInfo)
-        return {
-          ttime: d.fteTime,
-          wsnd: data.gasVal1Ave,
-          xdnd: data.gasVal1Rel,
-          jdnd: data.gasVal1Abs,
-          m3Ave: data.m3Ave
-        }
-      })
-      break;
-    case 'T2(回风绕道口10~15m处)':
-      gasList.value = paramData.value.map(d => {
-        let data = JSON.parse(d.gasInfo)
-        return {
-          ttime: d.fteTime,
-          wsnd: data.gasVal2Ave,
-          xdnd: data.gasVal2Rel,
-          jdnd: data.gasVal2Abs,
-          m3Ave: data.m3Ave
-        }
-      })
-      break;
+  function changeGasMenu(param) {
+    gasDevLabel.value = param.label;
+    switch (param.label) {
+      case 'T0(上隅角)':
+        gasList.value = paramData.value.map((d) => {
+          let data = JSON.parse(d.gasInfo);
+          return {
+            ttime: d.fteTime,
+            wsnd: data.gasVal0Ave,
+            xdnd: data.gasVal0Rel,
+            jdnd: data.gasVal0Abs,
+            m3Ave: data.m3Ave,
+          };
+        });
+        break;
+      case 'T1(回风流距工作面割煤线10m范围内)':
+        gasList.value = paramData.value.map((d) => {
+          let data = JSON.parse(d.gasInfo);
+          return {
+            ttime: d.fteTime,
+            wsnd: data.gasVal1Ave,
+            xdnd: data.gasVal1Rel,
+            jdnd: data.gasVal1Abs,
+            m3Ave: data.m3Ave,
+          };
+        });
+        break;
+      case 'T2(回风绕道口10~15m处)':
+        gasList.value = paramData.value.map((d) => {
+          let data = JSON.parse(d.gasInfo);
+          return {
+            ttime: d.fteTime,
+            wsnd: data.gasVal2Ave,
+            xdnd: data.gasVal2Rel,
+            jdnd: data.gasVal2Abs,
+            m3Ave: data.m3Ave,
+          };
+        });
+        break;
+    }
   }
-}
-function changeTime(param) {
-  console.log(param, 'param===')
-  timeDate.startTime = param.startTime
-  timeDate.endTime = param.endTime
-  getCurveGraphDataList()
-}
-
-watchEffect(()=>{
-  props.optionValue &&  getCurveGraphDataList()
-})
-onMounted(async () => {
-  await getMonitor(true)
-});
-onUnmounted(() => {
-  if (timer) {
-    clearTimeout(timer);
-    timer = undefined;
+  function changeTime(param) {
+    console.log(param, 'param===');
+    timeDate.startTime = param.startTime;
+    timeDate.endTime = param.endTime;
+    getCurveGraphDataList();
   }
-});
+
+  watchEffect(() => {
+    props.optionValue && getCurveGraphDataList();
+  });
+  onMounted(async () => {
+    await getMonitor(true);
+  });
+  onUnmounted(() => {
+    if (timer) {
+      clearTimeout(timer);
+      timer = undefined;
+    }
+  });
 </script>
 
 <style lang="less">
-@import '/@/design/vent/modal.less';
+  @import '/@/design/vent/modal.less';
 
-.@{ventSpace}-select-dropdown {
-  background: #ffffff !important;
-  border-bottom: 1px solid rgba(236, 236, 236, 0.4);
-  backdrop-filter: blur(10px);
+  .@{ventSpace}-select-dropdown {
+    background: #ffffff !important;
+    border-bottom: 1px solid rgba(236, 236, 236, 0.4);
+    backdrop-filter: blur(10px);
 
-  .@{ventSpace}-select-item-option-selected,
-  .@{ventSpace}-select-item-option-active {
-    background-color: #ffffff33 !important;
-  }
+    .@{ventSpace}-select-item-option-selected,
+    .@{ventSpace}-select-item-option-active {
+      background-color: #ffffff33 !important;
+    }
 
-  .@{ventSpace}-select-item:hover {
-    background-color: #ffffff33 !important;
+    .@{ventSpace}-select-item:hover {
+      background-color: #ffffff33 !important;
+    }
   }
-}
 </style>
 
 <style lang="less" scoped>
-@import '../../comment/less/workFace.less';
-@ventSpace: zxm;
+  @import '../../comment/less/workFace.less';
+  @ventSpace: zxm;
 
-.left-box {
-  margin-top: 0 !important;
-}
+  .left-box {
+    margin-top: 0 !important;
+  }
 
-.right-box {
-  margin-top: 0 !important;
-}
+  .right-box {
+    margin-top: 0 !important;
+  }
 </style>

+ 57 - 43
src/views/vent/monitorManager/footageMonitor/index.vue

@@ -1,63 +1,77 @@
 <template>
   <div class="footage-box">
-    <customHeader :fieldNames="{ label: 'label', value: 'value', options: 'children' }" :options='options'
-      @change="getSelectRow" :optionValue="optionValue">进尺与瓦斯涌出分析</customHeader>
+    <customHeader
+      :fieldNames="{ label: 'label', value: 'value', options: 'children' }"
+      :options="options"
+      @change="getSelectRow"
+      :optionValue="optionValue"
+      >进尺与瓦斯涌出分析</customHeader
+    >
     <div class="box-container">
-      <ModuleCommon :optionValue="optionValue"></ModuleCommon>
+      <ModuleCommon :optionValue="optionValue" />
     </div>
+    <!-- 巷道模型组件 -->
+    <VentModal />
   </div>
 </template>
 
 <script setup lang="ts">
-import { onBeforeMount, ref, onMounted, onUnmounted, nextTick, provide } from 'vue';
-import customHeader from '/@/components/vent/customHeader.vue';
-import ModuleCommon from './components/moduleCommon.vue';
-import { list } from './footage.api'
-import { useRouter,useRoute } from 'vue-router';
+  import { onBeforeMount, ref, onMounted, onUnmounted, nextTick, provide, onBeforeUnmount } from 'vue';
+  import customHeader from '/@/components/vent/customHeader.vue';
+  import ModuleCommon from './components/moduleCommon.vue';
+  import { list } from './footage.api';
+  import { useRouter, useRoute } from 'vue-router';
+  import VentModal from '/@/components/vent/micro/ventModal.vue';
+  import { unmountMicroApps } from '/@/qiankun';
 
-let options = ref<any[]>([])
-let optionValue = ref('')
-let router = useRouter(); //路由
-const currentRoute = useRoute();
+  let options = ref<any[]>([]);
+  let optionValue = ref('');
+  let router = useRouter(); //路由
+  const currentRoute = useRoute();
 
-async function getSysDataSource() {
-  let res = await list({ devicekind: 'footageGas' })
-  options.value = res.records.map(el => {
-    return {
-      label: el.strname,
-      value: el.id,
+  async function getSysDataSource() {
+    let res = await list({ devicekind: 'footageGas' });
+    options.value =
+      res.records.map((el) => {
+        return {
+          label: el.strname,
+          value: el.id,
+        };
+      }) || [];
+    if (!optionValue.value) {
+      optionValue.value = options.value[0]['value'];
+      nextTick(() => {
+        getSelectRow(optionValue.value);
+      });
     }
-  }) || []
-  if (!optionValue.value) {
-    optionValue.value = options.value[0]['value']
-    router.push(`${currentRoute.path}?deviceType=default&sysID=${ optionValue.value}`);
   }
-};
 
-// 切换检测数据
-async function getSelectRow(deviceID) {
-  console.log(deviceID, '选项切换')
-  optionValue.value = deviceID
-  router.push(`${currentRoute.path}?deviceType=default&sysID=${ optionValue.value}`);
-}
-
-onMounted(async () => {
+  // 切换检测数据
+  async function getSelectRow(deviceID) {
+    console.log(deviceID, '选项切换');
+    optionValue.value = deviceID;
+    router.push(`${currentRoute.path}?deviceType=default&sysID=${optionValue.value}`);
+  }
 
-  await getSysDataSource()
-})
+  onMounted(async () => {
+    await getSysDataSource();
+  });
+  onBeforeUnmount(() => {
+    unmountMicroApps(['/micro-vent-3dModal']);
+  });
 </script>
 <style lang="less" scoped>
-@import '/@/design/theme.less';
-@ventSpace: zxm;
+  @import '/@/design/theme.less';
+  @ventSpace: zxm;
 
-.footage-box {
-  position: relative;
-  width: 100%;
-  height: 100%;
+  .footage-box {
+    position: relative;
+    width: 100%;
+    height: 100%;
 
-  .box-container {
-    margin-top: 80px;
-    height: calc(100% - 80px);
+    .box-container {
+      margin-top: 80px;
+      height: calc(100% - 80px);
+    }
   }
-}
 </style>

+ 6 - 7
src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHome.vue

@@ -203,7 +203,7 @@
       <LivePlayer id="fm-player1" style="height: 250px;" ref="player1" :videoUrl="flvURL1()" muted live loading controls />
     </div> -->
     </div>
-    <div ref="playerRef" class="player-box"></div>
+    <div v-if="renderPlayer" ref="playerRef" class="player-box"></div>
   </div>
   <DetailModal @register="register" :device-type="deviceType" :device-id="deviceId" />
   <PasswordModal
@@ -260,6 +260,7 @@
   const passwordModalIsShow = ref(false);
   const handlerType = ref('');
   const playerRef = ref();
+  const renderPlayer = ref(true);
 
   // 监测数据
   const selectData = ref({
@@ -392,7 +393,7 @@
   watch(
     () => props.deviceType,
     () => {
-      removeCamera();
+      removeCamera(playerRef);
       nextTick(async () => {
         if (props.deviceType == 'pump_over') {
           setModelType('gasPump');
@@ -405,23 +406,21 @@
   watch(
     () => props.deviceId,
     async (deviceID) => {
-      removeCamera();
-      if (deviceID) await getCamera(deviceID, playerRef.value);
+      removeCamera(playerRef);
+      if (deviceID) await getCamera(deviceID, playerRef, renderPlayer);
     }
   );
 
   onMounted(async () => {
     timer = null;
     await getMonitor(true);
-    // if (selectData && selectData['deviceID']) await getCamera(selectData['deviceID'], playerRef.value);
   });
 
   onBeforeUnmount(() => {
-    removeCamera();
+    removeCamera(playerRef);
   });
 
   onUnmounted(() => {
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 0 - 8
src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeBD.vue

@@ -176,19 +176,11 @@
       });
     }
   );
-  // watch(
-  //   () => props.deviceId,
-  //   async (deviceID) => {
-  //     removeCamera();
-  //     if (deviceID) await getCamera(deviceID, playerRef.value);
-  //   }
-  // );
 
   onMounted(async () => {
     timer = null;
     await getMonitor(true);
     fetchConfigs('gasPumpMonitor');
-    // if (selectData && selectData['deviceID']) await getCamera(selectData['deviceID'], playerRef.value);
   });
 
   // onBeforeUnmount(() => {

+ 6 - 7
src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeBet.vue

@@ -141,7 +141,7 @@
       <LivePlayer id="fm-player1" style="height: 250px;" ref="player1" :videoUrl="flvURL1()" muted live loading controls />
     </div> -->
     </div>
-    <div ref="playerRef" class="player-box"></div>
+    <div v-if="renderPlayer" ref="playerRef" class="player-box"></div>
   </div>
   <DetailModal @register="register" :device-type="deviceType" :device-id="deviceId" />
   <PasswordModal
@@ -194,6 +194,7 @@
   const passwordModalIsShow = ref(false);
   const handlerType = ref('');
   const playerRef = ref();
+  const renderPlayer = ref(true);
 
   // 监测数据
   const selectData = ref({
@@ -326,7 +327,7 @@
   watch(
     () => props.deviceType,
     () => {
-      removeCamera();
+      removeCamera(playerRef);
       getMonitorData(props.deviceType).then((data) => {
         deviceProperty.value = data;
 
@@ -341,23 +342,21 @@
   watch(
     () => props.deviceId,
     async (deviceID) => {
-      removeCamera();
-      if (deviceID) await getCamera(deviceID, playerRef.value);
+      removeCamera(playerRef);
+      if (deviceID) await getCamera(deviceID, playerRef, renderPlayer);
     }
   );
 
   onMounted(async () => {
     timer = null;
     await getMonitor(true);
-    // if (selectData && selectData['deviceID']) await getCamera(selectData['deviceID'], playerRef.value);
   });
 
   onBeforeUnmount(() => {
-    removeCamera();
+    removeCamera(playerRef);
   });
 
   onUnmounted(() => {
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 6 - 6
src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeCC.vue

@@ -182,7 +182,7 @@
         </ventBox1>
       </div>
     </div>
-    <div ref="playerRef" class="player-box"></div>
+    <div v-if="renderPlayer" ref="playerRef" class="player-box"></div>
   </div>
   <DetailModal @register="register" :device-type="deviceType" :device-id="deviceId" />
   <PasswordModal
@@ -229,6 +229,7 @@
   const passwordModalIsShow = ref(false);
   const handlerType = ref('');
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const deviceProperty = ref({
     leftMonitor: [] as devicePropertyType[],
     rightMonitor: [] as devicePropertyType[],
@@ -333,23 +334,22 @@
   watch(
     () => props.deviceId,
     async (deviceID) => {
-      removeCamera();
-      if (deviceID) await getCamera(deviceID, playerRef.value);
+      removeCamera(playerRef);
+      if (deviceID) await getCamera(deviceID, playerRef, renderPlayer);
     }
   );
 
   onMounted(async () => {
     timer = null;
     await getMonitor(true);
-    if (selectData.value && selectData.value['deviceID']) await getCamera(selectData.value['deviceID'], playerRef.value);
+    if (selectData.value && selectData.value['deviceID']) await getCamera(selectData.value['deviceID'], playerRef, renderPlayer);
   });
 
   onBeforeUnmount(() => {
-    removeCamera();
+    removeCamera(playerRef);
   });
 
   onUnmounted(() => {
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 373 - 371
src/views/vent/monitorManager/gasPumpMonitor/components/gasPumpHomeCopy.vue

@@ -16,101 +16,115 @@
     <div class="elementContent" style="position: absolute; display: none">
       <div v-for="(tag, index) in modelMonitorTags" :key="index" :id="tag.domId" class="modal-monitor-box">
         <div class="title">{{ tag.title }}</div>
-        <div v-if="tag.type == 'sign'" class="signal-round"
-          :class="{ 'signal-round-gry': selectData[tag.code] != 1, 'signal-round-run': selectData[tag.code] == 1 }">
+        <div
+          v-if="tag.type == 'sign'"
+          class="signal-round"
+          :class="{ 'signal-round-gry': selectData[tag.code] != 1, 'signal-round-run': selectData[tag.code] == 1 }"
+        >
         </div>
         <div v-else class="value">{{ selectData[tag.code] }}</div>
       </div>
     </div>
     <div v-if="selectData['netStatus'] == 0" class="device-state">网络断开</div>
-  
+
     <!-- 可配置模块 -->
-    <ModuleCommon v-for="cfg in configs" :key="cfg.deviceType" :show-style="cfg.showStyle" :module-data="cfg.moduleData"
-      :module-name="cfg.moduleName" :device-type="cfg.deviceType" :data="selectData" :visible="true" />
+    <ModuleCommon
+      v-for="cfg in configs"
+      :key="cfg.deviceType"
+      :show-style="cfg.showStyle"
+      :module-data="cfg.moduleData"
+      :module-name="cfg.moduleName"
+      :device-type="cfg.deviceType"
+      :data="selectData"
+      :visible="true"
+    />
 
     <div class="lr left-box"></div>
     <div class="lr right-box"></div>
     <div ref="playerRef" class="player-box"></div>
   </div>
-  <PasswordModal :modal-is-show="passwordModalIsShow" modal-title="密码检验" :modal-type="handlerType" @handle-ok="handleOK"
-    @handle-cancel="handleCancel" />
+  <PasswordModal
+    :modal-is-show="passwordModalIsShow"
+    modal-title="密码检验"
+    :modal-type="handlerType"
+    @handle-ok="handleOK"
+    @handle-cancel="handleCancel"
+  />
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, onUnmounted, reactive, defineProps, watch, inject, nextTick, onBeforeUnmount, computed } from 'vue';
-// import ventBox1 from '/@/components/vent/ventBox1.vue';
-import { setModelType, playAnimate } from '../gasPump.threejs';
-// import ListItem from '@/views/vent/gas/components/list/listItem.vue';
-import {
-  getModelMonitorTags,
-} from '../gasPump.data';
-import { list } from '../gasPump.api';
-import { deviceControlApi } from '/@/api/vent/index';
-import PasswordModal from '../../comment/components/PasswordModal.vue';
-import { message } from 'ant-design-vue';
-// import fourBorderBg from '/@/components/vent/fourBorderBg.vue';
-// import { useCamera } from '/@/hooks/system/useCamera';
-import { useInitConfigs } from '../../../home/configurable/hooks/useInit';
- import ModuleCommon from '../../../home/configurable/components/ModuleCommon.vue';
- import { set } from 'lodash-es';
-
-const globalConfig = inject<any>('globalConfig');
-
-const props = defineProps({
-  deviceId: {
-    type: String,
-    require: true,
-  },
-  deviceType: {
-    type: String,
-    require: true,
-  },
-});
-
-const { configs: rawConfigs, fetchConfigs } = useInitConfigs();
-const configs = computed(() => {
-  return rawConfigs.value.filter((c) => c.deviceType == props.deviceId);
-});
-
-// const [register, { openModal }] = useModal();
-const modelMonitorTags = getModelMonitorTags();
-const loading = ref(false);
-const passwordModalIsShow = ref(false);
-const handlerType = ref('');
-const playerRef = ref();
-
-// 监测数据
-const selectData = ref({});
-
-// const { getCamera, removeCamera } = useCamera();
-
-// https获取监测数据
-let timer: null | NodeJS.Timeout = null;
-function getMonitor(flag?) {
-  if (Object.prototype.toString.call(timer) === '[object Null]') {
-    return new Promise((resolve) => {
-      timer = setTimeout(
-        async () => {
-          if (props.deviceId) {
-            const data = await getDataSource();
-            selectData.value = data;
-            playAnimate(data);
-            // Object.assign(selectData, data);
-          }
-          if (timer) {
-            timer = null;
-          }
-          resolve(null);
-          await getMonitor();
-          loading.value = false;
-        },
-        flag ? 0 : 1000
-      );
-    });
+  import { ref, onMounted, onUnmounted, reactive, defineProps, watch, inject, nextTick, onBeforeUnmount, computed } from 'vue';
+  // import ventBox1 from '/@/components/vent/ventBox1.vue';
+  import { setModelType, playAnimate } from '../gasPump.threejs';
+  // import ListItem from '@/views/vent/gas/components/list/listItem.vue';
+  import { getModelMonitorTags } from '../gasPump.data';
+  import { list } from '../gasPump.api';
+  import { deviceControlApi } from '/@/api/vent/index';
+  import PasswordModal from '../../comment/components/PasswordModal.vue';
+  import { message } from 'ant-design-vue';
+  // import fourBorderBg from '/@/components/vent/fourBorderBg.vue';
+  // import { useCamera } from '/@/hooks/system/useCamera';
+  import { useInitConfigs } from '../../../home/configurable/hooks/useInit';
+  import ModuleCommon from '../../../home/configurable/components/ModuleCommon.vue';
+  import { set } from 'lodash-es';
+
+  const globalConfig = inject<any>('globalConfig');
+
+  const props = defineProps({
+    deviceId: {
+      type: String,
+      require: true,
+    },
+    deviceType: {
+      type: String,
+      require: true,
+    },
+  });
+
+  const { configs: rawConfigs, fetchConfigs } = useInitConfigs();
+  const configs = computed(() => {
+    return rawConfigs.value.filter((c) => c.deviceType == props.deviceId);
+  });
+
+  // const [register, { openModal }] = useModal();
+  const modelMonitorTags = getModelMonitorTags();
+  const loading = ref(false);
+  const passwordModalIsShow = ref(false);
+  const handlerType = ref('');
+  const playerRef = ref();
+
+  // 监测数据
+  const selectData = ref({});
+
+  // const { getCamera, removeCamera } = useCamera();
+
+  // https获取监测数据
+  let timer: null | NodeJS.Timeout = null;
+  function getMonitor(flag?) {
+    if (Object.prototype.toString.call(timer) === '[object Null]') {
+      return new Promise((resolve) => {
+        timer = setTimeout(
+          async () => {
+            if (props.deviceId) {
+              const data = await getDataSource();
+              selectData.value = data;
+              playAnimate(data);
+              // Object.assign(selectData, data);
+            }
+            if (timer) {
+              timer = null;
+            }
+            resolve(null);
+            await getMonitor();
+            loading.value = false;
+          },
+          flag ? 0 : 1000
+        );
+      });
+    }
   }
-}
 
-async function getDataSource() {
+  async function getDataSource() {
     const res = await list({
       devicetype: 'gasdrainage',
       pagetype: 'normal',
@@ -123,115 +137,103 @@ async function getDataSource() {
     return result;
   }
 
-function handler(passWord, paramcode) {
-  let value = '';
-  if (paramcode == 'ykjdqh') {
-    value = selectData.value['ykjdqh'] == '1' ? '2' : '1';
+  function handler(passWord, paramcode) {
+    let value = '';
+    if (paramcode == 'ykjdqh') {
+      value = selectData.value['ykjdqh'] == '1' ? '2' : '1';
+    }
+    if (paramcode == 'jxmsqh') {
+      value = selectData.value['jxmsqh'] == '1' ? '2' : '1';
+    }
+    const data = {
+      deviceid: selectData.value['deviceID'],
+      devicetype: selectData.value['deviceType'],
+      paramcode: paramcode,
+      password: passWord,
+      value: value,
+    };
+    deviceControlApi(data)
+      .then((res) => {
+        if (globalConfig.History_Type == 'remote') {
+          message.success('指令已下发至生产管控平台成功!');
+        } else {
+          message.success('指令已下发成功!');
+        }
+      })
+      .catch((err) => {
+        message.success('控制异常');
+      });
   }
-  if (paramcode == 'jxmsqh') {
-    value = selectData.value['jxmsqh'] == '1' ? '2' : '1';
+
+  // function changeCtr(e) {
+  //   if (e == 1) {
+  //     // 就地
+  //     handlerType.value = 'jxmsqh';
+  //   } else if (e == 2) {
+  //     // 远程
+  //     handlerType.value = 'jxmsqh';
+  //   }
+  //   passwordModalIsShow.value = true;
+  // }
+
+  // function changeMode(e) {
+  //   if (e == 1) {
+  //     // 检修开
+  //     handlerType.value = 'ykjdqh';
+  //   } else if (e == 2) {
+  //     // 检修关
+  //     handlerType.value = 'ykjdqh';
+  //   }
+  //   passwordModalIsShow.value = true;
+  // }
+
+  // function handlerFn(paramcode) {
+  //   handlerType.value = paramcode;
+  //   passwordModalIsShow.value = true;
+  // }
+
+  function handleOK(passWord, handlerState) {
+    handler(passWord, handlerState);
+    passwordModalIsShow.value = false;
+    handlerType.value = '';
   }
-  const data = {
-    deviceid: selectData.value['deviceID'],
-    devicetype: selectData.value['deviceType'],
-    paramcode: paramcode,
-    password: passWord,
-    value: value,
-  };
-  deviceControlApi(data)
-    .then((res) => {
-      if (globalConfig.History_Type == 'remote') {
-        message.success('指令已下发至生产管控平台成功!');
-      } else {
-        message.success('指令已下发成功!');
-      }
-    })
-    .catch((err) => {
-      message.success('控制异常');
-    });
-}
-
-// function changeCtr(e) {
-//   if (e == 1) {
-//     // 就地
-//     handlerType.value = 'jxmsqh';
-//   } else if (e == 2) {
-//     // 远程
-//     handlerType.value = 'jxmsqh';
-//   }
-//   passwordModalIsShow.value = true;
-// }
-
-// function changeMode(e) {
-//   if (e == 1) {
-//     // 检修开
-//     handlerType.value = 'ykjdqh';
-//   } else if (e == 2) {
-//     // 检修关
-//     handlerType.value = 'ykjdqh';
-//   }
-//   passwordModalIsShow.value = true;
-// }
-
-// function handlerFn(paramcode) {
-//   handlerType.value = paramcode;
-//   passwordModalIsShow.value = true;
-// }
-
-function handleOK(passWord, handlerState) {
-  handler(passWord, handlerState);
-  passwordModalIsShow.value = false;
-  handlerType.value = '';
-}
-
-function handleCancel() {
-  passwordModalIsShow.value = false;
-  handlerType.value = '';
-}
-
-// 喷粉操作
-// function handlerDevice(code, data) { }
-
-watch(
-  () => props.deviceType,
-  () => {
-    // removeCamera();
-    nextTick(async () => {
-      if (props.deviceType == 'pump_over') {
-        setModelType('gasPump');
-      } else if (props.deviceType?.startsWith('pump_under') || props.deviceType == 'pump_n12m2pq') {
-        setModelType('gasPumpUnder');
-      }
-    });
+
+  function handleCancel() {
+    passwordModalIsShow.value = false;
+    handlerType.value = '';
   }
-);
-// watch(
-//   () => props.deviceId,
-//   async (deviceID) => {
-//     removeCamera();
-//     if (deviceID) await getCamera(deviceID, playerRef.value);
-//   }
-// );
-
-onMounted(async () => {
-  timer = null;
-  await getMonitor(true);
-  fetchConfigs('gasPumpMonitor');
-  // if (selectData && selectData['deviceID']) await getCamera(selectData['deviceID'], playerRef.value);
-});
-
-// onBeforeUnmount(() => {
-//   removeCamera();
-// });
-
-onUnmounted(() => {
-  // removeCamera();
-  if (timer) {
-    clearTimeout(timer);
+
+  // 喷粉操作
+  // function handlerDevice(code, data) { }
+
+  watch(
+    () => props.deviceType,
+    () => {
+      // removeCamera();
+      nextTick(async () => {
+        if (props.deviceType == 'pump_over') {
+          setModelType('gasPump');
+        } else if (props.deviceType?.startsWith('pump_under') || props.deviceType == 'pump_n12m2pq') {
+          setModelType('gasPumpUnder');
+        }
+      });
+    }
+  );
+
+  onMounted(async () => {
     timer = null;
-  }
-});
- // const rawConfigs = ref<Config[]>([
+    await getMonitor(true);
+    fetchConfigs('gasPumpMonitor');
+  });
+
+  onUnmounted(() => {
+    // removeCamera();
+    if (timer) {
+      clearTimeout(timer);
+      timer = null;
+    }
+  });
+  // const rawConfigs = ref<Config[]>([
   //   {
   //     deviceType: '1709773229143920641',
   //     moduleName: '瓦斯泵',
@@ -699,242 +701,242 @@ onUnmounted(() => {
   // ]);
 </script>
 <style lang="less" scoped>
-@import '/@/design/theme.less';
-@import '/@/design/vent/modal.less';
-@import '../../comment/less/workFace.less';
-@ventSpace: zxm;
+  @import '/@/design/theme.less';
+  @import '/@/design/vent/modal.less';
+  @import '../../comment/less/workFace.less';
+  @ventSpace: zxm;
 
-.elementContent {
-  :deep(.main-container) {
-    display: flex;
-    flex-wrap: wrap;
-    width: 690px;
-    padding: 10px 12px 10px 15px;
-    border: 1px solid #d3e1ff33;
-    background-color: #061c2a55;
-    box-shadow: 0 0 15px #3b567f55;
-    background-color: #38383833;
-
-    .gas-monitor-row {
+  .elementContent {
+    :deep(.main-container) {
       display: flex;
-      flex-direction: row;
       flex-wrap: wrap;
-      color: #fff;
-      line-height: 32px;
-
-      .title {
-        width: 250px;
-        color: #baeaff;
-      }
-
-      .value {
-        width: 80px;
-        color: #efae05;
+      width: 690px;
+      padding: 10px 12px 10px 15px;
+      border: 1px solid #d3e1ff33;
+      background-color: #061c2a55;
+      box-shadow: 0 0 15px #3b567f55;
+      background-color: #38383833;
+
+      .gas-monitor-row {
+        display: flex;
+        flex-direction: row;
+        flex-wrap: wrap;
+        color: #fff;
+        line-height: 32px;
+
+        .title {
+          width: 250px;
+          color: #baeaff;
+        }
+
+        .value {
+          width: 80px;
+          color: #efae05;
+        }
       }
     }
   }
-}
-
-.modal-monitor-box {
-  background-color: #000;
-  color: #fff;
-  padding: 0 5px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-
-  .title {
-    margin-right: 5px;
+
+  .modal-monitor-box {
+    background-color: #000;
+    color: #fff;
+    padding: 0 5px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    .title {
+      margin-right: 5px;
+    }
+
+    .signal-round {
+      margin-left: 5px;
+    }
+
+    .value {
+      width: 30px;
+      color: #efae05;
+    }
   }
 
-  .signal-round {
-    margin-left: 5px;
+  .device-state {
+    width: 100%;
+    position: absolute;
+    top: 20px;
+    color: #e90000;
+    display: flex;
+    justify-content: center;
+    font-size: 20px;
   }
 
-  .value {
-    width: 30px;
-    color: #efae05;
+  .lr {
+    margin-top: 0 !important;
   }
-}
-
-.device-state {
-  width: 100%;
-  position: absolute;
-  top: 20px;
-  color: #e90000;
-  display: flex;
-  justify-content: center;
-  font-size: 20px;
-}
-
-.lr {
-  margin-top: 0 !important;
-}
-
-.left-box {
-  width: 360px !important;
-  direction: rtl;
-  overflow-y: auto;
-  overflow-x: hidden;
-  height: calc(100% - 60px);
-  margin-top: 30px !important;
-
-  .left-container {
-    direction: ltr;
+
+  .left-box {
+    width: 360px !important;
+    direction: rtl;
+    overflow-y: auto;
+    overflow-x: hidden;
+    height: calc(100% - 60px);
+    margin-top: 30px !important;
+
+    .left-container {
+      direction: ltr;
+    }
   }
-}
-
-.right-box {
-  width: 350px !important;
-  overflow-y: auto;
-  overflow-x: hidden;
-
-  .environment-monitor {
-    .item {
-      flex: 1;
-      margin: 0 5px;
-
-      .title {
-        color: #7ae5ff;
-        text-align: center;
-        margin-bottom: 2px;
-      }
 
-      .num {
-        width: 100%;
-        height: 30px;
-        text-align: center;
-        border-top: 2px solid #50c8fc;
-        border-radius: 4px;
-        background-image: linear-gradient(#2e4d5955, #3780b499, #2e465955);
+  .right-box {
+    width: 350px !important;
+    overflow-y: auto;
+    overflow-x: hidden;
+
+    .environment-monitor {
+      .item {
+        flex: 1;
+        margin: 0 5px;
+
+        .title {
+          color: #7ae5ff;
+          text-align: center;
+          margin-bottom: 2px;
+        }
+
+        .num {
+          width: 100%;
+          height: 30px;
+          text-align: center;
+          border-top: 2px solid #50c8fc;
+          border-radius: 4px;
+          background-image: linear-gradient(#2e4d5955, #3780b499, #2e465955);
+        }
       }
     }
-  }
 
-  .pool-box {
-    width: 327px;
-    height: 65px;
-    padding: 0 5px;
-    background: url('/@/assets/images/vent/pump1.png') no-repeat;
-    background-size: cover;
-    background-origin: content-box;
-    margin-top: 2px;
+    .pool-box {
+      width: 327px;
+      height: 65px;
+      padding: 0 5px;
+      background: url('/@/assets/images/vent/pump1.png') no-repeat;
+      background-size: cover;
+      background-origin: content-box;
+      margin-top: 2px;
 
-    .num {
-      color: aqua;
-    }
+      .num {
+        color: aqua;
+      }
 
-    .center {
-      padding-right: 5px;
+      .center {
+        padding-right: 5px;
+      }
     }
   }
-}
-
-.player-box {
-  position: absolute;
-  height: 100%;
-  width: 100%;
-  padding: 0 20px 0 20px;
-  z-index: 9999;
-  display: flex;
-  align-items: end;
-  bottom: 80px;
-
-  :deep(#LivePlayerBox) {
-    display: flex;
-    justify-content: end;
-  }
-}
 
-.input-box {
-  width: calc(100%);
-  display: flex;
-  flex-direction: row !important;
-  flex-wrap: wrap !important;
-
-  .input-item {
-    width: calc(50% - 8px);
-    padding: 0 2px;
+  .player-box {
+    position: absolute;
+    height: 100%;
+    width: 100%;
+    padding: 0 20px 0 20px;
+    z-index: 9999;
+    display: flex;
+    align-items: end;
+    bottom: 80px;
 
-    &:nth-child(2n) {
-      margin-left: 4px;
+    :deep(#LivePlayerBox) {
+      display: flex;
+      justify-content: end;
     }
   }
-}
 
-.btn-group {
-  display: flex;
-  justify-content: space-around;
+  .input-box {
+    width: calc(100%);
+    display: flex;
+    flex-direction: row !important;
+    flex-wrap: wrap !important;
+
+    .input-item {
+      width: calc(50% - 8px);
+      padding: 0 2px;
 
-  .btn-item {
-    width: 82px;
-    text-align: center;
+      &:nth-child(2n) {
+        margin-left: 4px;
+      }
+    }
   }
-}
 
-.top-btn {
   .btn-group {
-    margin-bottom: 8px;
+    display: flex;
+    justify-content: space-around;
 
     .btn-item {
-      width: calc(50% - 16px);
-      margin: 0 4px;
+      width: 82px;
+      text-align: center;
     }
   }
 
-  .control-item {
-    margin-left: 10px;
-    margin-bottom: 8px;
-    display: flex;
+  .top-btn {
+    .btn-group {
+      margin-bottom: 8px;
 
-    .control-title {
-      width: 80px;
-      color: var(--vent-font-action-link);
+      .btn-item {
+        width: calc(50% - 16px);
+        margin: 0 4px;
+      }
     }
 
-    .control-container {
+    .control-item {
+      margin-left: 10px;
+      margin-bottom: 8px;
       display: flex;
+
+      .control-title {
+        width: 80px;
+        color: var(--vent-font-action-link);
+      }
+
+      .control-container {
+        display: flex;
+      }
     }
   }
-}
 
-.btn-box {
-  display: flex;
+  .btn-box {
+    display: flex;
 
-  .btn {
-    padding: 0 8px !important;
-    margin: 0 2px;
+    .btn {
+      padding: 0 8px !important;
+      margin: 0 2px;
+    }
   }
-}
 
-.state-header {
-  display: flex;
-  color: var(--vent-font-action-link);
+  .state-header {
+    display: flex;
+    color: var(--vent-font-action-link);
 
-  .header-item {
-    width: 25%;
-    text-align: center;
+    .header-item {
+      width: 25%;
+      text-align: center;
+    }
   }
-}
 
-.device-row {
-  display: flex;
-  margin-top: 10px;
+  .device-row {
+    display: flex;
+    margin-top: 10px;
 
-  .state {
-    width: 25%;
-    text-align: center;
-    font-size: 13px;
+    .state {
+      width: 25%;
+      text-align: center;
+      font-size: 13px;
+    }
   }
-}
 
-:deep(.@{ventSpace}-tabs-tabpane-active) {
-  overflow: auto;
-}
+  :deep(.@{ventSpace}-tabs-tabpane-active) {
+    overflow: auto;
+  }
 
-:deep(.list-item__background) {
-  background-image: linear-gradient(to right, #39deff15, #3977e500) !important;
-  line-height: 30px !important;
-  height: 30px !important;
-}
+  :deep(.list-item__background) {
+    background-image: linear-gradient(to right, #39deff15, #3977e500) !important;
+    line-height: 30px !important;
+    height: 30px !important;
+  }
 </style>

+ 5 - 6
src/views/vent/monitorManager/gateMonitor/gate.threejs.one.sp.ts

@@ -75,8 +75,8 @@ class FmSp1 {
     const screenDownText = VENT_PARAM['modalText']
       ? VENT_PARAM['modalText']
       : History_Type['type'] == 'remote'
-      ? `国能神东煤炭集团监制`
-      : '煤科通安(北京)智控科技有限公司研制';
+        ? `国能神东煤炭集团监制`
+        : '煤科通安(北京)智控科技有限公司研制';
 
     const screenDownTextX = 90 - (screenDownText.length - 11) * 6;
     const textArr = [
@@ -270,11 +270,10 @@ class FmSp1 {
   destroy() {
     if (this.model) {
       if (this.mixers) {
+        const fmSp1 = this.group.getObjectByName('FengMen_SiShanLing').getObjectByName('men1');
         this.mixers.uncacheClip(this.clipActionArr.frontDoor.getClip());
-        this.mixers.uncacheClip(this.clipActionArr.backDoor.getClip());
-        this.mixers.uncacheAction(this.clipActionArr.frontDoor.getClip(), this.group);
-        this.mixers.uncacheAction(this.clipActionArr.backDoor.getClip(), this.group);
-        this.mixers.uncacheRoot(this.group);
+        this.mixers.uncacheAction(this.clipActionArr.frontDoor.getClip(), fmSp1);
+        this.mixers.uncacheRoot(fmSp1);
 
         if (this.model.animations[0]) this.model.animations[0].tracks = [];
       }

+ 4 - 3
src/views/vent/monitorManager/gateMonitor/gate.threejs.three.hsw.ts

@@ -82,8 +82,8 @@ class FmHsw3 {
     const screenDownText = VENT_PARAM['modalText']
       ? VENT_PARAM['modalText']
       : History_Type['type'] == 'remote'
-      ? `国能神东煤炭集团监制`
-      : '煤科通安(北京)智控科技有限公司研制';
+        ? `国能神东煤炭集团监制`
+        : '煤科通安(北京)智控科技有限公司研制';
 
     const screenDownTextX = 80 - (screenDownText.length - 11) * 6;
     const textArr = [
@@ -512,7 +512,8 @@ class FmHsw3 {
   destroy() {
     if (this.model) {
       if (this.mixers) {
-        const fmGroup = this.group?.getObjectByName('fmThree');
+        const fmHsw3 = this.group?.getObjectByName('fmHsw3');
+        const fmGroup = fmHsw3?.getObjectByName('fm');
         this.mixers.uncacheClip(this.clipActionArr.frontDoor.getClip());
         this.mixers.uncacheClip(this.clipActionArr.backDoor.getClip());
         this.mixers.uncacheAction(this.clipActionArr.frontDoor.getClip(), fmGroup);

+ 3 - 3
src/views/vent/monitorManager/gateMonitor/gate.threejs.three.tl.ts

@@ -89,8 +89,8 @@ class FmThreeTl {
     const screenDownText = VENT_PARAM['modalText']
       ? VENT_PARAM['modalText']
       : History_Type['type'] == 'remote'
-      ? `国能神东煤炭集团监制`
-      : '煤科通安(北京)智控科技有限公司研制';
+        ? `国能神东煤炭集团监制`
+        : '煤科通安(北京)智控科技有限公司研制';
 
     const screenDownTextX = 80 - (screenDownText.length - 11) * 6;
     const textArr = [
@@ -477,7 +477,7 @@ class FmThreeTl {
   destroy() {
     if (this.model) {
       if (this.mixers) {
-        const fmGroup = this.group?.getObjectByName('fmThree');
+        const fmGroup = this.group?.getObjectByName('fm-three-tl');
         this.mixers.uncacheClip(this.clipActionArr.frontDoor.getClip());
         this.mixers.uncacheClip(this.clipActionArr.backDoor.getClip());
         this.mixers.uncacheAction(this.clipActionArr.frontDoor.getClip(), fmGroup);

+ 3 - 2
src/views/vent/monitorManager/gateMonitor/gate.threejs.tj.ssl.ts

@@ -206,11 +206,12 @@ class ddFmSsl {
       const frontDoor = new THREE.AnimationClip('frontDoor', 15, fontTracks);
       this.frontClipAction = this.mixers.clipAction(frontDoor, fmGroup);
       this.frontClipAction.clampWhenFinished = true;
+      this.frontClipAction.loop = THREE.LoopOnce;
       // this.frontClipAction.reset();
       // this.frontClipAction.time = 0;
       // this.frontClipAction.timeScale = 1;
       // this.frontClipAction.clampWhenFinished = true;
-      // this.frontClipAction.loop = THREE.LoopOnce;
+
       // this.frontClipAction.play();
     }
   }
@@ -234,7 +235,7 @@ class ddFmSsl {
           handler = () => {
             this.frontClipAction.paused = true;
             this.frontClipAction.reset(); //
-            this.frontClipAction.time = 2.5;
+            this.frontClipAction.time = 2;
             this.frontClipAction.timeScale = -timeScale;
             this.frontClipAction.play();
             this.fmClock.start();

+ 69 - 277
src/views/vent/monitorManager/gateMonitor/index.vue

@@ -89,14 +89,13 @@
           </a-radio-group>
           <div class="button-box" v-for="(item, index) in modelList" @click="playAnimation(7, item.value)" :key="index">{{ item.text }}</div>
         </div>
-
-        <!-- <div class="run-type row">
-          <div class="control-title">运行状态:</div>
-          <a-radio-group v-model:value="selectData.runRoRecondition">
-            <a-radio :value="`0`">检修</a-radio>
-            <a-radio :value="`1`">运行</a-radio>
-          </a-radio-group>
-        </div> -->
+      </div>
+      <div v-if="hasPermission('gate:overhaul')" class="run-type row">
+        <div class="control-title">是否检修:</div>
+        <a-radio-group v-model:value="selectData.runRoRecondition" @change="changeOverhaul">
+          <a-radio :value="`1`">是</a-radio>
+          <a-radio :value="`0`">否</a-radio>
+        </a-radio-group>
       </div>
     </div>
     <div class="title-text"> {{ selectData.supplyAirAddr || selectData.strinstallpos || selectData.strname }} </div>
@@ -173,6 +172,9 @@
                 <a-tag v-else-if="column.dataIndex === 'warnFlag'" :color="record.warnFlag == '0' ? 'green' : 'red'">{{
                   record.warnFlag == '0' ? '正常' : '报警'
                 }}</a-tag>
+                <a-tag v-if="column.dataIndex === 'runRoRecondition'" :color="record.runRoRecondition == '1' ? '#f00' : 'green'">{{
+                  record.runRoRecondition == '1' ? '检修' : '运行'
+                }}</a-tag>
                 <a-tag v-if="column.dataIndex === 'netStatus'" :color="record.netStatus == '0' ? '#f00' : 'green'">{{
                   record.netStatus == '0' ? '断开' : '连接'
                 }}</a-tag>
@@ -324,6 +326,7 @@
     </div>
   </div>
   <div
+    v-if="renderPlayer"
     ref="playerRef"
     style="
       z-index: 1;
@@ -402,6 +405,7 @@
   const deviceType = ref('gate');
   const activeKey = ref('1'); // tab
   const loading = ref(false);
+  const renderPlayer = ref(true);
   // const stationType = ref('plc1');
   const scroll = reactive({
     y: 230,
@@ -607,7 +611,7 @@
       addMonitorText(selectData);
       loading.value = false;
     });
-    await getCamera(selectRow.deviceID, playerRef.value);
+    await getCamera(selectRow.deviceID, playerRef, renderPlayer);
   }
 
   // 播放动画
@@ -615,98 +619,42 @@
     const value = data;
     switch (handlerState) {
       case 1: // 打开前门
-        if (selectData.frontGateOpen == '0' && selectData.frontGateClose == '1') {
-          modalTitle.value = '打开前门';
-          modalType.value = '1';
-          modalIsShow.value = true;
-        } else {
-          // message.warning('前门已经打开或正在打开,请勿重新操作');
-          message.warning('没有监测到前门关到位,无法进行指令下发操作');
-        }
+        modalTitle.value = '打开前门';
+        modalType.value = '1';
+        modalIsShow.value = true;
         break;
       case 2: // 关闭前门
-        if (selectData.frontGateOpen == '1' && selectData.frontGateClose == '0') {
-          modalTitle.value = '关闭前门';
-          modalType.value = '2';
-          modalIsShow.value = true;
-        } else {
-          // message.warning('前门已经关闭或正在关闭,请勿重新操作');
-          message.warning('没有监测到前门开到位,无法进行指令下发操作');
-        }
+        modalTitle.value = '关闭前门';
+        modalType.value = '2';
+        modalIsShow.value = true;
         break;
       case 3: // 打开后门
-        if (selectData.rearGateOpen == '0' && selectData.rearGateClose == '1') {
-          modalTitle.value = '打开后门';
-          modalType.value = '3';
-          modalIsShow.value = true;
-        } else {
-          // message.warning('后门已经打开或正在打开,请勿重新操作');
-          message.warning('没有监测到后门关到位,无法进行指令下发操作');
-        }
+        modalTitle.value = '打开后门';
+        modalType.value = '3';
+        modalIsShow.value = true;
         break;
       case 4: // 关闭后门
-        if (selectData.rearGateOpen == '1' && selectData.rearGateClose == '0') {
-          modalTitle.value = '关闭后门';
-          modalType.value = '4';
-          modalIsShow.value = true;
-        } else {
-          // message.warning('后门已经关闭或正在关闭,请勿重新操作');
-          message.warning('没有监测到后门开到位,无法进行指令下发操作');
-        }
+        modalTitle.value = '关闭后门';
+        modalType.value = '4';
+        modalIsShow.value = true;
         break;
       case 8: // 打开中间门
-        if (selectData.midGateOpen == '0' && selectData.midGateClose == '1') {
-          modalTitle.value = '打开中间门';
-          modalType.value = '8';
-          modalIsShow.value = true;
-        } else {
-          // message.warning('后门已经打开或正在打开,请勿重新操作');
-          message.warning('没有监测到中间门关到位,无法进行指令下发操作');
-        }
+        modalTitle.value = '打开中间门';
+        modalType.value = '8';
+        modalIsShow.value = true;
         break;
       case 9: // 关闭中间门
-        if (selectData.midGateOpen == '1' && selectData.midGateClose == '0') {
-          modalTitle.value = '关闭中间门';
-          modalType.value = '9';
-          modalIsShow.value = true;
-        } else {
-          // message.warning('后门已经关闭或正在关闭,请勿重新操作');
-          message.warning('没有监测到中间门开到位,无法进行指令下发操作');
-        }
+        modalTitle.value = '关闭中间门';
+        modalType.value = '9';
+        modalIsShow.value = true;
         break;
       case 5: // 打开前后门
-        // if (
-        //   selectData.frontGateOpen == '0' &&
-        //   selectData.frontGateClose == '1' &&
-        //   selectData.rearGateOpen == '0' &&
-        //   selectData.rearGateClose == '1'
-        // ) {
-        //   modalTitle.value = '打开前后门';
-        //   modalType.value = '5';
-        //   modalIsShow.value = true;
-        // } else {
-        //   // message.warning('前后门已经打开或正在打开,请勿重新操作');
-        //   message.warning('没有监测到前门、后门关到位,无法进行指令下发操作');
-        // }
-        modalTitle.value = '打开前后门';
+        modalTitle.value = '同时打开';
         modalType.value = '5';
         modalIsShow.value = true;
         break;
       case 6: // 关闭前后门
-        // if (
-        //   selectData.frontGateOpen == '1' &&
-        //   selectData.frontGateClose == '0' &&
-        //   selectData.rearGateOpen == '1' &&
-        //   selectData.rearGateClose == '0'
-        // ) {
-        //   modalTitle.value = '关闭前后门';
-        //   modalType.value = '6';
-        //   modalIsShow.value = true;
-        // } else {
-        //   // message.warning('前后门已经关闭或正在关闭,请勿重新操作');
-        //   message.warning('没有监测到前门、后门开到位,无法进行指令下发操作');
-        // }
-        modalTitle.value = '关闭前后门';
+        modalTitle.value = '同时关闭';
         modalType.value = '6';
         modalIsShow.value = true;
         break;
@@ -755,80 +703,19 @@
     }
     contrlValue = value;
   }
-  // 保德缺打开状态
 
-  // function playAnimation(handlerState, data: any = null) {
-  //   const value = data;
-  //   switch (handlerState) {
-  //     case 1: // 打开前门
-  //       modalTitle.value = '打开前门';
-  //       modalType.value = '1';
-  //       modalIsShow.value = true;
-  //       break;
-  //     case 2: // 关闭前门
-  //       modalTitle.value = '关闭前门';
-  //       modalType.value = '2';
-  //       modalIsShow.value = true;
-  //       break;
-  //     case 3: // 打开后门
-  //       modalTitle.value = '打开后门';
-  //       modalType.value = '3';
-  //       modalIsShow.value = true;
-  //       break;
-  //     case 4: // 关闭后门
-  //       modalTitle.value = '关闭后门';
-  //       modalType.value = '4';
-  //       modalIsShow.value = true;
-  //       break;
-  //     case 8: // 打开中间门
-  //       modalTitle.value = '打开中间门';
-  //       modalType.value = '8';
-  //       modalIsShow.value = true;
-  //       break;
-  //     case 9: // 关闭中间门
-  //       modalTitle.value = '关闭中间门';
-  //       modalType.value = '9';
-  //       modalIsShow.value = true;
-  //       break;
-  //     case 5: // 打开前后门
-  //       modalTitle.value = '打开前后门';
-  //       modalType.value = '5';
-  //       modalIsShow.value = true;
-  //       break;
-  //     case 6: // 关闭前后门
-  //       modalTitle.value = '关闭前后门';
-  //       modalType.value = '6';
-  //       modalIsShow.value = true;
-  //       break;
-
-  //     case 7: // 控制模式切换
-  //       modalTitle.value = '控制模式切换';
-  //       modalType.value = '7';
-  //       modalIsShow.value = true;
-  //       break;
-  //     case 10: // 风窗控制
-  //       modalTitle.value = '风窗控制';
-  //       modalType.value = '10';
-  //       modalIsShow.value = true;
-  //       break;
-  //   }
-
-  //   if (globalConfig?.simulatedPassword) {
-  //     handleOK('', handlerState + '');
-  //   }
-  //   contrlValue = value;
-  // }
+  const changeOverhaul = (e) => {
+    modalTitle.value = '检修控制';
+    modalType.value = '16';
+    modalIsShow.value = true;
+    contrlValue = e;
+  };
 
   function handleOK(passWord, handlerState, value?) {
     if (!passWord && !globalConfig?.simulatedPassword) {
       message.warning('请输入密码');
       return;
     }
-    if (isOpenRunning) {
-      message.warning('风门正在运行。。。');
-      modalIsShow.value = false;
-      return;
-    }
     const data = {
       deviceid: selectData.deviceID,
       devicetype: selectData.deviceType,
@@ -838,102 +725,31 @@
       masterComputer: selectData.masterComputer,
     };
     let handler = () => {};
-    debugger;
 
     switch (handlerState) {
       case '1': // 打开前门
-        if (selectData.frontGateOpen == '0' && selectData.frontGateClose == '1') {
-          handler = () => {
-            frontDoorIsOpen.value = true;
-          };
-          data.paramcode = 'frontGateOpen_S';
-        } else {
-          message.warning('前门已打开。。。');
-          modalIsShow.value = false;
-        }
+        data.paramcode = 'frontGateOpen_S';
         break;
       case '2': // 关闭前门
-        if (selectData.frontGateOpen == '1' && selectData.frontGateClose == '0') {
-          handler = () => {
-            frontDoorIsOpen.value = false;
-          };
-          data.paramcode = 'frontGateClose_S';
-        } else {
-          message.warning('前门已关闭。。。');
-          modalIsShow.value = false;
-        }
+        data.paramcode = 'frontGateClose_S';
         break;
       case '3': // 打开后门
-        if (selectData.rearGateOpen == '0' && selectData.rearGateClose == '1') {
-          handler = () => {
-            backDoorIsOpen.value = true;
-          };
-          data.paramcode = 'rearGateOpen_S';
-        } else {
-          message.warning('后门已打开。。。');
-          modalIsShow.value = false;
-        }
+        data.paramcode = 'rearGateOpen_S';
         break;
       case '4': // 关闭后门
-        if (selectData.rearGateOpen == '1' && selectData.rearGateClose == '0') {
-          handler = () => {
-            backDoorIsOpen.value = false;
-          };
-          data.paramcode = 'rearGateClose_S';
-        } else {
-          message.warning('后门已关闭。。。');
-          modalIsShow.value = false;
-        }
+        data.paramcode = 'rearGateClose_S';
         break;
       case '8': // 打开中间门
-        if (selectData.midGateOpen == '0' && selectData.midGateClose == '1') {
-          handler = () => {
-            midDoorIsOpen.value = true;
-          };
-          data.paramcode = 'midGateOpen_S';
-        } else {
-          message.warning('中间风门已打开。。。');
-          modalIsShow.value = false;
-        }
+        data.paramcode = 'midGateOpen_S';
         break;
       case '9': // 关闭中间门
-        if (selectData.midGateOpen == '1' && selectData.midGateClose == '0') {
-          handler = () => {
-            midDoorIsOpen.value = false;
-          };
-          data.paramcode = 'midGateClose_S';
-        } else {
-          message.warning('中间风门已关闭。。。');
-          modalIsShow.value = false;
-        }
+        data.paramcode = 'midGateClose_S';
         break;
       case '5': // 打开前后门
-        if (
-          selectData.frontGateOpen == '0' &&
-          selectData.frontGateClose == '1' &&
-          selectData.rearGateOpen == '0' &&
-          selectData.rearGateClose == '1'
-        ) {
-          handler = () => {
-            frontDoorIsOpen.value = true;
-            backDoorIsOpen.value = true;
-          };
-          data.paramcode = 'sameTimeOpen';
-        }
+        data.paramcode = 'sameTimeOpen';
         break;
       case '6': // 关闭前后门
-        if (
-          selectData.frontGateOpen == '1' &&
-          selectData.frontGateClose == '0' &&
-          selectData.rearGateOpen == '1' &&
-          selectData.rearGateClose == '0'
-        ) {
-          handler = () => {
-            frontDoorIsOpen.value = false;
-            backDoorIsOpen.value = false;
-          };
-          data.paramcode = 'sameTimeClose';
-        }
+        data.paramcode = 'sameTimeClose';
         break;
       case '7': // 远程与就地
         if (selectData.contrlMod == 'codeCtrl') {
@@ -980,22 +796,32 @@
         data.paramcode = 'rearSetValue';
         data.value = value;
         break;
+      case '16': // 检修控制
+        data.paramcode = 'runRoRecondition_S';
+        data.value = selectData['runRoRecondition'] == 0 ? '1' : '0';
+        break;
     }
 
     if (data.paramcode) {
-      deviceControlApi(data).then((res) => {
-        // 模拟时开启
-        if (res.success) {
-          modalIsShow.value = false;
-          if (globalConfig.History_Type == 'remote') {
-            message.success('指令已下发至生产管控平台成功!');
+      deviceControlApi(data)
+        .then((res) => {
+          // 模拟时开启
+          if (res.success) {
+            modalIsShow.value = false;
+            if (globalConfig.History_Type == 'remote') {
+              message.success('指令已下发至生产管控平台成功!');
+            } else {
+              message.success('指令已下发成功!');
+            }
           } else {
-            message.success('指令已下发成功!');
+            message.error(res.message);
           }
-        } else {
-          message.error(res.message);
-        }
-      });
+          contrlValue = '';
+        })
+        .catch(() => {
+          message.error('控制异常,请排查问题。。。');
+          contrlValue = '';
+        });
     }
   }
   let isOpenRunning = false; //开关门动作是否在进行
@@ -1128,40 +954,6 @@
     modalType.value = '';
   }
 
-  // // 远程、就地切换
-  // function changeType() {
-  //   const data = {
-  //     deviceid: selectData.deviceID,
-  //     devicetype: selectData.deviceType,
-  //     paramcode: 'autoRoManualControl',
-  //     value: selectData.autoRoManual,
-  //   };
-  //   deviceControlApi(data).then(() => {
-  //     if (globalConfig.History_Type == 'remote') {
-  //       message.success('指令已下发至生产管控平台成功!');
-  //     } else {
-  //       message.success('指令已下发成功!');
-  //     }
-  //   });
-  // }
-
-  // async function getDataSource() {
-  //   dataSource.value = [];
-  //   const params = await resetFormParam();
-  //   if (stationType.value !== 'redis') {
-  //     const result = await defHttp.get({ url: '/safety/ventanalyMonitorData/listdays', params: params });
-  //     if (result['datalist']['records'].length > 0) {
-  //       dataSource.value = result['datalist']['records'].map((item: any) => {
-  //         return Object.assign(item, item['readData']);
-  //       });
-  //     } else {
-  //       dataSource.value = [];
-  //     }
-  //   } else {
-  //     const result = await defHttp.post({ url: '/monitor/history/getHistoryData', params: params });
-  //     dataSource.value = result['records'] || [];
-  //   }
-  // }
   onMounted(async () => {
     const { query } = unref(currentRoute);
     if (query['deviceType']) deviceType.value = query['deviceType'] as string;
@@ -1191,11 +983,11 @@
   });
 
   onBeforeUnmount(() => {
+    removeCamera(playerRef);
     getDeviceBaseList();
   });
 
   onUnmounted(() => {
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 12 - 5
src/views/vent/monitorManager/groutMonitor/components/groutHomeHjt.vue

@@ -37,12 +37,17 @@
         </div>
       </div>
     </div>
-    <div ref="playerRef" style="z-index: 999; position: absolute; top: 100px; right: 15px; height: 100%; margin: auto; pointer-events: none"> </div>
+    <div
+      v-if="renderPlayer"
+      ref="playerRef"
+      style="z-index: 999; position: absolute; top: 100px; right: 15px; height: 100%; margin: auto; pointer-events: none"
+    >
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { ref, onMounted, onUnmounted, defineProps, watch } from 'vue';
+  import { ref, onMounted, onUnmounted, defineProps, watch, onBeforeUnmount } from 'vue';
   import { mountedThree, destroy, setModelType } from '../grout.threejs';
   import { list } from '../grout.api';
   import { useCamera } from '/@/hooks/system/useCamera';
@@ -61,6 +66,7 @@
   const loading = ref(false);
   const monitorData = ref({ InputFlux: undefined });
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const { getCamera, removeCamera } = useCamera();
 
   // https获取监测数据
@@ -101,7 +107,7 @@
     () => props.deviceId,
     async (deviceId) => {
       if (deviceId) {
-        await getCamera(deviceId, playerRef.value, 'pulping');
+        await getCamera(deviceId, playerRef, renderPlayer, 'pulping');
       }
     }
   );
@@ -115,10 +121,11 @@
       loading.value = false;
     });
   });
-
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
+  });
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 12 - 4
src/views/vent/monitorManager/groutMonitor/components/groutHomeJj.vue

@@ -26,12 +26,17 @@
         </div>
       </div>
     </div>
-    <div ref="playerRef" style="z-index: 999; position: absolute; top: 100px; right: 15px; height: 100%; margin: auto; pointer-events: none"> </div>
+    <div
+      v-if="renderPlayer"
+      ref="playerRef"
+      style="z-index: 999; position: absolute; top: 100px; right: 15px; height: 100%; margin: auto; pointer-events: none"
+    >
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { watch, ref, onMounted, onUnmounted, defineProps, reactive } from 'vue';
+  import { watch, ref, onMounted, onUnmounted, defineProps, reactive, onBeforeUnmount } from 'vue';
   import { mountedThree, destroy, setModelType } from '../grout.threejs';
   import { deviceMonitorList } from '../grout.data';
   import { list } from '../grout.api';
@@ -54,6 +59,7 @@
   const selectData = reactive({});
 
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const { getCamera, removeCamera } = useCamera();
 
   // https获取监测数据
@@ -95,7 +101,7 @@
     () => props.deviceId,
     async (deviceId) => {
       if (deviceId) {
-        await getCamera(deviceId, playerRef.value);
+        await getCamera(deviceId, playerRef, renderPlayer);
       }
     },
     { immediate: true }
@@ -111,9 +117,11 @@
       getMonitor(true);
     });
   });
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
+  });
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 15 - 5
src/views/vent/monitorManager/groutMonitor/components/groutHomehlg.vue

@@ -60,12 +60,17 @@
       </div>
     </div>
 
-    <div ref="playerRef" style="z-index: 9999; position: absolute; top: 0px; right: 15px; height: 100%; margin: auto; pointer-events: none"> </div>
+    <div
+      v-if="renderPlayer"
+      ref="playerRef"
+      style="z-index: 9999; position: absolute; top: 0px; right: 15px; height: 100%; margin: auto; pointer-events: none"
+    >
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { watch, ref, onMounted, onUnmounted, defineProps, reactive } from 'vue';
+  import { watch, ref, onMounted, onUnmounted, defineProps, onBeforeUnmount } from 'vue';
   import ventBox1 from '/@/components/vent/ventBox1.vue';
   import { mountedThree, destroy, setModelType } from '../grout.threejs';
   import { formatNum } from '/@/utils/ventutil';
@@ -90,6 +95,7 @@
   const monitorData = ref({});
 
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const { getCamera, removeCamera } = useCamera();
 
   // https获取监测数据
@@ -133,14 +139,14 @@
     () => props.deviceId,
     async (deviceId) => {
       if (deviceId) {
-        await getCamera(deviceId, playerRef.value, 'pulping');
+        await getCamera(deviceId, playerRef, renderPlayer, 'pulping');
       }
     }
   );
 
   onMounted(() => {
     getMonitor(true)?.then(async () => {
-      if (props.deviceId) await getCamera(props.deviceId, playerRef.value, 'pulping');
+      if (props.deviceId) await getCamera(props.deviceId, playerRef, renderPlayer, 'pulping');
     });
     loading.value = true;
     mountedThree().then(async () => {
@@ -150,9 +156,13 @@
       timer = null;
     });
   });
+
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
+  });
+
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 13 - 5
src/views/vent/monitorManager/groutMonitor/components/groutHomelt.vue

@@ -90,12 +90,17 @@
       </div>
     </div>
 
-    <div ref="playerRef" style="z-index: 9999; position: absolute; top: 550px; right: 15px; height: 100%; margin: auto; pointer-events: none"> </div>
+    <div
+      v-if="renderPlayer"
+      ref="playerRef"
+      style="z-index: 9999; position: absolute; top: 550px; right: 15px; height: 100%; margin: auto; pointer-events: none"
+    >
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { watch, ref, onMounted, onUnmounted, defineProps, reactive } from 'vue';
+  import { watch, ref, onMounted, onUnmounted, defineProps, onBeforeUnmount } from 'vue';
   import ventBox1 from '/@/components/vent/ventBox1.vue';
   import { mountedThree, destroy, setModelType } from '../grout.threejs';
   import { formatNum } from '/@/utils/ventutil';
@@ -120,6 +125,7 @@
   const monitorData = ref({});
 
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const { getCamera, removeCamera } = useCamera();
 
   // https获取监测数据
@@ -162,14 +168,14 @@
     () => props.deviceId,
     async (deviceId) => {
       if (deviceId) {
-        await getCamera(deviceId, playerRef.value, 'pulping');
+        await getCamera(deviceId, playerRef, renderPlayer, 'pulping');
       }
     }
   );
 
   onMounted(() => {
     getMonitor(true)?.then(async () => {
-      if (props.deviceId) await getCamera(props.deviceId, playerRef.value, 'pulping');
+      if (props.deviceId) await getCamera(props.deviceId, playerRef, renderPlayer, 'pulping');
     });
     loading.value = true;
     mountedThree().then(async () => {
@@ -179,9 +185,11 @@
       timer = null;
     });
   });
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
+  });
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 23 - 6
src/views/vent/monitorManager/mainFanMonitor/index.vue

@@ -427,8 +427,9 @@
     <LivePlayer id="main-player1" ref="player1" :videoUrl="flvURL1()" muted loop loading controls />
   </div>
   <div
-    v-show="showPlay"
+    v-if="renderPlayer"
     ref="playerRef"
+    v-show="showPlay"
     :class="{ 'to-right': rightColumns.length < 1 || leftColumns.length < 1, 'to-no-right': rightColumns.length > 0 && leftColumns.length > 0 }"
     style="z-index: 999; position: absolute; width: 100%; height: 100%; overflow-x: auto; overflow-y: hidden; margin: auto; pointer-events: none"
     :style="{ top: hasPermission('btn:show') ? '155px' : '100px' }"
@@ -629,7 +630,21 @@
   import FanDeviceEcharts from '../comment/FanDeviceEcharts.vue';
   import BarAndLine from '../../../../components/chart/BarAndLine.vue';
   import FanEchrats from '/@/views/vent/monitorManager/mainFanMonitor/fanEchrats.vue';
-  import { onBeforeMount, unref, ref, onMounted, inject, onUnmounted, reactive, toRaw, watch, nextTick, defineAsyncComponent, shallowRef } from 'vue';
+  import {
+    onBeforeMount,
+    unref,
+    ref,
+    onMounted,
+    inject,
+    onUnmounted,
+    reactive,
+    toRaw,
+    watch,
+    nextTick,
+    defineAsyncComponent,
+    shallowRef,
+    onBeforeUnmount,
+  } from 'vue';
   import GroupMonitorTable from '../comment/GroupMonitorTable.vue';
   // // import HistoryTable from '../comment/HistoryTable.vue';
   // import HistoryTable from '../../../vent/comment/history/HistoryTable.vue';
@@ -693,6 +708,7 @@
   const player2 = ref(null);
   const activeKey = ref('1');
   const loading = ref(false);
+  const renderPlayer = ref(true);
   const modalIsShow = ref<boolean>(false); // 是否显示模态框
   const modalWarnIsShow = ref<boolean>(false); // 是否显示故障诊断模态框
   const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
@@ -1028,7 +1044,7 @@
   };
   // 切换检测数据
   const getSelectRow = async (id) => {
-    removeCamera();
+    removeCamera(playerRef);
     if (!id) return;
     loading.value = true;
     const baseDataIndex: any = dataSource.value.findIndex((baseData: any) => baseData.deviceID === id);
@@ -1073,7 +1089,7 @@
       backMonitorIsShow.value = false;
       await setModelType(type);
     });
-    await getCamera(id, playerRef.value);
+    await getCamera(id, playerRef, renderPlayer);
     return;
   };
 
@@ -1650,9 +1666,10 @@
       });
     }
   });
-
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
+  });
   onUnmounted(() => {
-    removeCamera();
     destroy();
     if (timer) {
       clearTimeout(timer);

+ 10 - 10
src/views/vent/monitorManager/nitrogen/components/nitrogenHome.vue

@@ -21,8 +21,8 @@
                   monitorData[preMonitor.code.replace(prefix[0], `${prefix[0]}${groupNum}`)]
                     ? formatNum(monitorData[preMonitor.code.replace(prefix[0], `${prefix[0]}${groupNum}`)])
                     : preMonitor.value
-                    ? preMonitor.value
-                    : '-'
+                      ? preMonitor.value
+                      : '-'
                 }}</span
                 ><span class="unit">{{ preMonitor.unit }}</span></span
               >
@@ -53,8 +53,8 @@
                   monitorData[cqgMonitor.code.replace(prefix[0], `${prefix[1]}${groupNum}`)]
                     ? formatNum(monitorData[cqgMonitor.code.replace(prefix[1], `${prefix[1]}${groupNum}`)])
                     : cqgMonitor.value
-                    ? cqgMonitor.value
-                    : '-'
+                      ? cqgMonitor.value
+                      : '-'
                 }}</span
                 ><span class="unit">{{ cqgMonitor.unit }}</span></span
               >
@@ -131,8 +131,8 @@
                           monitorData[preFan.code.replace(prefix[2], prefix[2] + groupNum)] >= 0
                             ? formatNum(Number(monitorData[preFan.code.replace(prefix[2], prefix[2] + groupNum)]))
                             : preFan.value
-                            ? preFan.value
-                            : '-'
+                              ? preFan.value
+                              : '-'
                         }}</span>
                         <span
                           v-else
@@ -176,7 +176,7 @@
             </ventBox1>
           </div>
         </div>
-        <div ref="playerRef" class="playerBox"> </div>
+        <div v-if="renderPlayer" ref="playerRef" class="playerBox"> </div>
       </div>
     </div>
   </div>
@@ -202,6 +202,7 @@
   const monitorData = ref<Object | []>({});
   const sysStateList = ref<State[]>([]);
   const playerRef = ref();
+  const renderPlayer = ref(true);
 
   const { getCamera, removeCamera } = useCamera();
 
@@ -345,17 +346,16 @@
       mountedThree(monitorDataGroupArr).then(async () => {
         setMonitorGroupNum(1);
         getMonitor(true).then(async () => {
-          if (monitorData.value && monitorData.value['deviceID']) await getCamera(monitorData.value['deviceID'], playerRef.value);
+          if (monitorData.value && monitorData.value['deviceID']) await getCamera(monitorData.value['deviceID'], playerRef, renderPlayer);
         });
       });
     }, 0);
   });
   onBeforeUnmount(() => {
-    removeCamera();
+    removeCamera(playerRef);
   });
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 9 - 4
src/views/vent/monitorManager/nitrogen/components/nitrogenHome1.vue

@@ -187,7 +187,12 @@
         </div>
       </div>
     </div>
-    <div ref="playerRef" class="player-box" :class="{ 'sw-player': sysOrgCode == 'sdmtjtswmk' || sysOrgCode == 'hnqymdktymk' }"></div>
+    <div
+      v-if="renderPlayer"
+      ref="playerRef"
+      class="player-box"
+      :class="{ 'sw-player': sysOrgCode == 'sdmtjtswmk' || sysOrgCode == 'hnqymdktymk' }"
+    ></div>
   </div>
 </template>
 
@@ -218,6 +223,7 @@
   const monitorDataGroup = ref<Number[]>([]);
   const monitorDataGroupFlag = ref(1);
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const deviceProperty = ref({
     leftMonitor: [] as devicePropertyType[],
     rightMonitor: [] as devicePropertyType[],
@@ -402,17 +408,16 @@
       mountedThree(deviceProperty.value.monitorDataGroupArr).then(async () => {
         setMonitorGroupNum(1);
         getMonitor(true).then(async () => {
-          if (selectData.value && selectData.value['deviceID']) await getCamera(selectData.value['deviceID'], playerRef.value);
+          if (selectData.value && selectData.value['deviceID']) await getCamera(selectData.value['deviceID'], playerRef, renderPlayer);
         });
       });
     }, 0);
   });
   onBeforeUnmount(() => {
-    removeCamera();
+    removeCamera(playerRef);
   });
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 5 - 4
src/views/vent/monitorManager/nitrogen/components/nitrogenHomeBLT.vue

@@ -170,7 +170,8 @@
             </div>
           </div>
         </div>
-        <div ref="playerRef" style="z-index: 9999; position: fixed; top: 180px; right: 15px; height: 100%; pointer-events: none"> </div>
+        <div v-if="renderPlayer" ref="playerRef" style="z-index: 9999; position: fixed; top: 180px; right: 15px; height: 100%; pointer-events: none">
+        </div>
       </div>
     </div>
   </div>
@@ -188,6 +189,7 @@
   import { formatNum } from '/@/utils/ventutil';
   import { useCamera } from '/@/hooks/system/useCamera';
   const playerRef = ref();
+  const renderPlayer = ref(true);
 
   const loading = ref(true);
   const monitorDataGroupFlag = ref(0);
@@ -567,7 +569,7 @@
   onMounted(async () => {
     mountedThree(monitorDataGroupArr).then(() => {
       getMonitor(true).then(async () => {
-        if (monitorData.value && monitorData.value['deviceID']) await getCamera(monitorData.value['deviceID'], playerRef.value);
+        if (monitorData.value && monitorData.value['deviceID']) await getCamera(monitorData.value['deviceID'], playerRef, renderPlayer);
         monitorDataGroupFlag.value = 1;
         setMonitorGroupNum(1);
         getMonitorPro(1);
@@ -576,12 +578,11 @@
   });
 
   onBeforeUnmount(() => {
-    removeCamera();
+    removeCamera(playerRef);
   });
 
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 1 - 1
src/views/vent/monitorManager/obfurage1Monitor/index.vue

@@ -61,7 +61,7 @@
               :columns="columns"
               :dataSource="dataSource"
               design-scope="gate-monitor"
-              @selectRow="getSelectRow"
+              @select-row="getSelectRow"
               :scroll="{ y: scroll.y - 40 }"
               title="风门监测"
               :isShowPagination="true"

+ 13 - 6
src/views/vent/monitorManager/windowMonitor/index.vue

@@ -174,6 +174,7 @@
     </div>
   </div>
   <div
+    v-if="renderPlayer"
     ref="playerRef"
     style="
       z-index: 1;
@@ -182,11 +183,12 @@
       right: 0px;
       width: 100%;
       height: 800px;
-      overflow-y: auto;
       pointer-events: none;
-      margin-left: auto;
+      overflow-y: auto;
+      flex-direction: column;
     "
-  ></div>
+  >
+  </div>
   <LivePlayer
     id="fc-player1"
     style="height: 220px; width: 300px; position: absolute; top: 0px; z-index: -1"
@@ -228,7 +230,7 @@
 <script setup lang="ts">
   import { message } from 'ant-design-vue';
   import DeviceEcharts from '../comment/DeviceEcharts.vue';
-  import { onBeforeMount, ref, onMounted, onUnmounted, reactive, toRaw, shallowRef, nextTick, inject, unref } from 'vue';
+  import { onBeforeMount, ref, onMounted, onUnmounted, reactive, toRaw, shallowRef, nextTick, inject, unref, onBeforeUnmount } from 'vue';
   import MonitorTable from '../comment/MonitorTable.vue';
   import HistoryTable from '../comment/HistoryTable.vue';
   import AlarmHistoryTable from '../comment/AlarmHistoryTable.vue';
@@ -280,6 +282,7 @@
   const deviceBaseList = ref([]);
   const activeKey = ref('1');
   const loading = ref(false);
+  const renderPlayer = ref(true);
   const windowAngle = ref(0);
   const ch4 = ref(0.6);
   const targetVolume = ref(0);
@@ -403,7 +406,7 @@
     addMonitorText(selectData.value);
     playAnimation(selectRow, selectData.value.maxarea, true);
     loading.value = false;
-    await getCamera(selectRow.deviceID, playerRef.value);
+    await getCamera(selectRow.deviceID, playerRef, renderPlayer);
   };
 
   // 判断前后窗的面积是否发生改变,如果改变则开启动画
@@ -661,9 +664,13 @@
       });
     }
   });
+
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
+  });
+
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 14 - 5
src/views/vent/monitorManager/windowMonitorBet/index.vue

@@ -65,7 +65,7 @@
               ref="MonitorDataTable"
               columnsType="wintest_monitor"
               :dataSource="dataSource"
-              @selectRow="getSelectRow"
+              @select-row="getSelectRow"
               design-scope="wintest-monitor"
               :scroll="{ y: scroll.y - 40 }"
               title="风窗监测"
@@ -135,7 +135,11 @@
       </dv-border-box8>
     </div>
   </div>
-  <div ref="playerRef" style="z-index: 999; position: absolute; top: 100px; right: 10px; width: 300px; height: 280px; margin: auto"></div>
+  <div
+    v-if="renderPlayer"
+    ref="playerRef"
+    style="z-index: 999; position: absolute; top: 100px; right: 10px; width: 300px; height: 280px; margin: auto"
+  ></div>
   <LivePlayer
     id="fc-player1"
     style="height: 220px; width: 300px; position: absolute; top: 0px; z-index: -1"
@@ -155,7 +159,7 @@
 <script setup lang="ts">
   import { message } from 'ant-design-vue';
   import DeviceEcharts from '../comment/DeviceEcharts.vue';
-  import { onBeforeMount, ref, onMounted, onUnmounted, reactive, toRaw, watch, nextTick, inject } from 'vue';
+  import { onBeforeMount, ref, onMounted, onUnmounted, reactive, toRaw, onBeforeUnmount, nextTick, inject } from 'vue';
   import MonitorTable from '../comment/MonitorTable.vue';
   import HistoryTable from '../comment/HistoryTable.vue';
   import AlarmHistoryTable from '../comment/AlarmHistoryTable.vue';
@@ -184,6 +188,7 @@
   const MonitorDataTable = ref();
 
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const scroll = reactive({
     y: 230,
   });
@@ -328,7 +333,7 @@
       playAnimation(selectRow, selectData.maxarea, true);
       loading.value = false;
     });
-    await getCamera(selectRow.deviceID, playerRef.value);
+    await getCamera(selectRow.deviceID, playerRef, renderPlayer);
   };
 
   // 判断前后窗的面积是否发生改变,如果改变则开启动画
@@ -456,9 +461,13 @@
       addMonitorText(selectData);
     });
   });
+
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
+  });
+
   onUnmounted(() => {
     destroy();
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 8 - 3
src/views/vent/monitorManager/windrectMonitor/index.vue

@@ -119,6 +119,7 @@
     </div>
   </div>
   <div
+    v-if="renderPlayer"
     ref="playerRef"
     style="
       z-index: 1;
@@ -162,7 +163,7 @@
 
 <script setup lang="ts">
   import DeviceEcharts from '../comment/DeviceEcharts.vue';
-  import { unref, onBeforeMount, ref, onMounted, onUnmounted, reactive, toRaw, nextTick, inject, shallowRef } from 'vue';
+  import { unref, onBeforeMount, ref, onMounted, onUnmounted, reactive, toRaw, nextTick, inject, shallowRef, onBeforeUnmount } from 'vue';
   import { BasicModal, useModalInner } from '/@/components/Modal';
   import MonitorTable from '../comment/MonitorTable.vue';
   import ModalTable from './components/modalTable.vue';
@@ -208,6 +209,7 @@
   const runNum = ref(5); //设备运行数量
   const criticalPathList = ref([]);
   const playerRef = ref();
+  const renderPlayer = ref(true);
   const activeKey = ref('1');
   const loading = ref(false);
   // 默认初始是第一行
@@ -741,7 +743,7 @@
       loading.value = false;
       deviceRunState = '';
       tanTouRunState = '';
-      await getCamera(selectRow.deviceID, playerRef.value);
+      await getCamera(selectRow.deviceID, playerRef, renderPlayer);
     }
   }
   // 设置模型类型
@@ -909,8 +911,11 @@
     }
   });
 
+  onBeforeUnmount(() => {
+    removeCamera(playerRef);
+  });
+
   onUnmounted(() => {
-    removeCamera();
     if (timer) {
       clearTimeout(timer);
       timer = undefined;

+ 1 - 1
types/module.d.ts

@@ -15,4 +15,4 @@ declare module 'virtual:*' {
   export default result;
 }
 
-declare module "*.glsl";
+declare module '*.glsl';