dedsudiyu дней назад: 6
Родитель
Сommit
ede7fa1578

+ 2 - 1
.claude/settings.local.json

@@ -3,7 +3,8 @@
     "allow": [
       "Bash(npm run:*)",
       "Bash(node -e \"require\\(''buffer''\\); require\\(''process/browser''\\); require\\(''url/''\\)\")",
-      "Bash(npm install:*)"
+      "Bash(npm install:*)",
+      "Bash(node -e \"const p = require\\(''e:/git/2021项目/anKeYuan9600/node_modules/mqtt/package.json''\\); console.log\\(JSON.stringify\\(p.exports ? Object.keys\\(p.exports\\) : ''no exports'', null, 2\\)\\)\")"
     ]
   }
 }

+ 201 - 39
package-lock.json

@@ -10,9 +10,11 @@
       "dependencies": {
         "axios": "^0.27.2",
         "core-js": "^3.8.3",
+        "crypto-js": "^4.2.0",
         "echarts": "^5.4.3",
         "element-ui": "^2.15.14",
         "js-md5": "^0.8.3",
+        "mqtt": "^4.3.8",
         "vue": "^2.7.16",
         "vue-router": "^3.6.5",
         "vuex": "^3.6.2"
@@ -1531,10 +1533,11 @@
       }
     },
     "node_modules/@babel/runtime": {
-      "version": "7.28.6",
-      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.6.tgz",
-      "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
+      "version": "7.29.2",
+      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz",
+      "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
       "dev": true,
+      "license": "MIT",
       "engines": {
         "node": ">=6.9.0"
       }
@@ -3179,14 +3182,12 @@
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
-      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
-      "dev": true
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
     "node_modules/base64-js": {
       "version": "1.5.1",
       "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
       "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
-      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -3245,7 +3246,6 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz",
       "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
-      "dev": true,
       "dependencies": {
         "buffer": "^5.5.0",
         "inherits": "^2.0.4",
@@ -3256,7 +3256,6 @@
       "version": "5.7.1",
       "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz",
       "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
-      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -3342,7 +3341,6 @@
       "version": "1.1.12",
       "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz",
       "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
-      "dev": true,
       "dependencies": {
         "balanced-match": "^1.0.0",
         "concat-map": "0.0.1"
@@ -3421,8 +3419,7 @@
     "node_modules/buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz",
-      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
-      "dev": true
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
     },
     "node_modules/bytes": {
       "version": "3.1.2",
@@ -3761,6 +3758,16 @@
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
       "dev": true
     },
+    "node_modules/commist": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/commist/-/commist-1.1.0.tgz",
+      "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==",
+      "license": "MIT",
+      "dependencies": {
+        "leven": "^2.1.0",
+        "minimist": "^1.1.0"
+      }
+    },
     "node_modules/commondir": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz",
@@ -3815,8 +3822,22 @@
     "node_modules/concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
-      "dev": true
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+    },
+    "node_modules/concat-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-2.0.0.tgz",
+      "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
+      "engines": [
+        "node >= 6.0"
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.0.2",
+        "typedarray": "^0.0.6"
+      }
     },
     "node_modules/connect-history-api-fallback": {
       "version": "2.0.0",
@@ -3994,6 +4015,12 @@
         "semver": "bin/semver"
       }
     },
+    "node_modules/crypto-js": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz",
+      "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+      "license": "MIT"
+    },
     "node_modules/css-declaration-sorter": {
       "version": "6.4.1",
       "resolved": "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz",
@@ -4306,7 +4333,6 @@
       "version": "4.4.3",
       "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
       "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
-      "dev": true,
       "dependencies": {
         "ms": "^2.1.3"
       },
@@ -4686,6 +4712,18 @@
       "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
       "dev": true
     },
+    "node_modules/duplexify": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmmirror.com/duplexify/-/duplexify-4.1.3.tgz",
+      "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==",
+      "license": "MIT",
+      "dependencies": {
+        "end-of-stream": "^1.4.1",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.1.1",
+        "stream-shift": "^1.0.2"
+      }
+    },
     "node_modules/easy-stack": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/easy-stack/-/easy-stack-1.0.1.tgz",
@@ -4760,7 +4798,6 @@
       "version": "1.4.5",
       "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz",
       "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
-      "dev": true,
       "dependencies": {
         "once": "^1.4.0"
       }
@@ -5311,8 +5348,7 @@
     "node_modules/fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
-      "dev": true
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
     },
     "node_modules/fsevents": {
       "version": "2.3.3",
@@ -5406,7 +5442,6 @@
       "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
       "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
       "deprecated": "Glob versions prior to v9 are no longer supported",
-      "dev": true,
       "dependencies": {
         "fs.realpath": "^1.0.0",
         "inflight": "^1.0.4",
@@ -5570,6 +5605,16 @@
         "he": "bin/he"
       }
     },
+    "node_modules/help-me": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/help-me/-/help-me-3.0.0.tgz",
+      "integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==",
+      "license": "MIT",
+      "dependencies": {
+        "glob": "^7.1.6",
+        "readable-stream": "^3.6.0"
+      }
+    },
     "node_modules/highlight.js": {
       "version": "10.7.3",
       "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz",
@@ -5855,7 +5900,6 @@
       "version": "1.2.1",
       "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
       "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
-      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -5901,7 +5945,6 @@
       "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
       "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
       "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
-      "dev": true,
       "dependencies": {
         "once": "^1.3.0",
         "wrappy": "1"
@@ -5910,8 +5953,7 @@
     "node_modules/inherits": {
       "version": "2.0.4",
       "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
-      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-      "dev": true
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
     "node_modules/ipaddr.js": {
       "version": "2.3.0",
@@ -6167,6 +6209,16 @@
         "node": ">=0.6.0"
       }
     },
+    "node_modules/js-sdsl": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.3.0.tgz",
+      "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/js-sdsl"
+      }
+    },
     "node_modules/js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6264,6 +6316,15 @@
         "launch-editor": "^2.13.1"
       }
     },
+    "node_modules/leven": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/leven/-/leven-2.1.0.tgz",
+      "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/lilconfig": {
       "version": "2.1.0",
       "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz",
@@ -6747,7 +6808,6 @@
       "version": "3.1.5",
       "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz",
       "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
-      "dev": true,
       "dependencies": {
         "brace-expansion": "^1.1.7"
       },
@@ -6759,7 +6819,6 @@
       "version": "1.2.8",
       "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
       "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
-      "dev": true,
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
@@ -6788,6 +6847,68 @@
       "integrity": "sha512-bOclZt8hkpuGgSSoG07PKmvzTizROilUTvLNyrMqvlC9snhs7y7GzjNWAVbISIOlhCP1T14rH1PDAV9iNyBq/w==",
       "dev": true
     },
+    "node_modules/mqtt": {
+      "version": "4.3.8",
+      "resolved": "https://registry.npmmirror.com/mqtt/-/mqtt-4.3.8.tgz",
+      "integrity": "sha512-2xT75uYa0kiPEF/PE0VPdavmEkoBzMT/UL9moid0rAvlCtV48qBwxD62m7Ld/4j8tSkIO1E/iqRl/S72SEOhOw==",
+      "license": "MIT",
+      "dependencies": {
+        "commist": "^1.0.0",
+        "concat-stream": "^2.0.0",
+        "debug": "^4.1.1",
+        "duplexify": "^4.1.1",
+        "help-me": "^3.0.0",
+        "inherits": "^2.0.3",
+        "lru-cache": "^6.0.0",
+        "minimist": "^1.2.5",
+        "mqtt-packet": "^6.8.0",
+        "number-allocator": "^1.0.9",
+        "pump": "^3.0.0",
+        "readable-stream": "^3.6.0",
+        "reinterval": "^1.1.0",
+        "rfdc": "^1.3.0",
+        "split2": "^3.1.0",
+        "ws": "^7.5.5",
+        "xtend": "^4.0.2"
+      },
+      "bin": {
+        "mqtt": "bin/mqtt.js",
+        "mqtt_pub": "bin/pub.js",
+        "mqtt_sub": "bin/sub.js"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/mqtt-packet": {
+      "version": "6.10.0",
+      "resolved": "https://registry.npmmirror.com/mqtt-packet/-/mqtt-packet-6.10.0.tgz",
+      "integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==",
+      "license": "MIT",
+      "dependencies": {
+        "bl": "^4.0.2",
+        "debug": "^4.1.1",
+        "process-nextick-args": "^2.0.1"
+      }
+    },
+    "node_modules/mqtt/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "license": "ISC",
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/mqtt/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "license": "ISC"
+    },
     "node_modules/mrmime": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz",
@@ -6800,8 +6921,7 @@
     "node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
-      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "dev": true
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
     },
     "node_modules/multicast-dns": {
       "version": "7.2.5",
@@ -6981,6 +7101,16 @@
         "url": "https://github.com/fb55/nth-check?sponsor=1"
       }
     },
+    "node_modules/number-allocator": {
+      "version": "1.0.14",
+      "resolved": "https://registry.npmmirror.com/number-allocator/-/number-allocator-1.0.14.tgz",
+      "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.3.1",
+        "js-sdsl": "4.3.0"
+      }
+    },
     "node_modules/object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
@@ -7062,7 +7192,6 @@
       "version": "1.4.0",
       "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
       "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
-      "dev": true,
       "dependencies": {
         "wrappy": "1"
       }
@@ -7282,7 +7411,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
       "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -7967,8 +8095,7 @@
     "node_modules/process-nextick-args": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
-      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
-      "dev": true
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
     },
     "node_modules/progress-webpack-plugin": {
       "version": "1.0.16",
@@ -8081,7 +8208,6 @@
       "version": "3.0.4",
       "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz",
       "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
-      "dev": true,
       "dependencies": {
         "end-of-stream": "^1.1.0",
         "once": "^1.3.1"
@@ -8209,7 +8335,6 @@
       "version": "3.6.2",
       "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz",
       "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
-      "dev": true,
       "dependencies": {
         "inherits": "^2.0.3",
         "string_decoder": "^1.1.1",
@@ -8289,6 +8414,12 @@
         "regjsparser": "bin/parser"
       }
     },
+    "node_modules/reinterval": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/reinterval/-/reinterval-1.1.0.tgz",
+      "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==",
+      "license": "MIT"
+    },
     "node_modules/relateurl": {
       "version": "0.2.7",
       "resolved": "https://registry.npmmirror.com/relateurl/-/relateurl-0.2.7.tgz",
@@ -8401,6 +8532,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/rfdc": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
+      "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+      "license": "MIT"
+    },
     "node_modules/rimraf": {
       "version": "3.0.2",
       "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz",
@@ -8444,7 +8581,6 @@
       "version": "5.2.1",
       "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
       "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -8976,6 +9112,15 @@
         "wbuf": "^1.7.3"
       }
     },
+    "node_modules/split2": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmmirror.com/split2/-/split2-3.2.2.tgz",
+      "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
+      "license": "ISC",
+      "dependencies": {
+        "readable-stream": "^3.0.0"
+      }
+    },
     "node_modules/ssri": {
       "version": "8.0.1",
       "resolved": "https://registry.npmmirror.com/ssri/-/ssri-8.0.1.tgz",
@@ -9010,11 +9155,16 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/stream-shift": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.3.tgz",
+      "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
+      "license": "MIT"
+    },
     "node_modules/string_decoder": {
       "version": "1.3.0",
       "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
       "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
-      "dev": true,
       "dependencies": {
         "safe-buffer": "~5.2.0"
       }
@@ -9395,6 +9545,12 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
+      "license": "MIT"
+    },
     "node_modules/undici-types": {
       "version": "7.18.2",
       "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz",
@@ -9522,8 +9678,7 @@
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
-      "dev": true
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
     },
     "node_modules/utila": {
       "version": "0.4.0",
@@ -10191,14 +10346,12 @@
     "node_modules/wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
-      "dev": true
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
     },
     "node_modules/ws": {
       "version": "7.5.10",
       "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz",
       "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
-      "dev": true,
       "engines": {
         "node": ">=8.3.0"
       },
@@ -10215,6 +10368,15 @@
         }
       }
     },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4"
+      }
+    },
     "node_modules/y18n": {
       "version": "5.0.8",
       "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",

+ 2 - 0
package.json

@@ -9,9 +9,11 @@
   "dependencies": {
     "axios": "^0.27.2",
     "core-js": "^3.8.3",
+    "crypto-js": "^4.2.0",
     "echarts": "^5.4.3",
     "element-ui": "^2.15.14",
     "js-md5": "^0.8.3",
+    "mqtt": "^4.3.8",
     "vue": "^2.7.16",
     "vue-router": "^3.6.5",
     "vuex": "^3.6.2"

+ 12 - 0
src/api/auth.js

@@ -13,6 +13,18 @@ export function loginApi(data) {
 }
 
 /**
+ * 获取系统配置
+ * POST /system/config/info/getConfigByType
+ */
+export function getConfigByType(data) {
+  return request({
+    url: '/system/config/info/getConfigByType',
+    method: 'post',
+    data
+  })
+}
+
+/**
  * 获取验证码
  * GET /auth/captcha
  * 返回 { captcha: Boolean, image: String(base64), uuid: String }

+ 11 - 13
src/components/AlarmInfo.vue

@@ -14,7 +14,7 @@
     </div>
     <!-- Scrolling warning list -->
     <div class="warn-scroll-wrap">
-      <div class="warn-scroll-inner">
+      <div class="warn-scroll-inner" :class="{ scrolling: shouldScroll }">
         <div
           v-for="(item, idx) in scrollList"
           :key="idx"
@@ -48,22 +48,18 @@ export default {
     }
   },
   computed: {
-    /** Duplicate data for seamless CSS scroll loop */
+    shouldScroll() {
+      return this.warningList.length > 5
+    },
     scrollList() {
-      if(this.warningList[6]){
-        return [...this.warningList, ...this.warningList]
-      }else if(this.warningList[0]){
-        return [...this.warningList]
-      }else{
-        return []
-      }
+      return this.shouldScroll
+        ? [...this.warningList, ...this.warningList]
+        : [...this.warningList]
     }
   },
   mounted() {
     this.fetchData()
-    if(this.warningList[6]){
-      this.pollTimer = setInterval(this.fetchData, 5 * 60 * 1000)
-    }
+    this.pollTimer = setInterval(this.fetchData, 5 * 60 * 1000)
   },
   beforeDestroy() {
     if (this.pollTimer) clearInterval(this.pollTimer)
@@ -212,7 +208,9 @@ export default {
 }
 
 .warn-scroll-inner {
-  animation: scrollUp 22s linear infinite;
+  &.scrolling {
+    animation: scrollUp 22s linear infinite;
+  }
 
   &:hover {
     animation-play-state: paused;

+ 26 - 17
src/components/LabEnvironment.vue

@@ -15,7 +15,7 @@
     </div>
     <!-- Scrolling sensor list -->
     <div class="sensor-scroll-wrap">
-      <div class="sensor-scroll-inner">
+      <div class="sensor-scroll-inner" :class="{ scrolling: shouldScroll }">
         <div
           v-for="(item, idx) in scrollList"
           :key="idx"
@@ -29,9 +29,10 @@
             <span v-else class="sensor-status normal-status">&#x25CF; 正常</span>
           </div>
           <div class="sensor-metrics">
-            <div class="sensor-metric" v-for="(minItem, mIdx) in item.sensorList" :key="mIdx">
+            <div class="sensor-metric" :class="minItem.hasException?'redClass':''"
+            v-for="(minItem, mIdx) in item.sensorList" :key="mIdx">
               <span class="span-svg">
-                <svgIcon  icon-class="http://192.168.1.8/statics/2026/03/24/d3d2315f-4851-4563-8257-0bc64f08718f.svg"/>
+                <svgIcon class="svgIcon" :icon-class="fileBrowseEnvironment+minItem.icon"/>
               </span>
               <span class="span-text">{{ minItem.attributeName }} {{ minItem.deviceValue }}{{ minItem.unit }}</span>
             </div>
@@ -54,26 +55,23 @@ export default {
   data() {
     return {
       sensorList: [],
-      pollTimer: null
+      pollTimer: null,
+      fileBrowseEnvironment:localStorage.getItem('fileBrowseEnvironment'),
     }
   },
   computed: {
-    /** Duplicate data for seamless CSS scroll loop */
+    shouldScroll() {
+      return this.sensorList.length > 12
+    },
     scrollList() {
-      if(this.sensorList[12]){
-        return [...this.sensorList, ...this.sensorList]
-      }else if(this.sensorList[0]){
-        return [...this.sensorList]
-      }else{
-        return []
-      }
+      return this.shouldScroll
+        ? [...this.sensorList, ...this.sensorList]
+        : [...this.sensorList]
     }
   },
   mounted() {
     this.fetchData()
-    if(this.sensorList[12]){
-      this.pollTimer = setInterval(this.fetchData, 5 * 60 * 1000)
-    }
+    this.pollTimer = setInterval(this.fetchData, 5 * 60 * 1000)
   },
   beforeDestroy() {
     if (this.pollTimer) clearInterval(this.pollTimer)
@@ -242,7 +240,9 @@ export default {
 }
 
 .sensor-scroll-inner {
-  animation: scrollUp 30s linear infinite;
+  &.scrolling {
+    animation: scrollUp 30s linear infinite;
+  }
 
   &:hover {
     animation-play-state: paused;
@@ -311,7 +311,16 @@ export default {
   gap: 10px;
   flex-wrap: wrap;
 }
-
+.redClass{
+  background: rgba(255,0,0,0.2)!important ;
+  border: 1px solid rgba(255,0,0,0.7)!important;
+  .svgIcon{
+    color:red;
+  }
+  .span-text{
+    color:red;
+  }
+}
 .sensor-metric {
   padding: 5px 15px;
   border-radius: 5px;

+ 434 - 0
src/components/alarmWindow/alarm.vue

@@ -0,0 +1,434 @@
+<template>
+  <div class="alarm-dialog">
+    <!-- 头部 -->
+    <div class="alarm-header">
+      <div class="header-left">
+        <span class="siren-icon">🚨</span>
+        <div>
+          <div class="title">
+            <span class="flash-icon">⚡</span>
+            系统预警 · ALERT
+          </div>
+          <div class="subtitle">LABORATORY SAFETY MONITORING SYSTEM · EMERGENCY</div>
+        </div>
+      </div>
+      <div class="close-btn" @click="$emit('close')">✕</div>
+    </div>
+
+    <!-- 主体 -->
+    <div class="alarm-body">
+      <!-- 左侧:摄像头画面 -->
+      <div class="camera-panel">
+        <div class="camera-label">■ 告警实验室实时监控画面</div>
+        <div class="camera-view">
+          <div class="camera-topbar">
+            <span class="cam-name">CAM · {{ data.roomNum || 'N/A' }}</span>
+            <span class="rec-badge">⬤ REC</span>
+          </div>
+          <div class="camera-screen">
+            <img v-if="data.cameraUrl" :src="data.cameraUrl" class="cam-img" alt="camera" />
+            <div v-else class="cam-placeholder">
+              <span>暂无画面</span>
+            </div>
+            <!-- AI 检测标注框 -->
+            <div v-if="data.detectionLabel" class="detection-box">
+              <span class="detection-label">{{ data.detectionLabel }}</span>
+            </div>
+          </div>
+          <div class="camera-bottom">
+            <span class="lab-name-cam">{{ data.subName || '—' }}</span>
+            <span class="ai-badge">🤖 AI检测</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 右侧:告警信息 -->
+      <div class="info-panel">
+        <div class="info-grid">
+          <div class="info-cell">
+            <div class="cell-label">告警实验室</div>
+            <div class="cell-value">{{ data.subName || '—' }}</div>
+          </div>
+          <div class="info-cell">
+            <div class="cell-label">所属单位</div>
+            <div class="cell-value">{{ data.deptName || '—' }}</div>
+          </div>
+          <div class="info-cell">
+            <div class="cell-label">告警指标</div>
+            <div class="cell-value warn-text">{{ data.eventName || data.sensorName || '—' }}</div>
+          </div>
+          <div class="info-cell">
+            <div class="cell-label">当前值 / 安全阈值</div>
+            <div class="cell-value">
+              <span class="val-current">{{ data.triggerUploadData[0].deviceValue}}</span>
+              <span class="val-sep"> / </span>
+              <span class="val-threshold">{{ data.triggerUploadData[0].deviceValue}}</span>
+              <span v-if="data.unit" class="val-unit"> {{ data.triggerUploadData[0].unit || '—' }}</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 走马灯提示 -->
+        <div class="marquee-wrap">
+          <div class="marquee-text">
+            ▶▶ 请立即采取安全措施,疏散实验人员 ◀◀
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 底部按钮 -->
+    <div class="alarm-footer">
+      <button class="btn-later" @click="$emit('close')">稍后处理</button>
+      <button class="btn-emergency" @click="$emit('emergency')">⚠ 应急疏散</button>
+      <button class="btn-confirm" @click="$emit('confirm')">确认处理</button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Alarm',
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.alarm-dialog {
+  width: 1400px;
+  background: #0d0002;
+  border: 1px solid rgba(255, 40, 40, 0.5);
+  box-shadow: 0 0 60px rgba(255, 0, 0, 0.3), inset 0 0 40px rgba(100, 0, 0, 0.15);
+  border-radius: 8px;
+  overflow: hidden;
+  font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
+}
+
+/* 头部 */
+.alarm-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 28px 40px 22px;
+  background: linear-gradient(90deg, rgba(160, 0, 0, 0.35) 0%, rgba(40, 0, 0, 0.2) 100%);
+  border-bottom: 1px solid rgba(255, 40, 40, 0.25);
+
+  .header-left {
+    display: flex;
+    align-items: center;
+    gap: 20px;
+  }
+
+  .siren-icon {
+    font-size: 56px;
+    animation: siren-flash 0.8s ease-in-out infinite alternate;
+  }
+
+  .title {
+    font-size: 42px;
+    font-weight: 700;
+    color: #ff4040;
+    letter-spacing: 2px;
+    display: flex;
+    align-items: center;
+    gap: 10px;
+
+    .flash-icon {
+      color: #ffcc00;
+    }
+  }
+
+  .subtitle {
+    font-size: 20px;
+    color: rgba(255, 120, 120, 0.7);
+    letter-spacing: 3px;
+    margin-top: 6px;
+  }
+
+  .close-btn {
+    font-size: 36px;
+    color: rgba(255, 100, 100, 0.6);
+    cursor: pointer;
+    padding: 8px 12px;
+    border-radius: 4px;
+    transition: color 0.2s;
+
+    &:hover {
+      color: #ff4040;
+    }
+  }
+}
+
+/* 主体 */
+.alarm-body {
+  display: flex;
+  gap: 30px;
+  padding: 30px 40px;
+}
+
+/* 摄像头区域 */
+.camera-panel {
+  flex: 0 0 480px;
+
+  .camera-label {
+    font-size: 22px;
+    color: #AE6162;
+    margin-bottom: 16px;
+    letter-spacing: 1px;
+  }
+
+  .camera-view {
+    background: #0a0000;
+    border: 1px solid rgba(255, 40, 40, 0.3);
+    border-radius: 6px;
+    overflow: hidden;
+  }
+
+  .camera-topbar {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 10px 16px;
+    background: rgba(0, 0, 0, 0.6);
+    font-size: 20px;
+
+    .cam-name {
+      color: #ccc;
+      letter-spacing: 1px;
+    }
+
+    .rec-badge {
+      color: #ff2020;
+      font-weight: 600;
+      animation: rec-blink 1s step-end infinite;
+    }
+  }
+
+  .camera-screen {
+    position: relative;
+    height: 300px;
+    background: #050000;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    .cam-img {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+
+    .cam-placeholder {
+      color: rgba(255, 100, 100, 0.3);
+      font-size: 22px;
+    }
+
+    .detection-box {
+      position: absolute;
+      top: 60px;
+      left: 80px;
+      right: 80px;
+      bottom: 60px;
+      border: 2px solid #ff2020;
+      box-shadow: 0 0 12px rgba(255, 0, 0, 0.5);
+
+      .detection-label {
+        position: absolute;
+        top: -36px;
+        left: 0;
+        background: #ff2020;
+        color: #fff;
+        font-size: 18px;
+        padding: 4px 12px;
+        white-space: nowrap;
+      }
+    }
+  }
+
+  .camera-bottom {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 12px 16px;
+    background: rgba(0, 0, 0, 0.5);
+
+    .lab-name-cam {
+      color: #aaa;
+      font-size: 20px;
+    }
+
+    .ai-badge {
+      background: rgba(0, 200, 100, 0.15);
+      border: 1px solid rgba(0, 200, 100, 0.4);
+      color: #00cc66;
+      font-size: 18px;
+      padding: 4px 14px;
+      border-radius: 4px;
+    }
+  }
+}
+
+/* 信息区域 */
+.info-panel {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 24px;
+  width:810px;
+
+  .info-grid {
+    // display: grid;
+    // grid-template-columns: 1fr 1fr;
+    // grid-template-rows: 1fr 1fr;
+    gap: 16px;
+    // flex: 1;
+  }
+  .info-cell:nth-child(1){
+    margin-right:20px;
+  }
+  .info-cell:nth-child(3){
+    margin-right:20px;
+    margin-top:20px;
+  }
+  .info-cell:nth-child(4){
+    margin-top:20px;
+  }
+  .info-cell {
+    background: rgba(80, 0, 0, 0.2);
+    border: 1px solid rgba(255, 40, 40, 0.2);
+    border-radius: 6px;
+    padding: 20px 24px;
+    height: 120px;
+    width: 390px;
+    display: inline-block;
+
+    .cell-label {
+      font-size: 20px;
+      color: #AE6162;
+      margin-bottom: 12px;
+      letter-spacing: 1px;
+    }
+
+    .cell-value {
+      font-size: 28px;
+      color: #EF4444;
+      font-weight: 500;
+      line-height: 1.3;
+      display:block;
+      overflow:hidden;
+      text-overflow:ellipsis;
+      white-space:nowrap;
+    }
+
+    .warn-text {
+      color: #EF4444;
+    }
+
+    .val-current {
+      color: #EF4444;
+    }
+
+    .val-sep {
+      color: #EF4444;
+    }
+
+    .val-threshold {
+      color: #EF4444;
+    }
+
+    .val-unit {
+      font-size: 22px;
+      color: rgba(255, 200, 200, 0.5);
+    }
+  }
+
+  /* 走马灯 */
+  .marquee-wrap {
+    overflow: hidden;
+    background: rgba(60, 40, 0, 0.3);
+    border: 1px solid rgba(200, 150, 0, 0.3);
+    border-radius: 4px;
+    padding: 14px 0;
+    text-align: center;
+
+    .marquee-text {
+      display: inline-block;
+      white-space: nowrap;
+      // color: #BC4D4D;
+      color:#EF4444;
+      font-size: 24px;
+      font-weight: 600;
+      letter-spacing: 1px;
+      // animation: marquee 12s linear infinite;
+    }
+  }
+}
+
+/* 底部按钮 */
+.alarm-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 20px;
+  padding: 20px 40px 28px;
+  border-top: 1px solid rgba(255, 40, 40, 0.15);
+
+  button {
+    font-size: 24px;
+    padding: 14px 48px;
+    border-radius: 4px;
+    cursor: pointer;
+    border: none;
+    font-family: inherit;
+    transition: opacity 0.2s, transform 0.1s;
+
+    &:active {
+      transform: scale(0.97);
+    }
+  }
+
+  .btn-later {
+    background-color:rgba(69, 90, 116,0.2);
+    color:rgba(69, 90, 116,1);
+    border:2px solid rgba(69, 90, 116,1);
+    font-weight: 700;
+    box-shadow: 0 0 2rgba(69, 90, 116, 0.2);
+  }
+
+  .btn-emergency {
+    background-color:rgba(245,158,11,0.2);
+    color:rgba(245,158,11,1);
+    border:2px solid rgba(245,158,11,1);
+    font-weight: 700;
+    box-shadow: 0 0 20px rgba(245,158,11,0.2);
+  }
+
+  .btn-confirm {
+    background-color:rgba(239,68,68,0.2);
+    color:rgba(239,68,68,1);
+    border:2px solid rgba(239,68,68,1);
+    font-weight: 700;
+    box-shadow: 0 0 20px rgba(239,68,68,0.2);
+  }
+}
+
+/* 动画 */
+@keyframes siren-flash {
+  from { filter: brightness(0.6); }
+  to   { filter: brightness(1.4) drop-shadow(0 0 12px rgba(255, 100, 0, 0.9)); }
+}
+
+@keyframes rec-blink {
+  0%, 100% { opacity: 1; }
+  50%       { opacity: 0; }
+}
+
+@keyframes marquee {
+  from { transform: translateX(100%); }
+  to   { transform: translateX(-100%); }
+}
+</style>

+ 38 - 0
src/components/alarmWindow/index.vue

@@ -0,0 +1,38 @@
+<template>
+  <div class="alarm-overlay">
+    <Alarm
+      :data="alarmData"
+      @close="$emit('close')"
+      @emergency="$emit('emergency')"
+      @confirm="$emit('confirm')"
+    />
+  </div>
+</template>
+
+<script>
+import Alarm from './alarm.vue'
+
+export default {
+  name: 'AlarmWindow',
+  components: { Alarm },
+  props: {
+    alarmData: {
+      type: Object,
+      default: () => ({})
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.alarm-overlay {
+  position: fixed;
+  inset: 0;
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(0, 0, 0, 0.75);
+  backdrop-filter: blur(4px);
+}
+</style>

+ 15 - 0
src/utils/crypto.js

@@ -0,0 +1,15 @@
+import CryptoJS from 'crypto-js'
+
+const key = CryptoJS.enc.Utf8.parse('J4ny0Ja678Y7P2so')
+const iv  = CryptoJS.enc.Utf8.parse('pTNorfvZW2UZJbd0')
+
+export function Decrypt(word) {
+  const encryptedHexStr = CryptoJS.enc.Hex.parse(word)
+  const srcs    = CryptoJS.enc.Base64.stringify(encryptedHexStr)
+  const decrypt = CryptoJS.AES.decrypt(srcs, key, {
+    iv,
+    mode:    CryptoJS.mode.CBC,
+    padding: CryptoJS.pad.Pkcs7
+  })
+  return decrypt.toString(CryptoJS.enc.Utf8)
+}

+ 112 - 1
src/views/Screen.vue

@@ -25,10 +25,19 @@
         <AlarmInfo />
       </div>
     </div>
+    <!-- 报警弹窗 -->
+    <AlarmWindow
+      v-if="showAlarm"
+      :alarm-data="alarmData"
+      @close="showAlarm = false"
+      @emergency="showAlarm = false"
+      @confirm="showAlarm = false"
+    />
   </div>
 </template>
 
 <script>
+import mqtt from 'mqtt'
 import ScreenHeader from '@/components/ScreenHeader.vue'
 import EventStats from '@/components/EventStats.vue'
 import SafetyCompliance from '@/components/SafetyCompliance.vue'
@@ -38,6 +47,10 @@ import EquipmentStats from '@/components/EquipmentStats.vue'
 import SecurityMonitor from '@/components/SecurityMonitor.vue'
 import LabEnvironment from '@/components/LabEnvironment.vue'
 import AlarmInfo from '@/components/AlarmInfo.vue'
+import AlarmWindow from '@/components/alarmWindow/index.vue'
+import { getConfigByType } from '@/api/auth'
+import { selectTriggerInfo } from '@/api/screen'
+import { Decrypt } from '@/utils/crypto'
 
 export default {
   name: 'Screen',
@@ -50,7 +63,105 @@ export default {
     EquipmentStats,
     SecurityMonitor,
     LabEnvironment,
-    AlarmInfo
+    AlarmInfo,
+    AlarmWindow
+  },
+  data() {
+    return {
+      showAlarm: false,
+      alarmData: {},
+      mqttClient: null
+    }
+  },
+  mounted() {
+    this.initMqttConfig()
+    this.selectTriggerInfo();
+  },
+  beforeDestroy() {
+    if (this.mqttClient) {
+      this.mqttClient.end(true)
+      this.mqttClient = null
+    }
+  },
+  methods: {
+    async initMqttConfig() {
+      try {
+        const res = await getConfigByType({ category: 2, configType: 5 })
+        if (res.code === 200 && res.data && res.data.configValue) {
+          const cfg = JSON.parse(res.data.configValue)
+          let urlText = window.location.href.split('://')[0]+'://';
+          let outerNet = window.location.href.indexOf(cfg.ipIdentify) == -1;
+          if(outerNet){//外网
+            localStorage.setItem('mqttUrl', 'wss://'+Decrypt(cfg.mqttExtranetUrl))
+            localStorage.setItem('mqttUser', Decrypt(cfg.mqttExtranetUser))
+            localStorage.setItem('mqttPassword', Decrypt(cfg.mqttExtranetPassword))
+            localStorage.setItem('fileBrowseEnvironment',urlText+Decrypt(cfg.fileBrowseEnvironmentExtranet))
+            localStorage.setItem('fileBrowseEnvironmentExtranet',urlText+Decrypt(cfg.fileBrowseEnvironmentExtranet))
+          }else{
+            localStorage.setItem('mqttUrl', 'ws://'+Decrypt(cfg.mqttIntranetUrl))
+            localStorage.setItem('mqttUser', Decrypt(cfg.mqttIntranetUser))
+            localStorage.setItem('mqttPassword', Decrypt(cfg.mqttIntranetPassword))
+            localStorage.setItem('fileBrowseEnvironment',urlText+Decrypt(cfg.fileBrowseEnvironment))
+            localStorage.setItem('fileBrowseEnvironmentExtranet',urlText+Decrypt(cfg.fileBrowseEnvironmentExtranet))
+          }
+        }
+      } catch (e) {
+        // 配置获取失败不影响页面正常使用
+      }
+      this.initMqtt()
+    },
+    initMqtt() {
+      let self = this;
+      const url      = localStorage.getItem('mqttUrl')
+      const username = localStorage.getItem('mqttUser')
+      const password = localStorage.getItem('mqttPassword')
+      if (!url) return
+
+      try {
+        this.mqttClient = mqtt.connect(url, { username, password, reconnectPeriod: 5000 })
+
+        this.mqttClient.on('connect', () => {
+          // this.mqttClient.subscribe('#')
+          this.mqttClient.subscribe('lab/risk/plan/change', (err) => {
+            if (!err) {
+              console.log("预案-订阅成功:lab/risk/plan/change");
+            }else{
+              // console.log("预案-连接错误:" + err);
+            }
+          });
+        })
+
+        this.mqttClient.on('message', async (topic, message) => {
+          if (this.showAlarm) return
+          try {
+            const payload = JSON.parse(message.toString())
+            if(payload){
+              this.selectTriggerInfo();
+            }
+          } catch (e) {
+            // 忽略
+          }
+        })
+
+        this.mqttClient.on('error', () => {
+          this.mqttClient.end(true)
+        })
+      } catch (e) {
+        // MQTT 连接失败不影响页面正常使用
+      }
+    },
+    
+    async selectTriggerInfo() {
+      const res = await selectTriggerInfo()
+      if (res.code === 200 && res.data && res.data.length) {
+        res.data[0].triggerUploadData = JSON.parse(res.data[0].triggerUploadData)
+        this.$set(this,'alarmData', res.data[0]);
+        this.$set(this,'showAlarm',true);
+      }else{
+        this.$set(this,'showAlarm',false);
+        this.$set(this,'alarmData', {});
+      }
+    }
   }
 }
 </script>