Prechádzať zdrojové kódy

Merge branch 'master' of http://39.97.59.228:8013/hrx/mky-vent-base

bobo04052021@163.com 1 deň pred
rodič
commit
484fc8efda
36 zmenil súbory, kde vykonal 2569 pridanie a 130 odobranie
  1. 1 1
      .env.development
  2. 1 1
      .env.production
  3. 3 1
      package.json
  4. 85 6
      pnpm-lock.yaml
  5. 9 0
      pnpm-workspace.yaml
  6. BIN
      public/sparyVideo.mp4
  7. BIN
      public/texture/earth.jpg
  8. BIN
      public/texture/earth3.png
  9. BIN
      public/video/fanLocal.webm
  10. BIN
      src/assets/images/company/home-pageBg.png
  11. BIN
      src/assets/images/fireNew/FireBj.png
  12. BIN
      src/assets/images/gif/fanlocal.gif
  13. BIN
      src/assets/images/home-container/configurable/minehome/bg.png
  14. BIN
      src/assets/images/home-warn/bg.png
  15. BIN
      src/assets/images/vent/homeNew/bg.png
  16. BIN
      src/assets/images/vent/loginDataCenter/dataCenterBg.png
  17. BIN
      src/assets/images/vent/loginNeiye/bg.png
  18. BIN
      src/assets/images/zd-model.png
  19. BIN
      src/assets/video/fanLocal.mp4
  20. BIN
      src/assets/video/gate.mp4
  21. BIN
      src/assets/video/mainFan.mp4
  22. BIN
      src/assets/video/output_video.mp4
  23. 1 1
      src/hooks/system/useCameraPianation.ts
  24. 44 4
      src/views/vent/home/configurable/components/preset/SysWindCard.vue
  25. 57 19
      src/views/vent/monitorManager/deviceMonitor/components/device/modal/atomizing.modal.vue
  26. 12 7
      src/views/vent/monitorManager/fanLocalMonitor/fanLocal.threejs.ts
  27. 1308 0
      src/views/vent/safetyList/common/detail-235.vue
  28. 170 89
      src/views/vent/safetyList/common/detail.vue
  29. 4 1
      src/views/vent/safetyList/safetyList.api.ts
  30. 94 0
      src/views/ventAI/dataPicker/api.ts
  31. 60 0
      src/views/ventAI/dataPicker/components/DataInterpretationBtn.vue
  32. 355 0
      src/views/ventAI/dataPicker/components/DataInterpretationModal.vue
  33. 7 0
      src/views/ventAI/dataPicker/index.ts
  34. 49 0
      src/views/ventAI/dataPicker/types.ts
  35. 185 0
      src/views/ventAI/dataPicker/useDataInterpretation.ts
  36. 124 0
      src/views/ventAI/dataPicker/useDataInterpretationTrigger.ts

+ 1 - 1
.env.development

@@ -6,7 +6,7 @@ VITE_PUBLIC_PATH = /
 
 # 跨域代理,您可以配置多个 ,请注意,没有换行符
 #VITE_PROXY = [["/jeecgboot","http://localhost:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
-VITE_PROXY = [["/sw","http://182.92.126.35:6008"],["/jeecgsystem","http://182.92.126.35:9999"],["/upload","http://182.92.126.35:9999/upload"],["/documents", "http://182.92.126.35:9050"],["/modelreq", "http://182.92.126.35:9999"],["/webRtc", "http://182.92.126.35:8051"], ["/python", "http://127.0.0.1:8008"], ["/tun2D", "http://127.0.0.1:8088/micro-vent-2dModal/tun2D"],["/dataCenter", "http://39.97.59.228:10999"]]
+VITE_PROXY = [["/sw","http://182.92.126.35:6008"],["/jeecgsystem","http://182.92.126.35:9999"],["/upload","http://182.92.126.35:9999/upload"],["/documents", "http://182.92.126.35:9050"],["/modelreq", "http://182.92.126.35:9999"],["/webRtc", "http://182.92.126.35:8051"], ["/python", "http://127.0.0.1:8008"], ["/tun2D", "http://127.0.0.1:8088/micro-vent-2dModal/tun2D"],["/dataCenter", "http://39.97.59.228:10999"],["/ventAI", "http://39.97.59.228:8070"]]
 # VITE_PROXY = [["/jeecgsystem","http://192.168.183.88:9999"],["/upload","http://192.168.183.88:9999/upload"],["/documents", "http://192.168.183.88:9050"],["/modelreq", "http://192.168.183.88:9999"],["/webRtc", "http://192.168.183.88:8051"]]
 # VITE_PROXY = [["/jeecgsystem","http://10.10.150.72:9999"],["/upload","http://localhost:3300/upload"],["/documents", "http://10.10.150.72:9050"],["/modelreq", "http://10.10.150.72:9999"],["/webRtc", "http://192.168.183.216:8051"]]
 # VITE_PROXY = [["/jeecgsystem","http://192.168.1.8:9999"],["/upload","http://localhost:3300/upload"]]

+ 1 - 1
.env.production

@@ -17,7 +17,7 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
 #VITE_PROXY = [["/jeecgsystem","http://10.248.223.116:9999"],["/upload","http://localhost:3300/upload"]]
 #VITE_PROXY = [["/jeecgsystem","http://10.248.210.152:9999"],["/upload","http://10.248.210.152:9999/upload"]]
 #VITE_PROXY = [["/jeecgsystem","http://182.92.126.35:9999"],["/upload","http://182.92.126.35:9999/upload"]]
-VITE_PROXY = [["/sw","http://10.246.167.205:6008"],["/modelreq","http://172.16.41.171:9999"],["/upload","http://172.16.41.171:9999/upload"],["/documents", "http://182.92.126.35:9050"],["/dataCenter", "http://39.97.59.228:10999"]]
+VITE_PROXY = [["/sw","http://10.246.167.205:6008"],["/modelreq","http://172.16.41.171:9999"],["/upload","http://172.16.41.171:9999/upload"],["/documents", "http://182.92.126.35:9050"],["/dataCenter", "http://39.97.59.228:10999"],["/ventAI", "http://39.97.59.228:8070"]]
 #VITE_PROXY = [["/jeecgsystem","http://172.16.41.171:9999"],["/upload","http://172.16.41.171:9999/upload"]]
 #VITE_PROXY = [["/jeecgsystem","http://10.120.120.163:9999"],["/upload","http://10.120.120.163:9999/upload"]]
 #VITE_PROXY = [["/jeecgsystem","http://192.168.1.4:9999"],["/upload","http://localhost:3300/upload"]]

+ 3 - 1
package.json

@@ -30,6 +30,7 @@
     "@logicflow/core": "^1.2.12",
     "@logicflow/extension": "^1.2.13",
     "@qiaoqiaoyun/drag-free": "^1.1.4",
+    "@types/markdown-it": "^14.1.2",
     "@vue-office/docx": "^1.6.3",
     "@vue-office/excel": "^1.7.14",
     "@vue-office/pdf": "^2.0.10",
@@ -62,6 +63,7 @@
     "katex": "^0.16.25",
     "lodash-es": "^4.17.21",
     "lodash.get": "^4.4.2",
+    "markdown-it": "^14.3.0",
     "marked": "^17.0.1",
     "md5": "^2.3.0",
     "mky-svg": "^1.0.2",
@@ -158,6 +160,7 @@
     "jest": "^29.6.4",
     "less": "^4.2.0",
     "lint-staged": "14.0.1",
+    "mockjs": "^1.1.0",
     "npm-run-all": "^4.1.5",
     "picocolors": "^1.0.0",
     "postcss": "^8.4.28",
@@ -178,7 +181,6 @@
     "ts-node": "^10.9.1",
     "typescript": "^4.9.5",
     "unocss": "^0.55.3",
-    "mockjs": "^1.1.0",
     "video.js": "^8.23.4",
     "videojs-flvjs-es6": "^1.0.1",
     "vite": "^4.4.9",

+ 85 - 6
pnpm-lock.yaml

@@ -32,6 +32,9 @@ 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)
+      '@types/markdown-it':
+        specifier: ^14.1.2
+        version: 14.1.2
       '@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))
@@ -128,6 +131,9 @@ importers:
       lodash.get:
         specifier: ^4.4.2
         version: 4.4.2
+      markdown-it:
+        specifier: ^14.3.0
+        version: 14.3.0
       marked:
         specifier: ^17.0.1
         version: 17.0.1
@@ -386,7 +392,7 @@ importers:
         version: 11.3.2
       http-server:
         specifier: ^14.1.1
-        version: 14.1.1
+        version: 14.1.1(debug@4.4.3)
       husky:
         specifier: ^8.0.3
         version: 8.0.3
@@ -1995,12 +2001,21 @@ packages:
   '@types/jsonfile@6.1.4':
     resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==}
 
+  '@types/linkify-it@5.0.0':
+    resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
+
   '@types/lodash-es@4.17.12':
     resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
 
   '@types/lodash@4.17.20':
     resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}
 
+  '@types/markdown-it@14.1.2':
+    resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
+
+  '@types/mdurl@2.0.0':
+    resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
+
   '@types/minimist@1.2.5':
     resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
 
@@ -2169,6 +2184,7 @@ packages:
 
   '@ungap/structured-clone@1.3.0':
     resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+    deprecated: Potential CWE-502 - Update to 1.3.1 or higher
 
   '@unocss/astro@0.55.7':
     resolution: {integrity: sha512-mw8r14ArxUQBVCCisAJlF/WsZb650iBsduD/lXMk56N/nQ3MMArCcn62kcAxgZSb5tfIOQGQu/tbR8hEcD8y2g==}
@@ -2418,6 +2434,7 @@ packages:
   '@xmldom/xmldom@0.8.11':
     resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==}
     engines: {node: '>=10.0.0'}
+    deprecated: this version has critical issues, please update to the latest version
 
   '@zxcvbn-ts/core@3.0.4':
     resolution: {integrity: sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw==}
@@ -3075,15 +3092,18 @@ packages:
   conventional-changelog-atom@3.0.0:
     resolution: {integrity: sha512-pnN5bWpH+iTUWU3FaYdw5lJmfWeqSyrUkG+wyHBI9tC1dLNnHkbAOg1SzTQ7zBqiFrfo55h40VsGXWMdopwc5g==}
     engines: {node: '>=14'}
+    deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead.
 
   conventional-changelog-cli@3.0.0:
     resolution: {integrity: sha512-3zMYi0IrfNd6AAHdPMrcgCg5DbcffiqNaEBf8cYrlntXPbBIXaELTbnRmUy5TQAe0Hkgi0J6+/VmRCkkJQflcQ==}
     engines: {node: '>=14'}
+    deprecated: This package is no longer maintained. Please use the conventional-changelog package instead.
     hasBin: true
 
   conventional-changelog-codemirror@3.0.0:
     resolution: {integrity: sha512-wzchZt9HEaAZrenZAUUHMCFcuYzGoZ1wG/kTRMICxsnW5AXohYMRxnyecP9ob42Gvn5TilhC0q66AtTPRSNMfw==}
     engines: {node: '>=14'}
+    deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead.
 
   conventional-changelog-conventionalcommits@6.1.0:
     resolution: {integrity: sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw==}
@@ -3092,26 +3112,32 @@ packages:
   conventional-changelog-core@5.0.2:
     resolution: {integrity: sha512-RhQOcDweXNWvlRwUDCpaqXzbZemKPKncCWZG50Alth72WITVd6nhVk9MJ6w1k9PFNBcZ3YwkdkChE+8+ZwtUug==}
     engines: {node: '>=14'}
+    deprecated: Deprecated and no longer maintained. Please use conventional-changelog instead.
 
   conventional-changelog-ember@3.0.0:
     resolution: {integrity: sha512-7PYthCoSxIS98vWhVcSphMYM322OxptpKAuHYdVspryI0ooLDehRXWeRWgN+zWSBXKl/pwdgAg8IpLNSM1/61A==}
     engines: {node: '>=14'}
+    deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead.
 
   conventional-changelog-eslint@4.0.0:
     resolution: {integrity: sha512-nEZ9byP89hIU0dMx37JXQkE1IpMmqKtsaR24X7aM3L6Yy/uAtbb+ogqthuNYJkeO1HyvK7JsX84z8649hvp43Q==}
     engines: {node: '>=14'}
+    deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead.
 
   conventional-changelog-express@3.0.0:
     resolution: {integrity: sha512-HqxihpUMfIuxvlPvC6HltA4ZktQEUan/v3XQ77+/zbu8No/fqK3rxSZaYeHYant7zRxQNIIli7S+qLS9tX9zQA==}
     engines: {node: '>=14'}
+    deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead.
 
   conventional-changelog-jquery@4.0.0:
     resolution: {integrity: sha512-TTIN5CyzRMf8PUwyy4IOLmLV2DFmPtasKN+x7EQKzwSX8086XYwo+NeaeA3VUT8bvKaIy5z/JoWUvi7huUOgaw==}
     engines: {node: '>=14'}
+    deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead.
 
   conventional-changelog-jshint@3.0.0:
     resolution: {integrity: sha512-bQof4byF4q+n+dwFRkJ/jGf9dCNUv4/kCDcjeCizBvfF81TeimPZBB6fT4HYbXgxxfxWXNl/i+J6T0nI4by6DA==}
     engines: {node: '>=14'}
+    deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead.
 
   conventional-changelog-preset-loader@3.0.0:
     resolution: {integrity: sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA==}
@@ -4389,11 +4415,13 @@ packages:
   git-raw-commits@2.0.11:
     resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==}
     engines: {node: '>=10'}
+    deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.
     hasBin: true
 
   git-raw-commits@3.0.0:
     resolution: {integrity: sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==}
     engines: {node: '>=14'}
+    deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.
     hasBin: true
 
   git-remote-origin-url@2.0.0:
@@ -4403,6 +4431,7 @@ packages:
   git-semver-tags@5.0.1:
     resolution: {integrity: sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==}
     engines: {node: '>=14'}
+    deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.
     hasBin: true
 
   gitconfiglocal@1.0.0:
@@ -4418,11 +4447,12 @@ packages:
 
   glob@10.4.5:
     resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+    deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
     hasBin: true
 
   glob@7.2.3:
     resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
-    deprecated: Glob versions prior to v9 are no longer supported
+    deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
 
   global-directory@4.0.1:
     resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
@@ -5409,6 +5439,9 @@ packages:
   lines-and-columns@1.2.4:
     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
 
+  linkify-it@5.0.2:
+    resolution: {integrity: sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==}
+
   lint-staged@14.0.1:
     resolution: {integrity: sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw==}
     engines: {node: ^16.14.0 || >=18.0.0}
@@ -5581,6 +5614,10 @@ packages:
     resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==}
     engines: {node: '>=0.10.0'}
 
+  markdown-it@14.3.0:
+    resolution: {integrity: sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==}
+    hasBin: true
+
   marked@17.0.1:
     resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==}
     engines: {node: '>= 20'}
@@ -5602,6 +5639,9 @@ packages:
   mdn-data@2.0.30:
     resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
 
+  mdurl@2.0.0:
+    resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
+
   memorystream@0.3.1:
     resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
     engines: {node: '>= 0.10.0'}
@@ -6290,6 +6330,10 @@ packages:
   pump@3.0.3:
     resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
 
+  punycode.js@2.3.1:
+    resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+    engines: {node: '>=6'}
+
   punycode@2.3.1:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
@@ -7268,6 +7312,9 @@ packages:
     engines: {node: '>=4.2.0'}
     hasBin: true
 
+  uc.micro@2.1.0:
+    resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+
   ufo@1.6.1:
     resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
 
@@ -7382,11 +7429,12 @@ packages:
 
   uuid@10.0.0:
     resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
+    deprecated: uuid@10 and below is no longer supported.  For ESM codebases, update to uuid@latest.  For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
     hasBin: true
 
   uuid@3.4.0:
     resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
-    deprecated: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
+    deprecated: uuid@10 and below is no longer supported.  For ESM codebases, update to uuid@latest.  For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
     hasBin: true
 
   v8-compile-cache-lib@3.0.1:
@@ -7553,6 +7601,7 @@ packages:
   vue-i18n@9.2.2:
     resolution: {integrity: sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==}
     engines: {node: '>= 14'}
+    deprecated: v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html
     peerDependencies:
       vue: ^3.0.0
 
@@ -7672,10 +7721,12 @@ packages:
 
   whatwg-encoding@1.0.5:
     resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==}
+    deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
 
   whatwg-encoding@2.0.0:
     resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
     engines: {node: '>=12'}
+    deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
 
   whatwg-mimetype@2.3.0:
     resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==}
@@ -9854,12 +9905,21 @@ snapshots:
     dependencies:
       '@types/node': 20.19.25
 
+  '@types/linkify-it@5.0.0': {}
+
   '@types/lodash-es@4.17.12':
     dependencies:
       '@types/lodash': 4.17.20
 
   '@types/lodash@4.17.20': {}
 
+  '@types/markdown-it@14.1.2':
+    dependencies:
+      '@types/linkify-it': 5.0.0
+      '@types/mdurl': 2.0.0
+
+  '@types/mdurl@2.0.0': {}
+
   '@types/minimist@1.2.5': {}
 
   '@types/mockjs@1.0.10': {}
@@ -12859,7 +12919,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  http-proxy@1.18.1:
+  http-proxy@1.18.1(debug@4.4.3):
     dependencies:
       eventemitter3: 4.0.7
       follow-redirects: 1.15.11(debug@4.4.3)
@@ -12867,14 +12927,14 @@ snapshots:
     transitivePeerDependencies:
       - debug
 
-  http-server@14.1.1:
+  http-server@14.1.1(debug@4.4.3):
     dependencies:
       basic-auth: 2.0.1
       chalk: 4.1.2
       corser: 2.0.1
       he: 1.2.0
       html-encoding-sniffer: 3.0.0
-      http-proxy: 1.18.1
+      http-proxy: 1.18.1(debug@4.4.3)
       mime: 1.6.0
       minimist: 1.2.8
       opener: 1.5.2
@@ -14089,6 +14149,10 @@ snapshots:
 
   lines-and-columns@1.2.4: {}
 
+  linkify-it@5.0.2:
+    dependencies:
+      uc.micro: 2.1.0
+
   lint-staged@14.0.1:
     dependencies:
       chalk: 5.3.0
@@ -14263,6 +14327,15 @@ snapshots:
     dependencies:
       object-visit: 1.0.1
 
+  markdown-it@14.3.0:
+    dependencies:
+      argparse: 2.0.1
+      entities: 4.5.0
+      linkify-it: 5.0.2
+      mdurl: 2.0.0
+      punycode.js: 2.3.1
+      uc.micro: 2.1.0
+
   marked@17.0.1: {}
 
   math-intrinsics@1.1.0: {}
@@ -14279,6 +14352,8 @@ snapshots:
 
   mdn-data@2.0.30: {}
 
+  mdurl@2.0.0: {}
+
   memorystream@0.3.1: {}
 
   meow@10.1.5:
@@ -14963,6 +15038,8 @@ snapshots:
       end-of-stream: 1.4.5
       once: 1.4.0
 
+  punycode.js@2.3.1: {}
+
   punycode@2.3.1: {}
 
   pure-rand@6.1.0: {}
@@ -16039,6 +16116,8 @@ snapshots:
 
   typescript@4.9.5: {}
 
+  uc.micro@2.1.0: {}
+
   ufo@1.6.1: {}
 
   uglify-js@3.19.3:

+ 9 - 0
pnpm-workspace.yaml

@@ -0,0 +1,9 @@
+allowBuilds:
+  '@swc/core': true
+  '@vue-office/docx': true
+  '@vue-office/excel': true
+  '@vue-office/pdf': true
+  core-js: true
+  es5-ext: true
+  esbuild: true
+  vue-demi: true

BIN
public/sparyVideo.mp4


BIN
public/texture/earth.jpg


BIN
public/texture/earth3.png


BIN
public/video/fanLocal.webm


BIN
src/assets/images/company/home-pageBg.png


BIN
src/assets/images/fireNew/FireBj.png


BIN
src/assets/images/gif/fanlocal.gif


BIN
src/assets/images/home-container/configurable/minehome/bg.png


BIN
src/assets/images/home-warn/bg.png


BIN
src/assets/images/vent/homeNew/bg.png


BIN
src/assets/images/vent/loginDataCenter/dataCenterBg.png


BIN
src/assets/images/vent/loginNeiye/bg.png


BIN
src/assets/images/zd-model.png


BIN
src/assets/video/fanLocal.mp4


BIN
src/assets/video/gate.mp4


BIN
src/assets/video/mainFan.mp4


BIN
src/assets/video/output_video.mp4


+ 1 - 1
src/hooks/system/useCameraPianation.ts

@@ -1,5 +1,5 @@
 import { defHttp } from '/@/utils/http/axios';
-import { render, nextTick } from 'vue';
+import { render, nextTick, ref } from 'vue';
 import { useDrag } from '../event/useDrag';
 import Player, { I18N } from 'xgplayer';
 import HlsPlugin from 'xgplayer-hls';

+ 44 - 4
src/views/vent/home/configurable/components/preset/SysWindCard.vue

@@ -2,8 +2,26 @@
 <template>
   <!-- 基准的画廊模块,通过不同的 type 展示不同的样式 -->
   <div ref="container" class="syswind__container w-full h-full">
-    <div v-for="item in data" :key="item.deviceID" class="syswind__card">
-      <!-- 画廊项的具体内容填充剩余宽度 -->
+    <!-- 画廊项的具体内容填充剩余宽度 -->
+    <div
+      v-for="item in data"
+      :key="item.deviceID"
+      class="syswind__card"
+      @mouseenter="getTrigger(item.deviceID).onEnter($event)"
+      @mousemove="getTrigger(item.deviceID).onMove($event)"
+      @mouseleave="getTrigger(item.deviceID).onLeave()"
+    >
+      <!-- 数据解读悬浮按钮(公共组件) -->
+      <DataInterpretationBtn
+        v-if="getTrigger(item.deviceID).state.visible"
+        :x="getTrigger(item.deviceID).state.x"
+        :y="getTrigger(item.deviceID).state.y"
+        @click="handleInterpret(item)"
+        @btn-enter="getTrigger(item.deviceID).onBtnEnter()"
+        @btn-leave="getTrigger(item.deviceID).onBtnLeave()"
+      />
+
+      <!-- 卡片内容 -->
       <div class="syswind__card__title font-bold" :title="getFormattedText(item, config.title)">{{ getFormattedText(item, config.title) }}</div>
       <div class="flex items-center justify-between syswind__card__vol_wrapper">
         <div class="syswind__card__vol_image" :class="{ overlimit: isVolumnOverLimit(item) }"></div>
@@ -31,11 +49,24 @@
             }"
           >
             <span>{{ getFormattedText(item, ctx.label) }}</span>
-            <span class=".font-bold">{{ getFormattedText(item, ctx.value) }}</span>
+            <span class="font-bold">{{ getFormattedText(item, ctx.value) }}</span>
           </div>
         </div>
       </div>
     </div>
+
+    <!-- 数据解读弹框(公共组件) -->
+    <DataInterpretationModal
+      :visible="modalVisible"
+      :loading="loading"
+      :streaming-text="streamingText"
+      :error="error"
+      :thinking="thinking"
+      :todos="todos"
+      :executing-tools="executingTools"
+      :tool-calls="toolCalls"
+      @close="handleClose"
+    />
   </div>
 </template>
 <script lang="ts" setup>
@@ -46,6 +77,7 @@
 
   // import { get } from 'lodash-es';
   // import { computed } from 'vue';
+  import { useDataInterpretationList, DataInterpretationBtn, DataInterpretationModal } from '/@/views/ventAI/dataPicker';
 
   const props = defineProps<{
     config: {
@@ -57,8 +89,15 @@
     data: any[];
   }>();
   //   defineEmits(['click']);
-
   // 判断val是否在low、high指定的范围内
+
+  const { modalVisible, loading, streamingText, error, thinking, todos, executingTools, toolCalls, getTrigger, handleInterpret, handleClose } =
+    useDataInterpretationList((item) => ({
+      device_id: item.deviceID,
+      device_name: item.strinstallpos,
+      device_type: 'windrect',
+    }));
+
   function isOverLimit(val: string, low: string = '', high: string = '') {
     const l = parseFloat(low);
     const h = parseFloat(high);
@@ -137,6 +176,7 @@
     --image-syswind-icon-spd: url(/@/assets/images/home-container/configurable/preset/syswind-icon-spd.svg);
     --image-syswind-icon-spd-red: url(/@/assets/images/home-container/configurable/preset/syswind-icon-spd-red.svg);
 
+    position: relative;
     background-image: var(--image-syswind-module);
     background-repeat: no-repeat;
     width: 320px;

+ 57 - 19
src/views/vent/monitorManager/deviceMonitor/components/device/modal/atomizing.modal.vue

@@ -72,15 +72,24 @@
               <span class="title">排污状态:</span>
               <div class="flex">
                 <div class="pr-10px"
-                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['airStatus'] === 'FF00' }"></span
+                  ><span
+                    class="signal-round signal-round-gry"
+                    :class="{ 'signal-round-run': atomizingMonitor[airStatusConfig[0].prop] === airStatusConfig[0].val }"
+                  ></span
                   ><span class="pl-10px">开启</span></div
                 >
                 <div class="pr-10px"
-                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['airStatus'] === '0000' }"></span
+                  ><span
+                    class="signal-round signal-round-gry"
+                    :class="{ 'signal-round-run': atomizingMonitor[airStatusConfig[1].prop] === airStatusConfig[1].val }"
+                  ></span
                   ><span class="pl-10px">关闭</span></div
                 >
                 <div class="pr-10px"
-                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['airStatus'] === '0001' }"></span
+                  ><span
+                    class="signal-round signal-round-gry"
+                    :class="{ 'signal-round-run': atomizingMonitor[airStatusConfig[2].prop] === airStatusConfig[2].val }"
+                  ></span
                   ><span class="pl-10px">停止</span></div
                 >
               </div>
@@ -88,14 +97,20 @@
             <div class="base-item flex">
               <span class="title">连接状态:</span>
               <div class="flex">
-                <div class="pr-10px"
-                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['connStatus'] === '0000' }"></span
-                  ><span class="pl-10px">未连接</span></div
-                >
-                <div class="pr-10px"
-                  ><span class="signal-round signal-round-gry" :class="{ 'signal-round-run': atomizingMonitor['connStatus'] === '0001' }"></span
-                  ><span class="pl-10px">已连接</span></div
-                >
+                <div class="pr-10px">
+                  <span
+                    class="signal-round signal-round-gry"
+                    :class="{ 'signal-round-run': atomizingMonitor[netStatusConfig[0].prop] === netStatusConfig[0].val }"
+                  ></span>
+                  <span class="pl-10px">未连接</span>
+                </div>
+                <div class="pr-10px">
+                  <span
+                    class="signal-round signal-round-gry"
+                    :class="{ 'signal-round-run': atomizingMonitor[netStatusConfig[1].prop] === netStatusConfig[1].val }"
+                  ></span>
+                  <span class="pl-10px">已连接</span>
+                </div>
               </div>
             </div>
             <div class="base-item flex">
@@ -198,7 +213,7 @@
             </div>
           </div>
         </div>
-        <div class="right-bottom">
+        <div class="right-bottom" v-if="posList && posList.length > 0">
           <span class="base-title">实时数据监测曲线</span>
           <div class="echarts-box">
             <BarAndLine xAxisPropType="time" :dataSource="posList" height="100%" :chartsColumns="chartsColumns" :option="echatsOption" />
@@ -330,7 +345,7 @@
             // })
             // if(list.length > 0) posList.value = list
             posList.value = item['history'];
-            atomizingMonitor.value = item.readData;
+            atomizingMonitor.value = { ...item, ...item.readData };
             activeDeviceType.value = item.deviceType;
           }
         });
@@ -401,15 +416,36 @@
       const atomizeConfig = computed(() => {
         if (activeDeviceType.value === 'atomizing_cy') {
           return [
-            { prop: 'deviceStatus', val: '1' },
-            { prop: 'deviceStatus', val: '0' },
-            { prop: 'deviceStatus', val: '2' },
+            { prop: 'deviceStatus', val: '-256' }, // 喷雾开启
+            { prop: 'deviceStatus', val: '0' }, // 喷雾关闭
+            { prop: 'deviceStatus', val: '2' }, // 喷雾停止
+          ];
+        }
+        return [
+          { prop: 'waterStatus', val: 'FF00' }, // 喷雾开启
+          { prop: 'waterStatus', val: '0000' }, // 喷雾关闭
+          { prop: 'waterStatus', val: '0001' }, // 喷雾停止
+        ];
+      });
+      // 排污状态配置:默认与 atomizing_cy 使用相同字段和值
+      const airStatusConfig = computed(() => {
+        return [
+          { prop: 'airStatus', val: 'FF00' }, // 排污开启
+          { prop: 'airStatus', val: '0000' }, // 排污关闭
+          { prop: 'airStatus', val: '0001' }, // 排污停止
+        ];
+      });
+      // 连接状态配置:默认与 atomizing_cy 使用相同字段和值
+      const netStatusConfig = computed(() => {
+        if (activeDeviceType.value === 'atomizing_cy') {
+          return [
+            { prop: 'connStatus', val: '0000' }, // 未连接
+            { prop: 'connStatus', val: '0001' }, // 已连接
           ];
         }
         return [
-          { prop: 'waterStatus', val: 'FF00' },
-          { prop: 'waterStatus', val: '0000' },
-          { prop: 'waterStatus', val: '0001' },
+          { prop: 'netStatus', val: 0 }, // 未连接
+          { prop: 'netStatus', val: 1 }, // 已连接
         ];
       });
 
@@ -433,6 +469,8 @@
         handlePasswordConfirm,
         workModeConfig,
         atomizeConfig,
+        airStatusConfig,
+        netStatusConfig,
       };
     },
   });

+ 12 - 7
src/views/vent/monitorManager/fanLocalMonitor/fanLocal.threejs.ts

@@ -95,8 +95,6 @@ export function setModelType(modelType: 'fanLocal' | 'fanLocalDual' | 'fanLocalS
         group.children.forEach((e) => {
           e.visible = true;
         });
-        debugger;
-
         // 模型发生了替换,需要使用摄像头动画// 模型不同需要不同的初始角度与位置
         if (type == 'fanLocal') {
           const oldCameraPosition = { x: 615, y: 275, z: 744 };
@@ -199,14 +197,21 @@ export const destroy = () => {
 // 为了兼容性而添加的方法导出
 export function addText(d, e) {
   if (!model) return;
-  if (modelContextList[0]) modelContextList[0].context?.addText(d);
-  if (modelContextList[1]) modelContextList[1].context?.addText(e);
-  if (modelContextList[2]) modelContextList[2].context?.addText(d);
+  modelContextList.forEach(({ type, context }) => {
+    if (!context || !context.addText) return;
+    // fanLocalDual 需要数组参数(左右风机数据),其余各模型接受单个对象
+    if (type === 'fanLocalDual') {
+      context.addText(e);
+    } else {
+      context.addText(d);
+    }
+  });
 }
 export function addCssText() {
   if (!model) return;
-  if (modelContextList[0] && modelContextList[0].context && modelContextList[0].context['addCssText']) modelContextList[0].context['addCssText']();
-  if (modelContextList[2] && modelContextList[2].context && modelContextList[2].context['addCssText']) modelContextList[2].context['addCssText']();
+  modelContextList.forEach(({ context }) => {
+    if (context && context['addCssText']) context['addCssText']();
+  });
 }
 export function playSmoke(d) {
   if (!model) return;

+ 1308 - 0
src/views/vent/safetyList/common/detail-235.vue

@@ -0,0 +1,1308 @@
+<template>
+  <div class="safetyList">
+    <customHeader>监控分站管理详情</customHeader>
+    <div class="content">
+      <a-tabs class="tab-box" v-model:activeKey="activeKey" @change="onChangeTab">
+        <a-tab-pane tab="分站监测" key="device" />
+        <a-tab-pane tab="监测详情" key="manageAuto" />
+        <a-tab-pane tab="操作记录" key="operationRecord" />
+      </a-tabs>
+      <div class="box-content">
+        <!-- 分站监测 -->
+        <div class="now-content" v-if="activeKey == 'device'">
+          <div class="left-box">
+            <div class="left-title">
+              <div class="title-fz">
+                <span>分站:</span>
+                <span>
+                  [通讯正常]
+                  <span class="zd-open">{{ openNum || 0 }}</span>
+                </span>
+                <span>
+                  [通讯中断]
+                  <span class="zd-close">{{ clsoeNum || 0 }}</span>
+                </span>
+              </div>
+              <div class="title-btn" style="margin-left: 55%">
+                <a-button type="primary" size="small" @click="getAllShow">显示全部</a-button>
+              </div>
+            </div>
+            <div class="left-content">
+              <div class="card-box" v-for="(item, index) in cardList" :key="index">
+                <div :class="[
+                  'card-item',
+                  {
+                    selected: selectedIndex === index,
+                    'card-itemL': item.linkstatus,
+                    'card-itemD': !item.linkstatus,
+                  },
+                ]" @click="cardClick(item, index)">
+                  <div class="card-item-label">{{ item.strname }}</div>
+                </div>
+                <div :class="activeIndex % 4 == 3 ? 'card-modal1' : 'card-modal'" v-if="activeIndex == index && isShow">
+                  <div class="modal-name">站点名称:</div>
+                  <a-input v-model:value="stationName" size="small" placeholder="请输入" @blur="changeName" />
+                  <div class="modal-lj">连接状态:</div>
+                  <a-radio-group v-model:value="stationStatus" size="small" :options="ljList" @change="changeStatus" />
+                  <a-popconfirm title="删除内容无法恢复,是否删除" ok-text="确定" cancel-text="取消" @confirm="handleDelStation"
+                    @cancel="handleCancelDelStation">
+                    <a-button type="primary" preIcon="ant-design:delete-outlined" size="mini"
+                      class="down-btn">删除</a-button>
+                  </a-popconfirm>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="right-box">
+            <div class="right-title">详细信息:</div>
+            <a-table size="small" :scroll="{ y: 680 }" :columns="columns" :data-source="tableData"
+              :pagination="pagination" @change="pageChange">
+              <template #action="{ record }">
+                <a-button v-if="!record.devInfoList" type="primary" :disabled="record.linkId != '0'" size="small"
+                  @click="handlerunDeviceMonitor(record, '启动')">启动</a-button>
+                <a-button type="success" size="small" style="margin: 0px 10px"
+                  @click="handlerunDeviceMonitor(record, '编辑')">编辑</a-button>
+                <a-popconfirm title="删除内容无法恢复,是否删除" ok-text="确定" cancel-text="取消"
+                  @confirm="handlerunDeviceMonitor(record, '删除')" @cancel="handleCancelDelStation">
+                  <a-button v-if="!record.devInfoList" type="success" size="small"
+                    style="margin-right:10px">删除</a-button>
+                </a-popconfirm>
+                <a-button type="primary" v-if="!record.devInfoList" size="small" @click="debugClick(record)">{{
+                  record.debugTitle }}</a-button>
+              </template>
+              <template #bodyCell="{ column, text }">
+                <template v-if="column.dataIndex === 'valueJc' && text">
+                  <div v-for="item in text.split(',')" :key="item">
+                    <span
+                      v-if="item.substring(item.indexOf(':') + 1) && !isNaN(parseFloat(item.substring(item.indexOf(':') + 1)))"
+                      style="display: inline-block; width: 42%; text-align: right; color: rgb(0, 242, 255); margin-right: 5px">{{
+                        item.substring(0, item.indexOf(':') + 1) }}</span>
+                    <span
+                      v-if="item.substring(item.indexOf(':') + 1) && !isNaN(parseFloat(item.substring(item.indexOf(':') + 1)))"
+                      style="display: inline-block; width: 52%; text-align: left; color: #fff">{{
+                        item.substring(item.indexOf(':') + 1) === '1'
+                          ? '正风'
+                          : item.substring(item.indexOf(':') + 1) === '2'
+                            ? '反风'
+                            : item.substring(item.indexOf(':') + 1)
+                      }}
+                    </span>
+                  </div>
+                </template>
+              </template>
+            </a-table>
+            <!-- 一键启动弹窗 -->
+            <a-modal style="top: 300px; left: 360px" v-model:visible="visibleModal" :width="450" title="一键启动"
+              @ok="handleOk" @cancel="handleCancel">
+              <a-form :model="startupData" labelAlign="right" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }">
+                <a-form-item label="安装位置">
+                  <a-input v-model:value="startupData.address" placeholder="请输入" style="width: 260px" />
+                </a-form-item>
+              </a-form>
+            </a-modal>
+            <!-- 编辑弹窗 -->
+            <a-modal style="top: 300px; left: 360px" v-model:visible="visibleModalEdit" :width="450" title="编辑信息"
+              @ok="handleOkEdit" @cancel="handleCancelEdit">
+              <a-form :model="startupDataEdit" labelAlign="right" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }">
+                <a-form-item label="安装位置">
+                  <a-input v-model:value="startupDataEdit.address" placeholder="请输入" style="width: 260px" />
+                </a-form-item>
+              </a-form>
+            </a-modal>
+            <!-- 调试弹窗 -->
+            <a-modal style="top: 300px; left: 360px" v-model:visible="visibleModalDebug" :width="450" title="调试信息"
+              @ok="handleOkDebug" @cancel="handleCancelDebug">
+              <a-form :model="startupDataDebug" labelAlign="right" :label-col="{ span: 12 }"
+                :wrapper-col="{ span: 12 }">
+                <a-form-item label="风速">
+                  <a-input type="number" v-model:value="startupDataDebug.speed" placeholder="请输入"
+                    style="width: 260px" />
+                </a-form-item>
+                <a-form-item label="风向">
+                  <a-select v-model:value="startupDataDebug.direction" style="width: 260px">
+                    <a-select-option v-for="file in derictList" :key="file.label" :value="file.value">{{ file.label
+                    }}</a-select-option>
+                  </a-select>
+                </a-form-item>
+                <a-form-item label="密码">
+                  <a-input type="password" v-model:value="startupDataDebug.pwd" placeholder="请输入"
+                    style="width: 260px" />
+                </a-form-item>
+                <a-form-item label="浮动范围">
+                  <a-input type="number" v-model:value="startupDataDebug.range" placeholder="请输入"
+                    style="width: 260px" />
+                </a-form-item>
+              </a-form>
+            </a-modal>
+            <!-- 结束调试弹窗 -->
+            <a-modal style="top: 300px; left: 360px" v-model:visible="visibleModalDebugEnd" :width="450" title="调试信息"
+              @ok="handleOkDebugEnd" @cancel="handleCancelDebugEnd">
+              <a-form :model="startupDataDebugEnd" labelAlign="right" :label-col="{ span: 12 }"
+                :wrapper-col="{ span: 12 }">
+                <a-form-item label="密码">
+                  <a-input type="password" v-model:value="startupDataDebugEnd.pwd" placeholder="请输入"
+                    style="width: 260px" />
+                </a-form-item>
+              </a-form>
+            </a-modal>
+          </div>
+        </div>
+
+        <!-- 监测详情 -->
+        <div class="detail-content" v-if="activeKey == 'manageAuto'">
+
+          <a-table size="small" :scroll="{ y: 710 }" :row-key="(record) => record.stationId"
+            :expandedRowKeys="expandedRowKeys" :columns="columnsDetail130" :data-source="tableData1"
+            @expand="tableExpand">
+
+            <template #bodyCell="{ column, text }"></template>
+            <template #expandedRowRender="{ record }">
+              <a-table :columns="deviceColumns130" :data-source="record.devInfoList" :pagination="false" maxWidth="340"
+                :scroll="{ x: 'max-content', y: 200 }">
+                <template #action="{ record }">
+                  <a-button v-if="hasPermission('operateRecord:return')" type="primary" size="small"
+                    @click="handleEdit(record)">编辑</a-button>
+                </template>
+              </a-table>
+            </template>
+          </a-table>
+          <!-- 编辑弹窗 -->
+          <a-modal v-model:visible="visibleModalEdit1" :width="1100" @cancel="cancenModal"
+            :bodyStyle="{ height: '680px', 'overflow-y': 'auto', 'margin-bottom': '15px' }" title="编辑信息" :footer="null">
+            <!-- <a-form :model="formView" labelAlign="right" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }">
+              <a-form-item label="第一路风速风向:">
+                <a-input v-model:value="formView.dylfsfx" placeholder="请输入" disabled
+                  style="width: 260px; margin-right: 10px" />
+              </a-form-item>
+              <a-form-item label="第一路报警状态:">
+                <a-input v-model:value="formView.dylbjzt" placeholder="请输入" disabled
+                  style="width: 260px; margin-right: 10px" />
+              </a-form-item>
+              <a-form-item label="第一路1发2收AD值:">
+                <a-input v-model:value="formView.dyl1f2sADz" placeholder="请输入" disabled
+                  style="width: 260px; margin-right: 10px" />
+              </a-form-item>
+              <a-form-item label="第一路2发1收AD值:">
+                <a-input v-model:value="formView.dyl2f1sADz" placeholder="请输入" disabled
+                  style="width: 260px; margin-right: 10px" />
+              </a-form-item>
+              <a-form-item label="通风量:">
+                <a-input v-model:value="formView.tfl" placeholder="请输入" disabled
+                  style="width: 260px; margin-right: 10px" />
+              </a-form-item>
+              <a-form-item label="硬件版本:">
+                <a-input v-model:value="formView.yjbb" placeholder="请输入" disabled
+                  style="width: 260px; margin-right: 10px" />
+              </a-form-item>
+              <a-form-item label="软件版本:">
+                <a-input v-model:value="formView.rjbb" placeholder="请输入" disabled
+                  style="width: 260px; margin-right: 10px" />
+              </a-form-item>
+              <a-form-item label="在线离线标志:">
+                <a-input v-model:value="formView.zxlxbz" placeholder="请输入" disabled
+                  style="width: 260px; margin-right: 10px" />
+              </a-form-item>
+              <a-form-item label="日期和时间:">
+                <a-input v-model:value="formView.rqsj" placeholder="请输入" disabled
+                  style="width: 260px; margin-right: 10px" />
+              </a-form-item>
+            </a-form> -->
+
+            <a-form :model="formEdit" labelAlign="right" layout="inline" :label-col="{ span: 7 }"
+              :wrapper-col="{ span: 17 }">
+              <!-- <a-form-item label="传感器设备:">
+                <a-select v-model:value="formEdit.cgq" @change="changeCgq" style="width: 260px; margin-right: 10px">
+                  <a-select-option v-for="(file, index) in cgqList" :key="index" :value="file.value">{{ file.label
+                  }}</a-select-option>
+                </a-select>
+                <a-button type="success" @click="getDeviceList">读取</a-button>
+              </a-form-item> -->
+              <a-form-item label="地址:">
+                <a-input v-model:value="formEdit.dz" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('地址')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="探头距离:">
+                <a-input v-model:value="formEdit.ttjl" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('探头距离')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="基线距离:">
+                <a-input v-model:value="formEdit.jxjl" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('基线距离')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="角度:">
+                <a-input v-model:value="formEdit.jd" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('角度')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="开启时长:">
+                <a-input v-model:value="formEdit.kqsz" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('开启时长')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="平滑周期:">
+                <a-input v-model:value="formEdit.phzq" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('平滑周期')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="上行运放倍数:">
+                <a-input v-model:value="formEdit.sxyfbs" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('上行运放倍数')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="下行运放倍数:">
+                <a-input v-model:value="formEdit.xxyfbs" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('下行运放倍数')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="风道截面积:">
+                <a-input v-model:value="formEdit.fdjmj" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('风道截面积')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="总体校准系数:">
+                <a-input v-model:value="formEdit.ztxzxs" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('总体校准系数')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="校准系数一:">
+                <a-input v-model:value="formEdit.xzxs1" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('校准系数一')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="校准系数二:">
+                <a-input v-model:value="formEdit.xzxs2" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('校准系数二')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="校准系数三:">
+                <a-input v-model:value="formEdit.xzxs3" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('校准系数三')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="校准系数四:">
+                <a-input v-model:value="formEdit.xzxs4" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('校准系数四')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="校准系数五:">
+                <a-input v-model:value="formEdit.xzxs5" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('校准系数五')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="校准系数六:">
+                <a-input v-model:value="formEdit.xzxs6" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('校准系数六')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="校准系数b:">
+                <a-input v-model:value="formEdit.xzxsb" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('校准系数b')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="系数b符号:">
+                <a-input v-model:value="formEdit.xsbfh" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('系数b符号')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="高报警阈值:">
+                <a-input v-model:value="formEdit.gbjyz" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('高报警阈值')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="低报警阈值:">
+                <a-input v-model:value="formEdit.dbjyz" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('低报警阈值')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="报警使能:">
+                <a-input v-model:value="formEdit.bjsn" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('报警使能')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="第一路485波特率:">
+                <a-input v-model:value="formEdit.dyl485btl" placeholder="请输入"
+                  style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('第一路485波特率')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="复位:">
+                <a-input v-model:value="formEdit.fw" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('复位')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="浮动范围:">
+                <a-input v-model:value="formEdit.range" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('浮动范围')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="测试模式:">
+                <a-select ref="select" v-model:value="formEdit.csms" placeholder="请选择..."
+                  style="width: 260px; margin-right: 10px">
+                  <a-select-option v-for="(item, index) in optionList" :key="index" :value="item.value">{{ item.label
+                  }}</a-select-option>
+                </a-select>
+                <a-button class="down-btn" type="primary" @click="handleClick('测试模式')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="测试风速:">
+                <a-input v-model:value="formEdit.csfs" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('测试风速')">下发</a-button>
+              </a-form-item>
+              <a-form-item label="测试风向:">
+                <a-select ref="select" v-model:value="formEdit.csfx" placeholder="请选择..."
+                  style="width: 260px; margin-right: 10px">
+                  <a-select-option v-for="(item, index) in optionList1" :key="index" :value="item.value">{{ item.label
+                  }}</a-select-option>
+                </a-select>
+                <a-button class="down-btn" type="primary" @click="handleClick('测试风向')">下发</a-button>
+              </a-form-item>
+            </a-form>
+          </a-modal>
+        </div>
+        <!-- 操作记录 -->
+        <div class="detail-content" v-if="activeKey == 'operationRecord'">
+          <operateRecord :operationData="operationData" @getSearchRecord="getSearchRecord"></operateRecord>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, nextTick, reactive, onMounted, onUnmounted, inject } from 'vue';
+import { usePermission } from '/@/hooks/web/usePermission';
+import {
+  subStationList,
+  get235List,
+  //   getList,
+  getListAll,
+  getEdit,
+  update130DevName,
+  runDeviceMonitor,
+  //   update158DevName,
+  updateDebugStatus,
+  get130StationData,
+  set130StationDevicesRead,
+  set130StationData,
+  get130StationDevices,
+  set130StationRead,
+  remove130Substation,
+  // get130SetLog,
+  remove130Device,
+  //   get158StationData,
+  //   set158StationDevicesRead,
+  //   set158StationData,
+  //   get158StationDevices,
+  //   set158StationRead,
+  //   remove158Substation,
+  get158SetLog,
+  //   remove158Device
+} from '../safetyList.api';
+
+import { columnsDetail130, columns, deviceColumns130, optionList, optionList1, derictList, pagination } from '../safetyList.data';
+
+import customHeader from '/@/components/vent/customHeader.vue';
+import operateRecord from './operateRecord.vue'
+import { AesEncryption } from '/@/utils/cipher';
+import { loginCipher } from '/@/settings/encryptionSetting';
+import { message } from 'ant-design-vue';
+
+
+let expandedRowKeys = reactive<any[]>([])
+const { hasPermission } = usePermission();
+let activeKey = ref('device');
+let cardList = ref<any[]>([]);
+let activeIndex = ref(null);
+let isShow = ref(false);
+let stationName = ref('');
+let stationStatus = ref(null);
+let ljList = reactive<any[]>([
+  { value: 0, label: '断开' },
+  { value: 1, label: '连接' },
+]);
+let selectedIndex = ref(0);
+let openNum = ref(0);
+let clsoeNum = ref(0);
+let tableData = ref<any[]>([]);
+let tableData1 = ref<any[]>([]);
+
+let visibleModalEdit = ref(false);
+let visibleModalEdit1 = ref(false);
+let formEdit = reactive({
+  id: '',
+  dz: '',
+  csms: '',
+  ttjl: '',
+  jxjl: '',
+  jd: '',
+  kqsz: '',
+  phzq: '',
+  sxyfbs: '',
+  xxyfbs: '',
+  fdjmj: '',
+  ztxzxs: '',
+  xzxs1: '',
+  xzxs2: '',
+  xzxs3: '',
+  xzxs4: '',
+  xzxs5: '',
+  xzxs6: '',
+  xzxsb: '',
+  xsbfh: '',
+  gbjyz: '',
+  dbjyz: '',
+  bjsn: '',
+  dyl485btl: '',
+  fw: '',
+  range: '',
+  csfs: '',
+  csfx: ''
+});
+// let formView = reactive({
+//   dylfsfx: '',
+//   dylbjzt: '',
+//   dyl1f2sADz: '',
+//   dyl2f1sADz: '',
+//   tfl: '',
+//   yjbb: '',
+//   rjbb: '',
+//   zxlxbz: '',
+//   rqsj: '',
+// });
+
+// let cgqList = reactive<any[]>([]);
+let devId = ref('');
+let startupDataEdit = reactive({
+  address: '',
+});
+//一键启动弹窗
+let visibleModal = ref(false);
+let startupData = reactive({
+  address: '',
+});
+let paramId = ref('');
+let startupDataDebug = reactive({
+  speed: '',
+  direction: '',
+  pwd: '',
+  range: '',
+});
+let visibleModalDebug = ref(false);
+let debugFlag = ref('');
+let debugStationId = ref('');
+let debugDeviceId = ref('');
+
+let devStationId = ref('');
+let stationId = ref('');
+
+//结束调试弹窗相关信息
+let visibleModalDebugEnd = ref(false);
+let startupDataDebugEnd = reactive({
+  pwd: '',
+});
+let operationData = ref<any[]>([])//操作记录列表
+
+
+//定时刷新左侧分站数据
+let timer: null | NodeJS.Timeout = null;
+function getMonitor(flag = false) {
+  timer = setTimeout(
+    async () => {
+      if (activeKey.value == 'device') {
+        await getSubStationList();
+        await getStationList1();
+      } else if (activeKey.value == 'manageAuto') {
+        await getStationList();
+      }
+      if (timer) {
+        timer = null;
+      }
+      getMonitor();
+    },
+    flag ? 0 : 3000
+  );
+}
+
+let timer3: null | NodeJS.Timeout = null;
+function getMonitor3(flag = false) {
+  timer3 = setTimeout(
+    async () => {
+      await getStationListAll();
+      if (timer3) {
+        timer3 = null;
+      }
+      getMonitor3();
+    },
+    flag ? 0 : 3000
+  );
+}
+
+async function tableExpand(expaned, record) {
+  if (expaned) {
+    expandedRowKeys.push(record.stationId)
+  } else {
+    expandedRowKeys.length = 0
+  }
+}
+
+
+//tab选项切换
+async function onChangeTab(tab) {
+  activeKey.value = tab;
+  stationId.value = ''
+  clearTimeout(timer);
+  clearTimeout(timer3)
+  if (activeKey.value == 'device') {
+    // await getSubStationList();
+    // await getStationList1();
+    getMonitor()
+  } else if (activeKey.value == 'manageAuto') {
+    await getStationList();
+    getMonitor();
+  } else if (activeKey.value == 'operationRecord') {
+    await getSearchRecord({ stationId: '', deviceId: '' })
+  }
+}
+
+//弹窗关闭清空数据
+function cancenModal() {
+  Object.assign(formEdit, {
+    id: '',
+    dz: '',
+    csms: '',
+    ttjl: '',
+    jxjl: '',
+    jd: '',
+    kqsz: '',
+    phzq: '',
+    sxyfbs: '',
+    xxyfbs: '',
+    fdjmj: '',
+    ztxzxs: '',
+    xzxs1: '',
+    xzxs2: '',
+    xzxs3: '',
+    xzxs4: '',
+    xzxs5: '',
+    xzxs6: '',
+    xzxsb: '',
+    xsbfh: '',
+    gbjyz: '',
+    dbjyz: '',
+    bjsn: '',
+    dyl485btl: '',
+    fw: '',
+    range: '',
+    csfs: '',
+    csfx: '',
+  });
+}
+//获取详细信息列表
+async function getStationList() {
+  let res = await get235List();
+  tableData1.value = res.map((el: any) => {
+    el.devInfoList = el.devInfoList.map(v => {
+      return { ...v, ...v.param, stationId: el.id }
+    })
+    el.key = el.id
+    el = { ...el, ...el.station235Param }
+    return el
+  });
+}
+// //传感器选项切换
+// function changeCgq(val) {
+//   formEdit.cgq = val;
+// }
+//编辑
+async function handleEdit(record) {
+  // cgqList.length = 0;
+  visibleModalEdit1.value = true;
+  devStationId.value = record.stationId;
+  formEdit.id = record.id
+  // let res = await get130StationDevices({ stationId: devStationId.value });
+  // if (res.length != 0) {
+  //   res.forEach((el) => {
+  //     cgqList.push({ label: el.strinstallpos, value: el.id });
+  //   });
+  // }
+}
+
+//下发
+async function handleClick(data) {
+  switch (data) {
+    case '地址':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'dz', value: formEdit.dz });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '测试模式':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'csms', value: formEdit.csms });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '探头距离':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'ttjl', value: formEdit.ttjl });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '基线距离':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'jxjl', value: formEdit.jxjl });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '角度':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'jd', value: formEdit.jd });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '开启时长':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'kqsz', value: formEdit.kqsz });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '平滑周期':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'phzq', value: formEdit.phzq });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '上行运放倍数':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'sxyfbs', value: formEdit.sxyfbs });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '下行运放倍数':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'xxyfbs', value: formEdit.xxyfbs });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '风道截面积':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'fdjmj', value: formEdit.fdjmj });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '总体校准系数':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'ztxzxs', value: formEdit.ztxzxs });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '校准系数一':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'xzxs1', value: formEdit.xzxs1 });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '校准系数二':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'xzxs2', value: formEdit.xzxs2 });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '校准系数三':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'xzxs3', value: formEdit.xzxs3 });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '校准系数四':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'xzxs4', value: formEdit.xzxs4 });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '校准系数五':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'xzxs5', value: formEdit.xzxs5 });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '校准系数六':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'xzxs6', value: formEdit.xzxs6 });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '校准系数b':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'xzxsb', value: formEdit.xzxsb });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '系数b符号':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'xsbfh', value: formEdit.xsbfh });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '高报警阈值':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'gbjyz', value: formEdit.gbjyz });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '低报警阈值':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'dbjyz', value: formEdit.dbjyz });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '报警使能':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'bjsn', value: formEdit.bjsn });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '第一路485波特率':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'dyl485btl', value: formEdit.dyl485btl });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '复位':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'fw', value: formEdit.fw });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '浮动范围':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'range', value: formEdit.range });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '测试风速':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'csfs', value: formEdit.csfs });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+    case '测试风向':
+      await set130StationData({ stationId: devStationId.value, deviceId: formEdit.id, plcCode: 'csfx', value: formEdit.csfx });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
+  }
+}
+
+//获取分站实时监测信息
+async function getSubStationList() {
+  let res = await subStationList({ strtype: 'modbus', reqparam: '235' });
+  if (res.length != 0) {
+    cardList.value = res;
+    stationId.value = stationId.value ? stationId.value : cardList.value[0].id
+    openNum.value = cardList.value?.filter((v) => v.linkstatus == 1)['length'];
+    clsoeNum.value = cardList.value?.filter((v) => v.linkstatus == 0)['length'];
+  } else {
+    cardList.value = [];
+  }
+}
+//分站站点选项点击
+function cardClick(item, index) {
+  clearTimeout(timer3)
+  clearTimeout(timer)
+  selectedIndex.value = index; // 更新选中索引
+  activeIndex.value =  !item.linkstatus ? index : null;
+  stationName.value = item.strname;
+  stationStatus.value = item.linkstatus;
+  stationId.value = item.id;
+  isShow.value = true;
+  getStationList1();
+  getMonitor()
+}
+//分站站点名称编辑
+function changeName(val) {
+  getChangeStation();
+}
+async function getChangeStation() {
+  let res = await getEdit({ id: stationId.value, strname: stationName.value, linkstatus: stationStatus.value });
+  getSubStationList();
+  isShow.value = false;
+}
+//站点连接状态修改
+function changeStatus(val) {
+  getChangeStation();
+}
+//获取详细信息列表
+async function getStationList1() {
+  let res = await get235List({ subId: stationId.value, pageNo: pagination.current, pageSize: pagination.pageSize });
+  if (res && res.length != 0) {
+    res.forEach((el) => {
+      el.key = el.id;
+      el.linkIdC = el.linkId || '';
+      el.stripC = el.strip || '';
+      el.linkstatusC = el.linkstatus ? '连接' : '断开';
+      el.gdmsC = el.gdms == '1' ? '直流供电' : el.gdms == '0' ? '交流供电' : '';
+      el.children = el.devInfoList;
+      el.children.forEach((v) => {
+        v.key = v.id;
+        v.debugTitle = v.deviceType == 'windrect_ds_two_130' ? v.status === 1 ? '结束调试' : v.status === 0 ? '调试' : '' : ''
+        v.stripC = v.strserno || '';
+        v.linkstatusC = v.netStatus ? '连接' : '断开';
+        v.linkIdC = v.linkId == '0' ? '未启用' : v.linkId == '1' ? '启用' : v.linkId == '2' ? '设备异常' : '';
+        v.updateTime = v.time;
+        v.gdmsC = v.gdms == '1' ? '直流供电' : v.gdms == '0' ? '交流供电' : '';
+        v.valueJc = `风向:${v.forward || ''},风量:${v.m3 || ''}m³/min,风速:${v.windSpeed || ''}m/s,气压:${v.pa || ''}Pa,压差:${v.difPress || ''
+          }Pa,温度:${v.temperature || ''}℃,湿度:${v.humidity || ''}%,断面积:${v.area || ''}㎡,ch4:${v.ch4 || ''}%`;
+      });
+    });
+  }
+
+  tableData.value = res;
+  pagination.total = res.total;
+}
+async function getStationListAll() {
+  let res = await get235List();
+  res.forEach((el) => {
+    el.key = el.id;
+    el.linkIdC = el.linkId || '';
+    el.stripC = el.strip || '';
+    el.linkstatusC = el.linkstatus ? '连接' : '断开';
+    el.gdmsC = el.gdms == '1' ? '直流供电' : el.gdms == '0' ? '交流供电' : '';
+    el.children = el.devInfoList;
+    el.children.forEach((v) => {
+      v.key = v.id;
+      v.debugTitle = v.deviceType == 'windrect_ds_two_130' ? v.status === 1 ? '结束调试' : v.status === 0 ? '调试' : '' : ''
+      v.stripC = v.strserno || '';
+      v.linkstatusC = v.netStatus ? '连接' : '断开';
+      v.linkIdC = v.linkId == '0' ? '未启用' : v.linkId == '1' ? '启用' : v.linkId == '2' ? '设备异常' : '';
+      v.updateTime = v.time;
+      v.gdmsC = v.gdms == '1' ? '直流供电' : v.gdms == '0' ? '交流供电' : '';
+      v.valueJc = `风向:${v.forward || ''},风量:${v.m3 || ''}m³/min,风速:${v.windSpeed || ''}m/s,气压:${v.pa || ''}Pa,压差:${v.difPress || ''
+        }Pa,温度:${v.temperature || ''}℃,湿度:${v.humidity || ''}%,断面积:${v.area || ''}㎡,ch4:${v.ch4 || ''}%`;
+    });
+  });
+  
+  tableData.value = res;
+  // pagination.total = res.total;
+}
+//显示全部
+function getAllShow() {
+  clearTimeout(timer)
+  clearTimeout(timer3)
+  getStationListAll()
+  getMonitor3()
+}
+//启动新设备
+async function handlerunDeviceMonitor(record, val) {
+  devId.value = record.id;
+  switch (val) {
+    case '编辑':
+      visibleModalEdit.value = true;
+      startupDataEdit.address = record.strinstallpos;
+      paramId.value = record.devInfoList ? 'subId' : 'devId';
+      break;
+    case '启动':
+      visibleModal.value = true;
+      startupData.address = record.strinstallpos;
+      break;
+    case '删除':
+      await remove130Device({ devId: devId.value })
+      getStationList1()
+      break;
+  }
+}
+//分站,设备调试
+function debugClick(record) {
+  if (record.debugTitle == '调试') {
+    //正在调试中
+    startupDataDebug.speed = '';
+    startupDataDebug.direction = '';
+    startupDataDebug.pwd = ''
+    startupDataDebug.range = ''
+    visibleModalDebug.value = true;
+    debugFlag.value = 'device';
+    debugDeviceId.value = record.id;
+    tableData.value.forEach((el) => {
+      el.devInfoList.forEach((v) => {
+        if (v.id == debugDeviceId.value) {
+          debugStationId.value = el.id;
+        }
+      });
+    });
+    // record.debugTitle = '结束调试';
+
+  } else if (record.debugTitle == '结束调试') {
+    debugFlag.value = 'device';
+    debugDeviceId.value = record.id;
+    visibleModalDebugEnd.value = true
+    startupDataDebugEnd.pwd = ''
+    tableData.value.forEach((el) => {
+      el.devInfoList.forEach((v) => {
+        if (v.id == debugDeviceId.value) {
+          debugStationId.value = el.id;
+        }
+      });
+    });
+    // record.debugTitle = '调试';
+  }
+}
+//确定结束调试
+async function handleOkDebugEnd() {
+  const encryption = new AesEncryption({ key: loginCipher.key, iv: loginCipher.iv });
+  let res = await updateDebugStatus({ pwd: encryption.encryptByAES(startupDataDebugEnd.pwd), stationId: debugStationId.value, deviceId: debugDeviceId.value, debugFlag: '0' });
+  if (res.code == 500) {
+    message.error(res.message);
+  } else {
+    visibleModalDebugEnd.value = false;
+    getStationList1();
+  }
+}
+//取消结束调试
+function handleCancelDebugEnd() {
+  visibleModalDebugEnd.value = false;
+  debugFlag.value = '';
+  debugStationId.value = '';
+  debugDeviceId.value = '';
+}
+async function handleOk() {
+  let res = await runDeviceMonitor({ devId: devId.value, devName: startupData.address });
+  visibleModal.value = false;
+  getStationList1();
+}
+function handleCancel() {
+  visibleModal.value = false;
+  startupData.address = '';
+}
+//编辑
+async function handleOkEdit() {
+  if (paramId.value == 'subId') {
+    let res = await update130DevName({ subId: devId.value, devName: startupDataEdit.address });
+    visibleModalEdit.value = false;
+    getStationList1();
+  } else {
+    let res = await update130DevName({ devId: devId.value, devName: startupDataEdit.address });
+    visibleModalEdit.value = false;
+    getStationList1();
+  }
+}
+//取消编辑
+function handleCancelEdit() {
+  visibleModalEdit.value = false;
+  startupDataEdit.address = '';
+}
+//调试确认
+async function handleOkDebug() {
+  const encryption = new AesEncryption({ key: loginCipher.key, iv: loginCipher.iv });
+  let res = await updateDebugStatus({
+    stationId: debugStationId.value,
+    deviceId: debugDeviceId.value,
+    speed: startupDataDebug.speed,
+    direction: startupDataDebug.direction,
+    range: startupDataDebug.range,
+    pwd: encryption.encryptByAES(startupDataDebug.pwd),
+    debugFlag: '1',
+  });
+  if (res.code == 500) {
+    message.error(res.message);
+  } else {
+    visibleModalDebug.value = false;
+    getStationList1();
+  }
+}
+//调试取消
+function handleCancelDebug() {
+  visibleModalDebug.value = false;
+  // tableData.value.forEach((el) => {
+  //   el.devInfoList.forEach((v) => {
+  //     //  v.debugTitle = v.status===1 ? '结束调试'  : v.status===0 ? '调试' : ''
+  //     v.debugTitle = v.deviceType == 'windrect_ds_25x' || v.deviceType == 'windrect_ds_two' ? v.status === 1 ? '结束调试' : v.status === 0 ? '调试' : '' : ''
+  //   });
+  // });
+  debugFlag.value = '';
+  debugStationId.value = '';
+  debugDeviceId.value = '';
+}
+//分页切换
+function pageChange(val) {
+  pagination.current = val.current;
+  pagination.pageSize = val.pageSize;
+  getStationList1();
+}
+
+//删除左侧分站
+async function handleDelStation() {
+  let res = await remove130Substation({ stationId: stationId.value })
+  if (res) {
+    getSubStationList();
+    getStationList1();
+  }
+}
+//取消删除左侧分站
+function handleCancelDelStation() { }
+
+//操作记录查询
+async function getSearchRecord(param) {
+  let res = await get158SetLog({ stationType: 130, ...param })
+  if (res && res.length != 0) {
+    operationData.value = res.records
+  }
+}
+onMounted(async () => {
+  await getSubStationList();
+  await getStationList1();
+  getMonitor();
+});
+onUnmounted(() => {
+  [timer, timer3].forEach((t) => t && clearTimeout(t));
+  timer = timer3 = undefined;
+});
+</script>
+
+<style lang="less" scoped>
+.safetyList {
+  width: calc(100% - 20px);
+  height: calc(100% - 80px);
+  position: relative;
+  margin: 70px 10px 10px 10px;
+
+  .content {
+    position: relative;
+    width: 100%;
+    height: 100%;
+
+    .tab-box {
+      display: flex;
+      color: #fff;
+      position: relative;
+      background: linear-gradient(#001325, #012e4f);
+
+      :deep(.zxm-tabs-nav) {
+        margin: 0 !important;
+
+        .zxm-tabs-tab {
+          width: 180px;
+          height: 45px;
+          background: url('/@/assets/images/top-btn.png') center no-repeat;
+          background-size: cover;
+          display: flex;
+          justify-content: center;
+          font-size: 16px;
+        }
+
+        .zxm-tabs-tab-active {
+          width: 180px;
+          position: relative;
+          background: url('/@/assets/images/top-btn-select.png') center no-repeat;
+          background-size: cover;
+
+          .zxm-tabs-tab-btn {
+            color: #fff !important;
+          }
+        }
+
+        .zxm-tabs-ink-bar {
+          width: 0 !important;
+        }
+
+        .zxm-tabs-tab+.zxm-tabs-tab {
+          margin: 0 !important;
+        }
+      }
+    }
+
+    .box-content {
+      height: calc(100% - 50px);
+      padding-top: 10px;
+      box-sizing: border-box;
+
+      .now-content {
+        position: relative;
+        width: 100%;
+        height: 100%;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+
+        .left-box {
+          width: 40%;
+          height: 100%;
+          margin-right: 15px;
+          padding: 10px;
+          box-sizing: border-box;
+          background: url('/@/assets/images/fire/bj1.png') no-repeat center;
+          background-size: 100% 100%;
+
+          .left-title {
+            display: flex;
+            height: 30px;
+            align-items: center;
+            font-size: 14px;
+            margin-bottom: 10px;
+
+            span {
+              color: #fff;
+            }
+
+            .zd-open {
+              color: rgb(0, 242, 255);
+            }
+
+            .zd-close {
+              color: #ff0000;
+            }
+
+            .title-fz {
+              margin-right: 25px;
+            }
+          }
+
+          .left-content {
+            display: flex;
+            justify-content: flex-start;
+            align-items: flex-start;
+            flex-wrap: wrap;
+            height: calc(100% - 40px);
+            overflow-y: auto;
+
+            .card-box {
+              position: relative;
+              // width: 242px;
+              width: 182px;
+              height: 120px;
+              margin-bottom: 15px;
+              display: flex;
+              justify-content: center;
+
+              .card-itemN {
+                position: relative;
+                width: 85px;
+                height: 120px;
+                background: url('/@/assets/images/zd-2.png') no-repeat center;
+                background-size: 100% 100%;
+                cursor: pointer;
+
+                .card-item-label {
+                  width: 100%;
+                  position: absolute;
+                  bottom: 5px;
+                  font-size: 12px;
+                  color: #fff;
+                  text-align: center;
+                }
+              }
+
+              .card-itemL {
+                position: relative;
+                width: 85px;
+                height: 120px;
+                background: url('/@/assets/images/zd-3.png') no-repeat center;
+                background-size: 100% 100%;
+                cursor: pointer;
+
+                .card-item-label {
+                  width: 100%;
+                  position: absolute;
+                  bottom: 5px;
+                  font-size: 12px;
+                  color: #fff;
+                  text-align: center;
+                }
+              }
+
+              .card-itemD {
+                position: relative;
+                width: 85px;
+                height: 120px;
+                background: url('/@/assets/images/zd-1.png') no-repeat center;
+                background-size: 100% 100%;
+                cursor: pointer;
+
+                .card-item-label {
+                  width: 100%;
+                  position: absolute;
+                  bottom: 5px;
+                  font-size: 12px;
+                  color: #fff;
+                  text-align: center;
+                }
+              }
+
+              .card-modal {
+                width: 86px;
+                position: absolute;
+                left: 140px;
+                color: #fff;
+                top: 50%;
+                transform: translate(0, -50%);
+                font-size: 12px;
+              }
+
+              .card-modal1 {
+                width: 86px;
+                position: absolute;
+                left: -42px;
+                color: #fff;
+                top: 50%;
+                transform: translate(0, -50%);
+                font-size: 12px;
+              }
+            }
+          }
+        }
+
+        .right-box {
+          width: calc(60% - 15px);
+          height: 100%;
+          padding: 10px;
+          box-sizing: border-box;
+          background: url('/@/assets/images/fire/bj1.png') no-repeat center;
+          background-size: 100% 100%;
+
+          .right-title {
+            display: flex;
+            height: 30px;
+            align-items: center;
+            font-size: 14px;
+            color: #fff;
+            margin-bottom: 10px;
+          }
+        }
+      }
+
+      .detail-content {
+        width: 100%;
+        height: 100%;
+      }
+
+
+    }
+  }
+}
+
+.down-btn {
+  line-height: 15px;
+  height: 20px;
+  padding: 0px 17px;
+  font-size: 12px;
+}
+
+.zxm-form {
+  width: 100%;
+  height: 100%;
+  padding-top: 20px !important;
+  box-sizing: border-box;
+
+  .zxm-form-item {
+    width: 50%;
+    margin-right: 0px
+  }
+}
+
+.zxm-picker,
+.zxm-input {
+  border: 1px solid #3ad8ff77 !important;
+  background-color: #ffffff !important;
+  color: #fff !important;
+}
+
+.card-item.selected {
+  border: 2px solid #3ad8ff77;
+  /* 选中时的边框颜色 */
+}
+
+::v-deep(.zxm-radio-wrapper) {
+  font-size: 12px;
+}
+
+::v-deep(.zxm-input) {
+  font-size: 12px;
+}
+
+::v-deep(.zxm-select:not(.zxm-select-customize-input) .zxm-select-selector) {
+  border: 1px solid #3ad8ff77 !important;
+}
+</style>
+<style>
+div[aria-hidden='true'] {
+  display: none !important;
+}
+</style>

+ 170 - 89
src/views/vent/safetyList/common/detail.vue

@@ -518,8 +518,11 @@ let startupDataDebugEnd = reactive({
 });
 
 let operationData = ref<any[]>([])//操作记录列表
-//定时刷新左侧分站数据
 let timer: null | NodeJS.Timeout = null;
+/**
+ * 获取监控数据并设置轮询定时器
+ * @param flag - 是否立即执行(true为立即执行,false为延迟3秒执行)
+ */
 function getMonitor(flag = false) {
   timer = setTimeout(
     async () => {
@@ -574,26 +577,38 @@ function getMonitor3(flag = false) {
     flag ? 0 : 3000
   );
 }
+/**
+ * 获取设备列表数据
+ * 调用站点读取接口获取设备信息,成功后刷新监测数据
+ */
 async function getDeviceList() {
   let res = await set158StationRead({ stationId: devStationId.value, deviceId: formEdit.cgq });
   if (res) {
     getMonitor2(true)
   }
 }
+/**
+ * 获取并显示158站点数据
+ * 从158站点数据列表中筛选出当前设备站点对应的数据,
+ * 将相关测量值(电压、流量、液位、报警等)赋值到表单视图对象
+ */
 async function getShowReadList() {
   let data = await get158StationData();
-  if (data && data.length != 0) {
-    let list = data.filter((v) => v.stationId == devStationId.value)[0];
-    formView.dylfsfx = list.dylfsfx
-    formView.dyl1f2sADz = list.dyl1f2sADz
-    formView.dyl2f1sADz = list.dyl2f1sADz
-    formView.tfl = list.tfl
-    formView.yjbb = list.yjbb
-    formView.rjbb = list.rjbb
-    formView.zxlxbz = list.zxlxbz
-    formView.rqsj = list.rqsj
+  if (data && data.length) {
+    let list = data.find((v) => v.stationId == devStationId.value);
+    if (list) {
+      const { dylfsfx, dyl1f2sADz, dyl2f1sADz, tfl, yjbb, rjbb, zxlxbz, rqsj } = list;
+      Object.assign(formView, { dylfsfx, dyl1f2sADz, dyl2f1sADz, tfl, yjbb, rjbb, zxlxbz, rqsj });
+    }
   }
 }
+/**
+ * 表格展开/收起处理函数
+ *
+ * @param expaned - 是否展开,true为展开,false为收起
+ * @param record - 当前行的记录数据,包含stationId字段
+ * @returns Promise<void>
+ */
 async function tableExpand(expaned, record) {
   let res = await set158StationDevicesRead({ stationId: record.stationId })
   if (res && expaned) {
@@ -607,84 +622,111 @@ async function tableExpand(expaned, record) {
 }
 
 
-//tab选项切换
+/**
+ * 标签页切换处理函数
+ *
+ * 根据选中的标签页类型执行相应的数据加载操作:
+ * - device: 加载设备监控数据
+ * - history: 加载子站点列表、站点列表及监控数据
+ * - manageAuto: 加载站点列表及监控数据1
+ * - operationRecord: 加载操作记录
+ *
+ * 切换时会清空定时器并重置站点ID
+ *
+ * @param {string} tab - 切换到的标签页标识
+ * @returns {Promise<void>}
+ */
 async function onChangeTab(tab) {
   activeKey.value = tab;
-  stationId.value = ''
-  clearTimeout(timer);
-  clearTimeout(timer1);
-  clearTimeout(timer2)
-  clearTimeout(timer3)
-  if (activeKey.value == 'device') {
-    // await getSubStationList();
-    // await getStationList1();
-    getMonitor()
-  } else if (activeKey.value == 'history') {
+  stationId.value = '';
+  [timer, timer1, timer2, timer3].forEach((t) => t && clearTimeout(t));
+
+  if (activeKey.value === 'device') {
+    getMonitor();
+  } else if (activeKey.value === 'history') {
     await getSubStationList();
     await getStationList();
-    getMonitor()
-  } else if (activeKey.value == 'manageAuto') {
+    getMonitor();
+  } else if (activeKey.value === 'manageAuto') {
     await getStationList();
     getMonitor1(true);
-  } else if (activeKey.value == 'operationRecord') {
-    await getSearchRecord({ stationId: '', deviceId: '' })
+  } else if (activeKey.value === 'operationRecord') {
+    await getSearchRecord({ stationId: '', deviceId: '' });
   }
 }
 
-//弹窗关闭
+/**
+ * 取消模态框
+ * 清除表单编辑数据和查看数据,重置所有字段为空字符串
+ */
 function cancenModal() {
-  clearTimeout(timer2)
-  formEdit.id = ''
-  formEdit.cgq = ''
-  formEdit.rs485modbusdz = ''
-  formEdit.ttazjl = ''
-  formEdit.jxjl = ''
-  formEdit.azjd = ''
-  formEdit.szsz = ''
-  formEdit.sjpjzq = ''
-  formEdit.dylyfesPGz = ''
-  formEdit.dylefysPGz = ''
-  formEdit.fdjmj = ''
-  formEdit.dylztxsk = ''
-  formEdit.dyldydxs1 = ''
-  formEdit.dyldedxs2 = ''
-  formEdit.dyldsdxs3 = ''
-  formEdit.dyldsdxs4 = ''
-  formEdit.dyldwdxs5 = ''
-  formEdit.dyldldxs6 = ''
-  formEdit.xsKB = ''
-  formEdit.xsKBfh = ''
-  formEdit.gbjyz = ''
-  formEdit.dbjyz = ''
-  formEdit.bjsn = ''
-  formEdit.dyl485btl = ''
-  formEdit.sgzjbcmm = ''
-  formEdit.fw = ''
-
-  formView.dylfsfx = ''
-  formView.dylbjzt = ''
-  formView.dyl1f2sADz = ''
-  formView.dyl2f1sADz = ''
-  formView.tfl = ''
-  formView.yjbb = ''
-  formView.rjbb = ''
-  formView.zxlxbz = ''
-  formView.rqsj = ''
+  clearTimeout(timer2);
+  Object.assign(formEdit, {
+    id: '',
+    cgq: '',
+    rs485modbusdz: '',
+    ttazjl: '',
+    jxjl: '',
+    azjd: '',
+    szsz: '',
+    sjpjzq: '',
+    dylyfesPGz: '',
+    dylefysPGz: '',
+    fdjmj: '',
+    dylztxsk: '',
+    dyldydxs1: '',
+    dyldedxs2: '',
+    dyldsdxs3: '',
+    dyldsdxs4: '',
+    dyldwdxs5: '',
+    dyldldxs6: '',
+    xsKB: '',
+    xsKBfh: '',
+    gbjyz: '',
+    dbjyz: '',
+    bjsn: '',
+    dyl485btl: '',
+    sgzjbcmm: '',
+    fw: '',
+  });
+  Object.assign(formView, {
+    dylfsfx: '',
+    dylbjzt: '',
+    dyl1f2sADz: '',
+    dyl2f1sADz: '',
+    tfl: '',
+    yjbb: '',
+    rjbb: '',
+    zxlxbz: '',
+    rqsj: '',
+  });
 }
-//获取详细信息列表
+/**
+ * 获取158协议站点列表数据
+ * 调用接口获取站点数据,并对数据进行转换处理:
+ * - 将连接状态布尔值转换为中文文本
+ * - 设置唯一键值
+ */
 async function getStationList() {
   let res = await get158StationData();
-  res.forEach((el) => {
+  tableData1.value = res.map((el) => {
     el.linkstatusC = el.linkstatus ? '连接' : '断开';
-    el.key = el.stationId
+    el.key = el.stationId;
+    return el;
   });
-  tableData1.value = res;
 }
-//传感器选项切换
+/**
+ * 传感器选项切换
+ * @param {any} val - 选中的传感器值
+ */
 function changeCgq(val) {
   formEdit.cgq = val;
 }
-//编辑
+/**
+ * 打开编辑弹窗并加载站点设备列表
+ * @param {Object} record - 包含站点信息的记录对象
+ * @param {string|number} record.stationId - 站点ID
+ */
 async function handleEdit(record) {
   cgqList.length = 0;
   visibleModalEdit1.value = true;
@@ -697,7 +739,39 @@ async function handleEdit(record) {
   }
 }
 
-//下发
+/**
+ * 下发配置项数据到站点
+ *
+ * 根据传入的配置项类型,将对应的配置数据下发给指定站点的设备,
+ * 下发成功后关闭弹窗并刷新站点列表
+ *
+ * @param data - 配置项类型,支持以下类型:
+ *   - 'RS485_MODBUS地址'
+ *   - '探头安装距离'
+ *   - '基线距离'
+ *   - '安装角度'
+ *   - '设置时长'
+ *   - '数据平均周期'
+ *   - '第一路一发二收PG值'
+ *   - '第一路二发一收PG值'
+ *   - '风道截面积'
+ *   - '第一路整体系数k'
+ *   - '第一路第一段系数'
+ *   - '第一路第二段系数'
+ *   - '第一路第三段系数'
+ *   - '第一路第四段系数'
+ *   - '第一路第五段系数'
+ *   - '第一路第六段系数'
+ *   - '系数KB'
+ *   - '系数KB符号'
+ *   - '高报警阈值'
+ *   - '低报警阈值'
+ *   - '报警使能'
+ *   - '第一路485波特率'
+ *   - '四个字节保存密码'
+ *   - '复位'
+ * @returns 无返回值
+ */
 async function handleClick(data) {
   switch (data) {
     case 'RS485_MODBUS地址':
@@ -847,7 +921,10 @@ async function handleClick(data) {
   }
 }
 
-//获取分站实时监测信息
+/**
+ * 获取子站列表数据
+ * 调用modbus类型接口获取子站列表,并更新相关状态
+ */
 async function getSubStationList() {
   let res = await subStationList({ strtype: 'modbus', reqparam: '158' });
   if (res.length != 0) {
@@ -859,7 +936,11 @@ async function getSubStationList() {
     cardList.value = [];
   }
 }
-//分站站点选项点击
+/**
+ * 分站站点选项点击事件处理
+ * @param item - 站点数据对象
+ * @param index - 站点索引
+ */
 function cardClick(item, index) {
   clearTimeout(timer3)
   clearTimeout(timer)
@@ -876,6 +957,9 @@ function cardClick(item, index) {
 function changeName(val) {
   getChangeStation();
 }
+/**
+ * @description 更新站点信息并刷新列表
+ */
 async function getChangeStation() {
   let res = await getEdit({ id: stationId.value, strname: stationName.value, linkstatus: stationStatus.value });
   getSubStationList();
@@ -885,7 +969,11 @@ async function getChangeStation() {
 function changeStatus(val) {
   getChangeStation();
 }
-//获取详细信息列表
+/**
+ * 获取站点列表数据
+ *
+ * @returns {Promise<void>}
+ */
 async function getStationList1() {
   let res = await getList({ subId: stationId.value, pageNo: pagination.current, pageSize: pagination.pageSize });
   if (res && res.length != 0) {
@@ -917,6 +1005,11 @@ async function getStationList1() {
   tableData.value = res;
   pagination.total = res.total;
 }
+/**
+ * 获取所有站点列表并进行数据格式化
+ * 调用API获取站点数据,对站点及子设备进行中文标签转换,
+ * 生成表格展示所需的各类状态文本和传感器数据字符串
+ */
 async function getStationListAll() {
   let res = await getListAll();
   res.forEach((el) => {
@@ -1072,12 +1165,6 @@ async function handleOkDebug() {
 //调试取消
 function handleCancelDebug() {
   visibleModalDebug.value = false;
-  // tableData.value.forEach((el) => {
-  //   el.devInfoList.forEach((v) => {
-  //     //  v.debugTitle = v.status===1 ? '结束调试'  : v.status===0 ? '调试' : ''
-  //     v.debugTitle = v.deviceType == 'windrect_ds_25x' || v.deviceType == 'windrect_ds_two' ? v.status === 1 ? '结束调试' : v.status === 0 ? '调试' : '' : ''
-  //   });
-  // });
   debugFlag.value = '';
   debugStationId.value = '';
   debugDeviceId.value = '';
@@ -1110,17 +1197,11 @@ async function getSearchRecord(param) {
 onMounted(async () => {
   await getSubStationList();
   await getStationList1();
-  getMonitor()
+  getMonitor();
 });
 onUnmounted(() => {
-  if (timer) {
-    clearTimeout(timer);
-    timer = undefined;
-  }
-  if (timer1) {
-    clearTimeout(timer1);
-    timer1 = undefined;
-  }
+  [timer, timer1, timer2, timer3].forEach((t) => t && clearTimeout(t));
+  timer = timer1 = timer2 = timer3 = undefined;
 });
 </script>
 

+ 4 - 1
src/views/vent/safetyList/safetyList.api.ts

@@ -25,6 +25,7 @@ enum Api {
   remove130Device = '/safety/ventanalyDeviceInfo/remove130Device', //删除130分站传感器
   set130StationDevicesRead = '/safety/ventanalyDeviceInfo/set130StationDevicesRead',
   get130List = '/safety/ventanalyDeviceInfo/get130DeviceListBySubId',
+  get235List = '/safety/ventanalyDeviceInfo/getDeviceListBy235SubId',
 }
 
 // 分站查询接口
@@ -75,5 +76,7 @@ export const get130SetLog = (params) => defHttp.post({ url: Api.get130SetLog, pa
 export const remove130Device = (params) => defHttp.post({ url: Api.remove158Device, params }, { joinParamsToUrl: true });
 //130监测详情读取列表
 export const set130StationDevicesRead = (params) => defHttp.post({ url: Api.set130StationDevicesRead, params }, { joinParamsToUrl: true });
-// 分站详细信息列表
+// 130分站详细信息列表
 export const get130List = (params?) => defHttp.post({ url: Api.get130List, params });
+// 235分站详细信息列表
+export const get235List = (params?) => defHttp.post({ url: Api.get235List, params });

+ 94 - 0
src/views/ventAI/dataPicker/api.ts

@@ -0,0 +1,94 @@
+import type { SSERawEvent } from './types';
+
+enum Api {
+  clickTun = '/ventAI/api/interpret/click/tun',
+  clickDevice = '/ventAI/api/interpret/click/device',
+}
+
+/**
+ * 巷道数据解读(SSE 流式)
+ * @param params - { tun_id, tun_name }
+ * @param onEvent - 每个 SSE 事件的回调
+ * @returns 流完成后的 thread_id、session_id
+ */
+export async function fetchTunnelInterpretation(
+  params: { tun_id: string; tun_name: string },
+  onEvent: (event: SSERawEvent) => void
+): Promise<{ thread_id: string; session_id: string }> {
+  return ssePost(Api.clickTun, params, onEvent);
+}
+
+/**
+ * 设备数据解读(SSE 流式)
+ * @param params - { device_id, device_name, device_type }
+ * @param onEvent - 每个 SSE 事件的回调
+ * @returns 流完成后的 thread_id、session_id
+ */
+export async function fetchDeviceInterpretation(
+  params: { device_id: string; device_name: string; device_type: string },
+  onEvent: (event: SSERawEvent) => void
+): Promise<{ thread_id: string; session_id: string }> {
+  return ssePost(Api.clickDevice, params, onEvent);
+}
+
+/** SSE 流式 POST 请求的通用实现 */
+async function ssePost(
+  url: string,
+  body: Record<string, string>,
+  onEvent: (event: SSERawEvent) => void
+): Promise<{ thread_id: string; session_id: string }> {
+  const response = await fetch(url, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(body),
+  });
+
+  if (!response.ok) {
+    throw new Error(`HTTP错误: ${response.status}`);
+  }
+
+  const reader = response.body!.getReader();
+  const decoder = new TextDecoder();
+  let buffer = '';
+  let threadId = '';
+  let sessionId = '';
+  let isDone = false;
+
+  // eslint-disable-next-line no-constant-condition
+  while (true) {
+    const { done, value } = await reader.read();
+    if (done || isDone) break;
+
+    buffer += decoder.decode(value, { stream: true });
+    const lines = buffer.split('\n');
+    buffer = lines.pop() || '';
+
+    for (const line of lines) {
+      if (line.trim() === '') continue;
+
+      if (line.startsWith('data: ')) {
+        const dataStr = line.replace(/^data: /, '');
+
+        try {
+          const event: SSERawEvent = JSON.parse(dataStr);
+          onEvent(event);
+
+          if (event.type === 'done') {
+            threadId = event.thread_id || '';
+            sessionId = event.session_id || '';
+            isDone = true;
+            break;
+          }
+          if (event.type === 'error') {
+            isDone = true;
+            break;
+          }
+        } catch (e) {
+          console.warn('SSE数据解析失败:', dataStr);
+        }
+      }
+    }
+  }
+
+  return { thread_id: threadId, session_id: sessionId };
+}

+ 60 - 0
src/views/ventAI/dataPicker/components/DataInterpretationBtn.vue

@@ -0,0 +1,60 @@
+<template>
+  <div
+    class="di-floating-btn"
+    :style="{ left: x + 'px', top: y + 'px' }"
+    @click.stop="$emit('click')"
+    @mouseenter="$emit('btn-enter')"
+    @mouseleave="$emit('btn-leave')"
+  >
+    <span>数据解读</span>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  defineProps<{
+    x: number;
+    y: number;
+  }>();
+
+  defineEmits<{
+    click: [];
+    'btn-enter': [];
+    'btn-leave': [];
+  }>();
+</script>
+
+<style lang="less" scoped>
+  .di-floating-btn {
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 10;
+    padding: 3px 12px;
+    background: linear-gradient(135deg, #1e90ff, #4da6ff);
+    border-radius: 3px;
+    cursor: pointer;
+    font-size: 12px;
+    color: #fff;
+    letter-spacing: 1px;
+    white-space: nowrap;
+    pointer-events: auto;
+    box-shadow: 0 0 10px rgba(30, 144, 255, 0.4);
+    animation: diFadeIn 0.15s ease-out;
+
+    &:hover {
+      background: linear-gradient(135deg, #4da6ff, #79c0ff);
+      box-shadow: 0 0 16px rgba(30, 144, 255, 0.6);
+    }
+  }
+
+  @keyframes diFadeIn {
+    from {
+      opacity: 0;
+      transform: scale(0.9);
+    }
+    to {
+      opacity: 1;
+      transform: scale(1);
+    }
+  }
+</style>

+ 355 - 0
src/views/ventAI/dataPicker/components/DataInterpretationModal.vue

@@ -0,0 +1,355 @@
+<template>
+  <a-modal
+    :visible="visible"
+    :title="null"
+    :footer="null"
+    :width="620"
+    :closable="false"
+    :destroy-on-close="true"
+    wrap-class-name="data-interpretation-modal"
+    @cancel="$emit('close')"
+  >
+    <!-- 标题栏 -->
+    <div class="di-header">
+      <span class="di-header__title">数据解读</span>
+      <span class="di-header__close" @click="$emit('close')">✕</span>
+    </div>
+
+    <!-- 内容区域 -->
+    <div class="di-body">
+      <!-- 过程状态展示区 -->
+      <div v-if="showProcess" class="di-process">
+        <!-- 思考中 -->
+        <div v-if="thinking" class="di-process__row di-process__thinking"><a-spin size="small" /><span>AI 正在思考...</span></div>
+
+        <!-- Todo 列表 -->
+        <div v-if="todos.length > 0" class="di-process__todos">
+          <div v-for="(todo, i) in todos" :key="i" class="di-todo-item" :class="`di-todo-item--${todo.status}`">
+            <span class="di-todo-item__icon">
+              <span v-if="todo.status === 'completed'" class="di-todo-check">✓</span>
+              <a-spin v-else-if="todo.status === 'in_progress'" size="small" />
+              <span v-else class="di-todo-dot"></span>
+            </span>
+            <span class="di-todo-item__text">{{ todo.content }}</span>
+          </div>
+        </div>
+
+        <!-- 执行工具中 -->
+        <!-- <div v-if="executingTools.length > 0" class="di-process__row di-process__executing">
+          <a-spin size="small" />
+          <span>正在执行:{{ executingTools.join(', ') }}</span>
+        </div> -->
+
+        <!-- 工具调用记录 -->
+        <!-- <div v-if="toolCalls.length > 0" class="di-process__tools">
+          <div v-for="(tc, i) in toolCalls" :key="i" class="di-tool-item">
+            <span class="di-tool-item__dot" :class="{ 'di-tool-item__dot--done': tc.status === 'result' }"></span>
+            <span class="di-tool-item__name">{{ tc.tool }}</span>
+            <span v-if="tc.status === 'call'" class="di-tool-item__tag">执行中</span>
+            <span v-else class="di-tool-item__tag di-tool-item__tag--done">完成</span>
+          </div>
+        </div> -->
+      </div>
+
+      <!-- 无过程且加载中 -->
+      <div v-if="loading && !hasContent && !hasProcess" class="di-loading">
+        <a-spin tip="正在分析数据..." />
+      </div>
+
+      <!-- Markdown 正文 -->
+      <div v-if="hasContent" class="di-markdown" v-html="renderedText"></div>
+
+      <!-- 错误 -->
+      <div v-else-if="error && !loading" class="di-error">数据解读失败:{{ error }}</div>
+    </div>
+
+    <!-- 流式加载指示条 -->
+    <div v-if="loading && hasContent" class="di-streaming-bar">
+      <a-spin size="small" /><span class="di-streaming-bar__tip">AI 正在生成...</span>
+    </div>
+  </a-modal>
+</template>
+
+<script lang="ts" setup>
+  import { computed } from 'vue';
+  import markdownIt from 'markdown-it';
+  import type { TodoItem, ToolCallRecord } from '../types';
+
+  const props = defineProps<{
+    visible: boolean;
+    loading: boolean;
+    streamingText: string;
+    error: string | null;
+    thinking: boolean;
+    todos: TodoItem[];
+    executingTools: string[];
+    toolCalls: ToolCallRecord[];
+  }>();
+
+  defineEmits<{ close: [] }>();
+
+  const md = markdownIt();
+
+  const hasContent = computed(() => !!props.streamingText);
+  const hasProcess = computed(() => props.thinking || props.todos.length > 0 || props.executingTools.length > 0 || props.toolCalls.length > 0);
+  const showProcess = computed(() => hasProcess.value);
+
+  const renderedText = computed(() => {
+    if (!props.streamingText) return '';
+    const processed = props.streamingText.replace(/<br\s*\/?>/gi, '  \n');
+    return md.render(processed);
+  });
+</script>
+
+<style lang="less">
+  .data-interpretation-modal {
+    .ant-modal-content {
+      background: linear-gradient(180deg, #0a1a3a 0%, #0d2450 100%);
+      border: 1px solid rgba(30, 144, 255, 0.3);
+      border-radius: 8px;
+      box-shadow: 0 0 30px rgba(0, 120, 255, 0.15);
+      color: #c8d6e5;
+    }
+    .ant-modal-body {
+      padding: 0;
+      max-height: 75vh;
+      overflow-y: auto;
+    }
+    .ant-spin-text {
+      color: #c8d6e5;
+    }
+  }
+</style>
+
+<style lang="less" scoped>
+  .di-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 18px 24px;
+    background: linear-gradient(90deg, rgba(30, 144, 255, 0.15) 0%, transparent 100%);
+    border-bottom: 1px solid rgba(30, 144, 255, 0.15);
+    flex-shrink: 0;
+
+    &__title {
+      font-size: 18px;
+      font-weight: bold;
+      color: #4da6ff;
+      letter-spacing: 2px;
+    }
+    &__close {
+      cursor: pointer;
+      font-size: 18px;
+      color: #8899aa;
+      &:hover {
+        color: #fff;
+      }
+    }
+  }
+
+  .di-body {
+    padding: 18px 24px;
+    min-height: 120px;
+    max-height: 700px;
+    overflow-y: auto;
+  }
+
+  .di-loading {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    padding: 40px 0;
+  }
+
+  // ── 过程状态区 ──
+  .di-process {
+    margin-bottom: 16px;
+    padding-bottom: 12px;
+    border-bottom: 1px solid rgba(30, 144, 255, 0.12);
+
+    &__row {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 6px 0;
+      font-size: 13px;
+      color: #8899aa;
+    }
+  }
+
+  .di-process__thinking {
+    color: #4da6ff;
+    font-size: 13px;
+  }
+
+  .di-process__executing {
+    color: #c8a853;
+    font-size: 13px;
+  }
+
+  // ── Todo 列表 ──
+  .di-process__todos {
+    margin: 6px 0;
+  }
+
+  .di-todo-item {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 4px 0;
+    font-size: 13px;
+    line-height: 1.6;
+
+    &__icon {
+      flex-shrink: 0;
+      width: 16px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+    &__text {
+      color: #c8d6e5;
+    }
+
+    &--completed &__text {
+      color: #6b7a8a;
+      text-decoration: line-through;
+    }
+    &--in_progress &__text {
+      color: #4da6ff;
+    }
+  }
+
+  .di-todo-check {
+    color: #27ae60;
+    font-size: 12px;
+    font-weight: bold;
+  }
+
+  .di-todo-dot {
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    background: #4a5568;
+    display: inline-block;
+  }
+
+  // ── 工具调用记录 ──
+  .di-process__tools {
+    margin-top: 4px;
+  }
+
+  .di-tool-item {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding: 2px 0;
+    font-size: 12px;
+    color: #6b7a8a;
+
+    &__dot {
+      width: 6px;
+      height: 6px;
+      border-radius: 50%;
+      background: #c8a853;
+      flex-shrink: 0;
+
+      &--done {
+        background: #27ae60;
+      }
+    }
+    &__name {
+      color: #8899aa;
+    }
+    &__tag {
+      font-size: 10px;
+      padding: 0 4px;
+      border-radius: 2px;
+      background: rgba(200, 168, 83, 0.2);
+      color: #c8a853;
+
+      &--done {
+        background: rgba(39, 174, 96, 0.15);
+        color: #27ae60;
+      }
+    }
+  }
+
+  // ── Markdown 正文 ──
+  .di-markdown {
+    line-height: 1.8;
+    color: #c8d6e5;
+    word-break: break-word;
+
+    :deep(h1),
+    :deep(h2),
+    :deep(h3),
+    :deep(h4) {
+      color: #4da6ff;
+      margin: 12px 0 8px;
+    }
+    :deep(p) {
+      margin: 6px 0;
+    }
+    :deep(ul),
+    :deep(ol) {
+      padding-left: 20px;
+      margin: 6px 0;
+    }
+    :deep(strong) {
+      color: #fff;
+    }
+    :deep(code) {
+      background: rgba(30, 144, 255, 0.15);
+      padding: 1px 5px;
+      border-radius: 3px;
+      font-size: 13px;
+    }
+    :deep(pre) {
+      background: rgba(0, 0, 0, 0.3);
+      padding: 12px;
+      border-radius: 6px;
+      overflow-x: auto;
+    }
+    :deep(blockquote) {
+      border-left: 3px solid #4da6ff;
+      padding-left: 12px;
+      margin: 8px 0;
+      color: #8899aa;
+    }
+    :deep(table) {
+      width: 100%;
+      border-collapse: collapse;
+      margin: 8px 0;
+      th,
+      td {
+        border: 1px solid rgba(30, 144, 255, 0.2);
+        padding: 6px 10px;
+        text-align: left;
+      }
+      th {
+        background: rgba(30, 144, 255, 0.1);
+        color: #4da6ff;
+      }
+    }
+  }
+
+  .di-streaming-bar {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 8px 24px 16px;
+    border-top: 1px solid rgba(30, 144, 255, 0.1);
+
+    &__tip {
+      font-size: 12px;
+      color: #8899aa;
+    }
+  }
+
+  .di-error {
+    padding: 40px;
+    text-align: center;
+    color: #e74c3c;
+  }
+</style>

+ 7 - 0
src/views/ventAI/dataPicker/index.ts

@@ -0,0 +1,7 @@
+export { useDataInterpretation, useDataInterpretationList } from './useDataInterpretation';
+export { createTrigger } from './useDataInterpretationTrigger';
+export { fetchTunnelInterpretation, fetchDeviceInterpretation } from './api';
+export type { TriggerState, TriggerHandle, TriggerOptions } from './useDataInterpretationTrigger';
+export type { TunnelInterpretRequest, DeviceInterpretRequest, InterpretationResult, SSERawEvent, SSEEventType, TodoItem, ToolCallRecord } from './types';
+export { default as DataInterpretationModal } from './components/DataInterpretationModal.vue';
+export { default as DataInterpretationBtn } from './components/DataInterpretationBtn.vue';

+ 49 - 0
src/views/ventAI/dataPicker/types.ts

@@ -0,0 +1,49 @@
+/** 巷道解读请求参数 */
+export interface TunnelInterpretRequest {
+  tun_id: string;
+  tun_name: string;
+}
+
+/** 设备解读请求参数 */
+export interface DeviceInterpretRequest {
+  device_id: string;
+  device_name: string;
+  device_type: string;
+}
+
+/** SSE 事件类型 */
+export type SSEEventType = 'thinking' | 'executing' | 'tool_call' | 'tool_result' | 'updated_todo_list' | 'token' | 'done' | 'error';
+
+/** SSE 原始事件(后端推送的 JSON 结构) */
+export interface SSERawEvent {
+  type: SSEEventType;
+  content?: string;
+  source?: 'main' | 'subagent';
+  thread_id?: string;
+  session_id?: string;
+  tools?: string[];
+  tool?: string;
+  node?: string;
+  todos?: TodoItem[];
+  [key: string]: any;
+}
+
+/** Todo 项 */
+export interface TodoItem {
+  content: string;
+  status: 'pending' | 'in_progress' | 'completed';
+}
+
+/** 工具调用记录 */
+export interface ToolCallRecord {
+  tool: string;
+  source: string;
+  status: 'call' | 'result';
+}
+
+/** 解读完成后的结果 */
+export interface InterpretationResult {
+  content: string;
+  threadId: string;
+  sessionId: string;
+}

+ 185 - 0
src/views/ventAI/dataPicker/useDataInterpretation.ts

@@ -0,0 +1,185 @@
+import { ref } from 'vue';
+import { fetchTunnelInterpretation, fetchDeviceInterpretation } from './api';
+import { createTrigger, type TriggerHandle } from './useDataInterpretationTrigger';
+import type { TodoItem, ToolCallRecord } from './types';
+
+/** 数据解读 hook —— 调用后端 SSE 流式接口,实时追踪所有过程状态 */
+export function useDataInterpretation() {
+  const loading = ref(false);
+  const streamingText = ref('');
+  const error = ref<string | null>(null);
+  const threadId = ref('');
+  const sessionId = ref('');
+
+  // 过程状态
+  const thinking = ref(false);
+  const todos = ref<TodoItem[]>([]);
+  const executingTools = ref<string[]>([]);
+  const toolCalls = ref<ToolCallRecord[]>([]);
+
+  /** 统一的事件处理 */
+  function handleEvent(event: any) {
+    switch (event.type) {
+      case 'thinking':
+        thinking.value = true;
+        break;
+      case 'executing':
+        thinking.value = false;
+        executingTools.value = event.tools || [];
+        break;
+      case 'tool_call':
+        executingTools.value = [];
+        toolCalls.value = [...toolCalls.value, { tool: event.tool || '', source: event.source || 'main', status: 'call' as const }];
+        break;
+      case 'tool_result':
+        // 更新最近一条 tool_call 为 result 状态
+        if (toolCalls.value.length > 0) {
+          const last = toolCalls.value[toolCalls.value.length - 1];
+          if (last.tool === event.tool && last.status === 'call') {
+            last.status = 'result';
+          }
+        }
+        break;
+      case 'updated_todo_list':
+        thinking.value = false;
+        todos.value = event.todos || [];
+        break;
+      case 'token':
+        thinking.value = false;
+        if (event.content) {
+          streamingText.value += event.content;
+        }
+        break;
+    }
+  }
+
+  async function interpretTunnel(params: { tun_id: string; tun_name: string }) {
+    loading.value = true;
+    error.value = null;
+    streamingText.value = '';
+    thinking.value = false;
+    todos.value = [];
+    executingTools.value = [];
+    toolCalls.value = [];
+
+    try {
+      const result = await fetchTunnelInterpretation(params, handleEvent);
+      threadId.value = result.thread_id;
+      sessionId.value = result.session_id;
+    } catch (e: any) {
+      const msg = e?.message || '数据解读请求失败';
+      error.value = msg;
+      throw e;
+    } finally {
+      loading.value = false;
+      thinking.value = false;
+      executingTools.value = [];
+    }
+  }
+
+  async function interpretDevice(params: { device_id: string; device_name: string; device_type: string }) {
+    loading.value = true;
+    error.value = null;
+    streamingText.value = '';
+    thinking.value = false;
+    todos.value = [];
+    executingTools.value = [];
+    toolCalls.value = [];
+
+    try {
+      const result = await fetchDeviceInterpretation(params, handleEvent);
+      threadId.value = result.thread_id;
+      sessionId.value = result.session_id;
+    } catch (e: any) {
+      const msg = e?.message || '数据解读请求失败';
+      error.value = msg;
+      throw e;
+    } finally {
+      loading.value = false;
+      thinking.value = false;
+      executingTools.value = [];
+    }
+  }
+
+  function reset() {
+    streamingText.value = '';
+    error.value = null;
+    threadId.value = '';
+    sessionId.value = '';
+    thinking.value = false;
+    todos.value = [];
+    executingTools.value = [];
+    toolCalls.value = [];
+  }
+
+  return {
+    loading,
+    streamingText,
+    error,
+    threadId,
+    sessionId,
+    thinking,
+    todos,
+    executingTools,
+    toolCalls,
+    interpretTunnel,
+    interpretDevice,
+    reset,
+  };
+}
+
+/**
+ * 数据解读列表级便捷 Hook。
+ * 整合了弹框状态 + 每行数据的悬浮触发器管理 + SSE 流式解读。
+ *
+ * @param mapParams - 将列表行数据映射为接口请求参数
+ *
+ * 使用:在列表/表格/卡片组等 v-for 场景的组件中调用一次即可。
+ */
+export function useDataInterpretationList(
+  mapParams: (item: any) => { tun_id: string; tun_name: string } | { device_id: string; device_name: string; device_type: string }
+) {
+  const { loading, streamingText, error, threadId, sessionId, thinking, todos, executingTools, toolCalls, interpretTunnel, interpretDevice, reset } =
+    useDataInterpretation();
+
+  const modalVisible = ref(false);
+  const triggerMap = new Map<string, TriggerHandle>();
+
+  function getTrigger(id: string): TriggerHandle {
+    if (!triggerMap.has(id)) {
+      triggerMap.set(id, createTrigger());
+    }
+    return triggerMap.get(id)!;
+  }
+
+  async function handleInterpret(item: any) {
+    modalVisible.value = true;
+    const params = mapParams(item);
+    if ('tun_id' in params) {
+      await interpretTunnel(params);
+    } else {
+      await interpretDevice(params);
+    }
+  }
+
+  function handleClose() {
+    modalVisible.value = false;
+    reset();
+  }
+
+  return {
+    modalVisible,
+    loading,
+    streamingText,
+    error,
+    threadId,
+    sessionId,
+    thinking,
+    todos,
+    executingTools,
+    toolCalls,
+    getTrigger,
+    handleInterpret,
+    handleClose,
+  };
+}

+ 124 - 0
src/views/ventAI/dataPicker/useDataInterpretationTrigger.ts

@@ -0,0 +1,124 @@
+import { reactive } from 'vue';
+
+export interface TriggerState {
+  visible: boolean;
+  x: number;
+  y: number;
+}
+
+export interface TriggerHandle {
+  state: TriggerState;
+  onEnter: (e: MouseEvent) => void;
+  onMove: (e: MouseEvent) => void;
+  onLeave: () => void;
+  onBtnEnter: () => void;
+  onBtnLeave: () => void;
+  destroy: () => void;
+}
+
+export interface TriggerOptions {
+  /** 鼠标停顿多久后显示按钮 (ms),默认 400 */
+  pauseDelay?: number;
+  /** 按钮显示后多久自动消失 (ms),默认 3000 */
+  hideDelay?: number;
+}
+
+/**
+ * 创建一个悬浮按钮触发器实例。
+ * 每个需要数据解读的 DOM 元素(卡片行、表格行、3D 标记等)对应一个 trigger。
+ *
+ * 使用方式:
+ * 1. 在 v-for 中为每行数据 createTrigger()
+ * 2. 将 onEnter/onMove/onLeave 绑定到容器元素的 mouseenter/mousemove/mouseleave
+ * 3. 将 DataInterpretationBtn 放在容器内,绑定 state 和 onBtnEnter/onBtnLeave
+ */
+export function createTrigger(options?: TriggerOptions): TriggerHandle {
+  const PAUSE_DELAY = options?.pauseDelay ?? 400;
+  const HIDE_DELAY = options?.hideDelay ?? 3000;
+
+  const state = reactive<TriggerState>({
+    visible: false,
+    x: 0,
+    y: 0,
+  });
+
+  let pauseTimer: ReturnType<typeof setTimeout> | null = null;
+  let hideTimer: ReturnType<typeof setTimeout> | null = null;
+
+  function trackMouse(e: MouseEvent) {
+    const target = e.currentTarget as HTMLElement;
+    const rect = target.getBoundingClientRect();
+    state.x = e.clientX - rect.left - 36;
+    state.y = e.clientY - rect.top - 34;
+  }
+
+  function startPauseTimer() {
+    pauseTimer = setTimeout(() => {
+      state.visible = true;
+      startHideTimer();
+    }, PAUSE_DELAY);
+  }
+
+  function resetPauseTimer() {
+    if (pauseTimer) {
+      clearTimeout(pauseTimer);
+      pauseTimer = null;
+    }
+  }
+
+  function startHideTimer() {
+    clearHideTimer();
+    hideTimer = setTimeout(() => {
+      state.visible = false;
+    }, HIDE_DELAY);
+  }
+
+  function clearHideTimer() {
+    if (hideTimer) {
+      clearTimeout(hideTimer);
+      hideTimer = null;
+    }
+  }
+
+  function onEnter(e: MouseEvent) {
+    clearHideTimer();
+    trackMouse(e);
+    startPauseTimer();
+  }
+
+  function onMove(e: MouseEvent) {
+    if (state.visible) return;
+    trackMouse(e);
+    resetPauseTimer();
+    startPauseTimer();
+  }
+
+  function onLeave() {
+    resetPauseTimer();
+    clearHideTimer();
+    state.visible = false;
+  }
+
+  function onBtnEnter() {
+    clearHideTimer();
+  }
+
+  function onBtnLeave() {
+    startHideTimer();
+  }
+
+  function destroy() {
+    resetPauseTimer();
+    clearHideTimer();
+  }
+
+  return {
+    state,
+    onEnter,
+    onMove,
+    onLeave,
+    onBtnEnter,
+    onBtnLeave,
+    destroy,
+  };
+}