更新依赖版本,添加音效功能,优化WebSocket连接及图表显示逻辑

This commit is contained in:
6666 2025-03-16 00:52:48 +08:00
parent a8b6fb4b8b
commit 686b5facf8
7 changed files with 1358 additions and 567 deletions

858
package-lock.json generated
View File

@ -63,8 +63,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.26.9",
"integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==",
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@ -116,9 +117,394 @@
"vue": "^3.2.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz",
"integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz",
"integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz",
"integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz",
"integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz",
"integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
"integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz",
"integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz",
"integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz",
"integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz",
"integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz",
"integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz",
"integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz",
"integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz",
"integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz",
"integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz",
"integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz",
"integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz",
"integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz",
"integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz",
"integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz",
"integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz",
"integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz",
"integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz",
"integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.24.2",
"integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz",
"integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==",
"cpu": [
"x64"
],
@ -1176,8 +1562,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.7.9",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
"integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
@ -1474,8 +1861,9 @@
}
},
"node_modules/esbuild": {
"version": "0.24.2",
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
"integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==",
"dev": true,
"hasInstallScript": true,
"bin": {
@ -1485,415 +1873,31 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.24.2",
"@esbuild/android-arm": "0.24.2",
"@esbuild/android-arm64": "0.24.2",
"@esbuild/android-x64": "0.24.2",
"@esbuild/darwin-arm64": "0.24.2",
"@esbuild/darwin-x64": "0.24.2",
"@esbuild/freebsd-arm64": "0.24.2",
"@esbuild/freebsd-x64": "0.24.2",
"@esbuild/linux-arm": "0.24.2",
"@esbuild/linux-arm64": "0.24.2",
"@esbuild/linux-ia32": "0.24.2",
"@esbuild/linux-loong64": "0.24.2",
"@esbuild/linux-mips64el": "0.24.2",
"@esbuild/linux-ppc64": "0.24.2",
"@esbuild/linux-riscv64": "0.24.2",
"@esbuild/linux-s390x": "0.24.2",
"@esbuild/linux-x64": "0.24.2",
"@esbuild/netbsd-arm64": "0.24.2",
"@esbuild/netbsd-x64": "0.24.2",
"@esbuild/openbsd-arm64": "0.24.2",
"@esbuild/openbsd-x64": "0.24.2",
"@esbuild/sunos-x64": "0.24.2",
"@esbuild/win32-arm64": "0.24.2",
"@esbuild/win32-ia32": "0.24.2",
"@esbuild/win32-x64": "0.24.2"
}
},
"node_modules/esbuild/node_modules/@esbuild/aix-ppc64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
"integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/android-arm": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
"integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/android-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
"integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/android-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
"integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/darwin-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
"integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/darwin-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
"integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
"integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/freebsd-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
"integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-arm": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
"integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
"integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-ia32": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
"integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-loong64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
"integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-mips64el": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
"integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-ppc64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
"integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-riscv64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
"integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-s390x": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
"integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
"integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/netbsd-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
"integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/netbsd-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
"integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/openbsd-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
"integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/openbsd-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
"integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/sunos-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
"integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/win32-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
"integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild/node_modules/@esbuild/win32-ia32": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
"integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
"@esbuild/aix-ppc64": "0.25.1",
"@esbuild/android-arm": "0.25.1",
"@esbuild/android-arm64": "0.25.1",
"@esbuild/android-x64": "0.25.1",
"@esbuild/darwin-arm64": "0.25.1",
"@esbuild/darwin-x64": "0.25.1",
"@esbuild/freebsd-arm64": "0.25.1",
"@esbuild/freebsd-x64": "0.25.1",
"@esbuild/linux-arm": "0.25.1",
"@esbuild/linux-arm64": "0.25.1",
"@esbuild/linux-ia32": "0.25.1",
"@esbuild/linux-loong64": "0.25.1",
"@esbuild/linux-mips64el": "0.25.1",
"@esbuild/linux-ppc64": "0.25.1",
"@esbuild/linux-riscv64": "0.25.1",
"@esbuild/linux-s390x": "0.25.1",
"@esbuild/linux-x64": "0.25.1",
"@esbuild/netbsd-arm64": "0.25.1",
"@esbuild/netbsd-x64": "0.25.1",
"@esbuild/openbsd-arm64": "0.25.1",
"@esbuild/openbsd-x64": "0.25.1",
"@esbuild/sunos-x64": "0.25.1",
"@esbuild/win32-arm64": "0.25.1",
"@esbuild/win32-ia32": "0.25.1",
"@esbuild/win32-x64": "0.25.1"
}
},
"node_modules/escape-html": {
@ -2402,8 +2406,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.1",
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"funding": [
{
"type": "opencollective",
@ -2437,9 +2442,9 @@
}
},
"node_modules/prismjs": {
"version": "1.29.0",
"resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
"engines": {
"node": ">=6"
}
@ -2905,12 +2910,13 @@
"dev": true
},
"node_modules/vite": {
"version": "6.1.0",
"integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz",
"integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==",
"dev": true,
"dependencies": {
"esbuild": "^0.24.2",
"postcss": "^8.5.1",
"esbuild": "^0.25.0",
"postcss": "^8.5.3",
"rollup": "^4.30.1"
},
"bin": {

View File

@ -1,4 +1,4 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createRouter, createWebHashHistory } from 'vue-router'
import AdminLayout from '../layout/AdminLayout.vue'
import { useUserStore } from '../stores/user'
@ -24,7 +24,7 @@ Promise.all([
])
const router = createRouter({
history: createWebHistory(),
history: createWebHashHistory(),
routes: [
{
path: '/',

35
src/utils/SoundEffect.js Normal file
View File

@ -0,0 +1,35 @@
class SoundEffect {
constructor() {
this.SOUND_CONNECT = new Audio('/src/assets/harmonyos-sound/notification_accomplished_08.wav');
this.SOUND_DISCONNECT = new Audio('/src/assets/harmonyos-sound/notification_wrong_04.wav');
}
// 播放连接成功音效
playConnectSound() {
this.SOUND_CONNECT.currentTime = 0;
this.SOUND_CONNECT.play().catch(error => {
console.error('音效播放失败:', error);
});
}
// 播放断开连接音效
playDisconnectSound() {
this.SOUND_DISCONNECT.currentTime = 0;
this.SOUND_DISCONNECT.play().catch(error => {
console.error('音效播放失败:', error);
});
}
// 根据状态播放对应音效
playStatusSound(isOnline) {
if (isOnline) {
this.playConnectSound();
} else {
this.playDisconnectSound();
}
}
}
// 创建单例实例
const soundEffect = new SoundEffect();
export default soundEffect;

View File

@ -1,10 +1,36 @@
<script setup>
import { ref, onMounted, onUnmounted, watch, nextTick, onActivated, onDeactivated } from 'vue'
import { Plus, VideoCamera, Loading } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElNotification } from 'element-plus'
import { getDeviceList } from '@/api/device'
import { createFlvPlayer, destroyFlvPlayer } from '@/utils/videoPlayer'
//
const SOUND_CONNECT = new Audio('/src/assets/harmonyos-sound/notification_accomplished_08.wav')
const SOUND_DISCONNECT = new Audio('/src/assets/harmonyos-sound/notification_wrong_04.wav')
//
const preloadSounds = () => {
SOUND_CONNECT.load()
SOUND_DISCONNECT.load()
//
SOUND_CONNECT.addEventListener('canplaythrough', () => {
console.log('连接音效加载完成')
})
SOUND_DISCONNECT.addEventListener('canplaythrough', () => {
console.log('断开音效加载完成')
})
//
SOUND_CONNECT.addEventListener('error', (e) => {
console.error('连接音效加载失败:', e)
})
SOUND_DISCONNECT.addEventListener('error', (e) => {
console.error('断开音效加载失败:', e)
})
}
//
const droneList = ref([])
const loading = ref(false)
@ -15,6 +41,12 @@ const flvPlayers = ref([])
const playingVideos = ref(new Set()) //
let ws = null // WebSocket
// WebSocket
const wsRetryCount = ref(0)
const MAX_RETRY_COUNT = 3
let wsRetryTimer = null
let pollingTimer = null
// playingVideos Set
const isVideoPlaying = (code) => {
return playingVideos.value?.has(code) || false
@ -112,58 +144,169 @@ const currentDroneIndex = ref(0)
// WebSocket
const initWebSocket = () => {
ws = new WebSocket('ws://192.168.1.158:6894')
ws.onopen = () => {
console.log('WebSocket连接成功')
if (wsRetryCount.value >= MAX_RETRY_COUNT) {
console.warn('WebSocket重试次数超过限制切换到HTTP轮询模式')
startPolling()
return
}
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
if (data.type === 'device_status') {
//
const droneIndex = droneList.value.findIndex(drone => drone.code === data.device_code)
if (droneIndex !== -1) {
const newStatus = data.status === 1 ? 'online' : 'offline'
try {
if (ws) {
ws.close()
ws = null
}
//
if (droneList.value[droneIndex].status !== newStatus) {
droneList.value[droneIndex].status = newStatus
ws = new WebSocket('ws://localhost:6894')
// 线线
if (newStatus === 'online' &&
(!droneList.value[currentDroneIndex.value] ||
droneList.value[currentDroneIndex.value].status !== 'online')) {
currentDroneIndex.value = droneIndex
}
//
if (droneIndex === currentDroneIndex.value) {
nextTick(() => {
initVideoPlayers()
})
}
}
}
ws.onopen = () => {
wsRetryCount.value = 0
if (wsRetryTimer) {
clearTimeout(wsRetryTimer)
wsRetryTimer = null
}
}
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
console.log('收到WebSocket消息:', data) //
if (data.type === 'device_status') {
updateDeviceStatus(data)
}
} catch (error) {
console.error('WebSocket消息处理错误:', error)
}
}
ws.onerror = (error) => {
console.error('WebSocket错误:', error)
wsRetryCount.value++
reconnectWebSocket()
}
ws.onclose = () => {
console.log('WebSocket连接关闭')
reconnectWebSocket()
}
} catch (error) {
console.error('WebSocket连接失败:', error)
reconnectWebSocket()
}
}
//
const updateDeviceStatus = (data) => {
const droneIndex = droneList.value.findIndex(drone => drone.code === data.device_code)
if (droneIndex !== -1) {
const newStatus = data.status === 1 ? 'online' : 'offline'
const oldStatus = droneList.value[droneIndex].status
//
if (oldStatus !== newStatus) {
console.log('设备状态变化:', {
deviceName: droneList.value[droneIndex].name,
oldStatus,
newStatus
})
//
const updatedDrone = {
...droneList.value[droneIndex],
status: newStatus,
battery_level: data.battery_level || droneList.value[droneIndex].battery_level,
signal_strength: data.signal_strength || droneList.value[droneIndex].signal_strength
}
// 使Vue
droneList.value.splice(droneIndex, 1, updatedDrone)
//
showDeviceStatusNotification(newStatus, updatedDrone.name)
// 线线
if (newStatus === 'online' &&
(!droneList.value[currentDroneIndex.value] ||
droneList.value[currentDroneIndex.value].status !== 'online')) {
currentDroneIndex.value = droneIndex
}
//
if (droneIndex === currentDroneIndex.value) {
nextTick(() => {
initVideoPlayers()
})
}
} catch (error) {
console.error('WebSocket消息处理错误:', error)
}
}
}
ws.onerror = (error) => {
console.error('WebSocket错误:', error)
// WebSocket
const reconnectWebSocket = () => {
if (wsRetryTimer) return
wsRetryTimer = setTimeout(() => {
console.log('尝试重新连接WebSocket...')
initWebSocket()
}, 5000)
}
//
const startPolling = () => {
if (pollingTimer) return
//
getList()
// 10
pollingTimer = setInterval(() => {
getList()
}, 10000) // 10
}
//
const stopPolling = () => {
if (pollingTimer) {
clearInterval(pollingTimer)
pollingTimer = null
}
}
//
const showDeviceStatusNotification = async (status, deviceName) => {
try {
//
const sound = status === 'online' ? SOUND_CONNECT : SOUND_DISCONNECT
sound.currentTime = 0 //
//
const playPromise = sound.play()
if (playPromise !== undefined) {
playPromise.catch(error => {
console.error('音效播放失败:', error)
//
if (error.name === 'NotAllowedError') {
console.log('浏览器阻止了自动播放,将在下次用户交互时尝试播放')
}
})
}
} catch (error) {
console.error('音效处理失败:', error)
}
ws.onclose = () => {
setTimeout(initWebSocket, 5000)
}
ElNotification({
title: status === 'online' ? '设备上线' : '设备离线',
message: `${deviceName} ${status === 'online' ? '已连接到系统' : '已断开连接'}`,
type: status === 'online' ? 'success' : 'warning',
duration: 3000,
position: 'top-right'
})
}
//
const getList = async () => {
if (loading.value) return
loading.value = true
try {
const res = await getDeviceList({
page: pagination.value.page,
@ -172,11 +315,10 @@ const getList = async () => {
})
if (res.code === 200) {
droneList.value = res.data.list.map(drone => {
// WebSocket
const wsConnected = ws?.readyState === WebSocket.OPEN
const deviceStatus = drone.status.code || drone.status
const deviceOnline = wsConnected && deviceStatus === 1
const newDroneList = res.data.list.map(drone => {
//
const deviceStatus = drone.status?.code || drone.status
const deviceOnline = deviceStatus === 1
const status = deviceOnline ? 'online' : 'offline'
return {
@ -190,6 +332,19 @@ const getList = async () => {
}
})
//
if (droneList.value.length > 0) {
newDroneList.forEach((newDrone) => {
const oldDrone = droneList.value.find(d => d.code === newDrone.code)
if (oldDrone && oldDrone.status !== newDrone.status) {
showDeviceStatusNotification(newDrone.status, newDrone.name)
}
})
}
//
droneList.value = newDroneList
// 线线
const currentDrone = droneList.value[currentDroneIndex.value]
if (!currentDrone || currentDrone.status !== 'online') {
@ -238,6 +393,7 @@ const handleCurrentChange = (val) => {
onActivated(() => {
// WebSocket
if (!ws || ws.readyState !== WebSocket.OPEN) {
wsRetryCount.value = 0
initWebSocket()
}
//
@ -264,14 +420,20 @@ onDeactivated(() => {
})
flvPlayers.value = []
playingVideos.value.clear()
//
stopPolling()
})
// onMounted
onMounted(() => {
// WebSocket
initWebSocket()
//
wsRetryCount.value = 0
//
preloadSounds()
//
getList()
// WebSocket
initWebSocket()
})
// onUnmounted
@ -285,6 +447,16 @@ onUnmounted(() => {
destroyFlvPlayer(mainFlvPlayer.value)
}
flvPlayers.value.forEach(player => destroyFlvPlayer(player))
//
if (wsRetryTimer) {
clearTimeout(wsRetryTimer)
wsRetryTimer = null
}
stopPolling()
//
wsRetryCount.value = 0
})
</script>

View File

@ -3,10 +3,285 @@ import { ref, onMounted, onUnmounted } from "vue";
import { Plus, Monitor, Timer } from "@element-plus/icons-vue";
import { ElMessage, ElNotification } from "element-plus";
import { getDeviceList, deleteDevice } from "@/api/device";
import * as echarts from 'echarts';
import soundEffect from '@/utils/SoundEffect';
//
const loading = ref(false);
//
const chartDialogVisible = ref(false);
const chartInstance = ref(null);
const chartData = ref([]);
const currentSensor = ref(null);
//
const initChart = () => {
if (!chartInstance.value) {
const chartDom = document.getElementById('sensorChart');
if (!chartDom) return;
chartInstance.value = echarts.init(chartDom);
}
const option = {
backgroundColor: '#ffffff',
title: {
text: 'TDS值实时监测趋势',
left: 'center',
top: 10,
textStyle: {
fontSize: 18,
fontWeight: 500,
color: '#333'
}
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderColor: '#409EFF',
borderWidth: 1,
padding: [10, 15],
textStyle: {
color: '#666'
},
axisPointer: {
type: 'line',
lineStyle: {
color: '#409EFF',
opacity: 0.2,
width: 2
}
},
formatter: (params) => {
const data = params[0];
return `
<div style="font-family: Arial, sans-serif;">
<div style="color: #666; margin-bottom: 6px;">${data.name}</div>
<div style="color: #333; font-weight: bold; font-size: 14px;">
TDS值${data.value} mg/L
</div>
</div>
`;
}
},
grid: {
top: 80,
left: 60,
right: 40,
bottom: 60,
containLabel: true
},
xAxis: {
type: 'category',
data: chartData.value.map(item => item.time),
axisLabel: {
rotate: 45,
formatter: (value) => {
const [hour, minute, second] = value.split(':');
return `${hour}:${minute}:${second}`;
},
color: '#666',
fontSize: 12
},
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#eee'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#f5f5f5',
type: 'dashed'
}
}
},
yAxis: {
type: 'value',
name: 'TDS值 (mg/L)',
nameLocation: 'middle',
nameGap: 45,
nameTextStyle: {
color: '#666',
fontSize: 13,
fontWeight: 500,
padding: [0, 0, 10, 0]
},
axisLine: {
show: true,
lineStyle: {
color: '#eee'
}
},
axisTick: {
show: false
},
axisLabel: {
color: '#666',
fontSize: 12,
formatter: '{value}'
},
splitLine: {
lineStyle: {
color: '#f5f5f5',
type: 'dashed'
}
}
},
series: [{
name: 'TDS值',
type: 'line',
data: chartData.value.map(item => item.value),
smooth: true,
symbol: 'circle',
symbolSize: 8,
lineStyle: {
width: 4,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#409EFF' },
{ offset: 1, color: '#36CE9E' }
]),
shadowColor: 'rgba(64,158,255,0.3)',
shadowBlur: 10
},
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#409EFF' },
{ offset: 1, color: '#36CE9E' }
]),
borderColor: '#fff',
borderWidth: 2,
shadowColor: 'rgba(64,158,255,0.5)',
shadowBlur: 5
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64,158,255,0.3)' },
{ offset: 0.5, color: 'rgba(64,158,255,0.15)' },
{ offset: 1, color: 'rgba(64,158,255,0.05)' }
])
},
emphasis: {
itemStyle: {
borderWidth: 3,
borderColor: '#fff',
shadowColor: 'rgba(64,158,255,0.8)',
shadowBlur: 10
}
},
animationDuration: 1000,
animationEasing: 'cubicOut'
}]
};
// y
if (currentSensor.value?.monitoringType === 'h') {
option.yAxis.min = 1180;
option.yAxis.max = 1190;
option.series[0].lineStyle.color = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#36CE9E' },
{ offset: 1, color: '#3B7FFF' }
]);
option.series[0].itemStyle.color = option.series[0].lineStyle.color;
option.series[0].areaStyle.color = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(54,206,158,0.3)' },
{ offset: 1, color: 'rgba(59,127,255,0.05)' }
]);
} else if (currentSensor.value?.monitoringType === 'c') {
option.yAxis.min = 220;
option.yAxis.max = 228;
option.series[0].lineStyle.color = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#FF9F43' },
{ offset: 1, color: '#FF6B6B' }
]);
option.series[0].itemStyle.color = option.series[0].lineStyle.color;
option.series[0].areaStyle.color = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255,159,67,0.3)' },
{ offset: 1, color: 'rgba(255,107,107,0.05)' }
]);
} else if (currentSensor.value?.monitoringType === 'f') {
option.yAxis.min = 466;
option.yAxis.max = 474;
option.series[0].lineStyle.color = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#845EF7' },
{ offset: 1, color: '#BE4BDB' }
]);
option.series[0].itemStyle.color = option.series[0].lineStyle.color;
option.series[0].areaStyle.color = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(132,94,247,0.3)' },
{ offset: 1, color: 'rgba(190,75,219,0.05)' }
]);
}
chartInstance.value.setOption(option);
}
//
const handleResize = () => {
if (chartInstance.value) {
chartInstance.value.resize();
}
}
//
const showChart = (sensor) => {
//
currentSensor.value = JSON.parse(JSON.stringify(sensor));
chartDialogVisible.value = true;
//
const now = new Date();
chartData.value = Array.from({ length: 10 }, (_, i) => {
const time = new Date(now - (9 - i) * 1000);
let value;
//
if (sensor.monitoringType === 'h') {
// (1000-1370)
value = 1185 + (Math.random() * 6 - 3);
} else if (sensor.monitoringType === 'c') {
// (200-248)
value = 224 + (Math.random() * 4 - 2);
} else if (sensor.monitoringType === 'f') {
// (450-490)
value = 470 + (Math.random() * 4 - 2);
} else {
value = sensor.data?.tds || 0;
}
return {
time: time.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}),
value: Number(value.toFixed(2))
};
});
// tick
setTimeout(() => {
initChart();
}, 100);
}
//
onMounted(() => {
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
if (chartInstance.value) {
chartInstance.value.dispose();
}
});
//
const sensorList = ref([
{
@ -168,6 +443,53 @@ const dataRefreshTimers = ref({
f: null,
});
//
const updateChartData = (value) => {
if (!chartDialogVisible.value) return;
const now = new Date();
const time = now.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
//
chartData.value.push({
time,
value: Number(value.toFixed(2))
});
// 30
if (chartData.value.length > 30) {
chartData.value.shift();
}
//
if (chartInstance.value) {
chartInstance.value.setOption({
xAxis: {
data: chartData.value.map(item => item.time)
},
series: [{
data: chartData.value.map(item => item.value)
}]
});
}
//
if (currentSensor.value) {
currentSensor.value = {
...currentSensor.value,
data: {
...currentSensor.value.data,
tds: value
}
};
}
};
//
const handleKeyPress = (event) => {
const key = event.key.toLowerCase();
@ -184,6 +506,17 @@ const handleKeyPress = (event) => {
},
}));
//
if (currentSensor.value) {
currentSensor.value = {
...currentSensor.value,
status: isOnline ? 0 : 1,
data: {
tds: null
}
};
}
//
Object.entries(dataRefreshTimers.value).forEach(([timerKey, timer]) => {
if (timer) {
@ -192,6 +525,9 @@ const handleKeyPress = (event) => {
}
});
//
soundEffect.playStatusSound(!isOnline);
ElNotification({
title: isOnline ? "设备离线" : "设备上线",
message: isOnline ? "设备已停止工作" : "设备开始工作",
@ -211,14 +547,17 @@ const handleKeyPress = (event) => {
h: {
// (1000-1370)
tds: 1185,
monitoringType: 'h'
},
c: {
// (200-248)
tds: 224,
monitoringType: 'c'
},
f: {
// (450-490)
tds: 470,
monitoringType: 'f'
},
};
@ -233,7 +572,19 @@ const handleKeyPress = (event) => {
data: {
tds: null,
},
monitoringType: null
}));
//
if (currentSensor.value) {
currentSensor.value = {
...currentSensor.value,
data: {
tds: null
},
monitoringType: null
};
}
return;
}
@ -247,6 +598,19 @@ const handleKeyPress = (event) => {
//
const startData = substanceData[key];
sensorList.value = sensorList.value.map(sensor => ({
...sensor,
monitoringType: key
}));
//
if (currentSensor.value) {
currentSensor.value = {
...currentSensor.value,
monitoringType: key
};
}
dataRefreshTimers.value[key] = setInterval(() => {
//
let randomValue;
@ -278,6 +642,9 @@ const handleKeyPress = (event) => {
tds: randomValue,
},
}));
//
updateChartData(randomValue);
}, 1000); //
}
};
@ -313,6 +680,7 @@ onUnmounted(() => {
:key="sensor.id"
class="sensor-card"
:class="{ offline: sensor.status === 0 }"
@click="showChart(sensor)"
>
<div class="card-header">
<div class="sensor-info">
@ -404,6 +772,41 @@ onUnmounted(() => {
@current-change="handleCurrentChange"
/>
</div>
<!-- 图表对话框 -->
<el-dialog
v-model="chartDialogVisible"
:title="currentSensor?.device_name"
width="80%"
destroy-on-close
@closed="chartInstance = null"
>
<div class="chart-container">
<!-- TDS值显示区域 -->
<div class="tds-display">
<div class="tds-value">
<span class="label">实时TDS值</span>
<span class="value" :class="{ 'offline': currentSensor?.status === 0 }">
{{ formatValue(currentSensor?.data?.tds, 'mg/L') }}
</span>
</div>
<div class="tds-info">
<el-tag
:type="currentSensor?.status === 1 ? 'success' : 'info'"
size="large"
>
{{ currentSensor?.status === 1 ? '在线监测中' : '设备离线' }}
</el-tag>
<span class="update-time">
<el-icon><Timer /></el-icon>
更新时间: {{ new Date().toLocaleTimeString('zh-CN', { hour12: false }) }}
</span>
</div>
</div>
<!-- 图表区域 -->
<div id="sensorChart" style="width: 100%; height: 400px;"></div>
</div>
</el-dialog>
</div>
</template>
@ -450,10 +853,11 @@ onUnmounted(() => {
overflow: hidden;
transition: all 0.3s;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
cursor: pointer;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
transform: translateY(-4px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
}
.card-header {
@ -628,4 +1032,58 @@ onUnmounted(() => {
align-items: center;
}
}
//
.chart-container {
.tds-display {
background: #f8f9fb;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
.tds-value {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 16px;
.label {
font-size: 16px;
color: #606266;
margin-bottom: 8px;
}
.value {
font-size: 48px;
font-weight: 600;
color: #409EFF;
font-family: "DIN Alternate", sans-serif;
&.offline {
color: #909399;
}
}
}
.tds-info {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
.update-time {
color: #909399;
font-size: 14px;
display: flex;
align-items: center;
gap: 4px;
.el-icon {
font-size: 16px;
}
}
}
}
}
</style>

View File

@ -10,10 +10,7 @@ import {
Connection,
Loading
} from '@element-plus/icons-vue'
//
const SOUND_CONNECT = new Audio('/src/assets/harmonyos-sound/notification_accomplished_08.wav')
const SOUND_DISCONNECT = new Audio('/src/assets/harmonyos-sound/notification_wrong_04.wav')
import soundEffect from '@/utils/SoundEffect'
//
let hasUserInteracted = false
@ -26,17 +23,6 @@ const handleUserInteraction = () => {
document.removeEventListener('keydown', handleUserInteraction)
}
//
const playSound = (status) => {
if (!hasUserInteracted) return
const sound = status === 'online' ? SOUND_CONNECT : SOUND_DISCONNECT
sound.currentTime = 0 //
sound.play().catch(error => {
console.error('音效播放失败:', error)
})
}
//
const drone = ref(null)
const loading = ref(false)
@ -55,101 +41,228 @@ let wsRetryCount = 0
const MAX_RETRY_COUNT = 3
let wsRetryTimer = null
//
const CHART_DATA_STORAGE_KEY = 'wetland_ph_chart_data'
const CHART_DATA_TIMESTAMP_KEY = 'wetland_ph_chart_timestamp'
const CHART_DATA_EXPIRE_TIME = 24 * 60 * 60 * 1000 // 24
//
const saveChartDataToStorage = (data) => {
try {
localStorage.setItem(CHART_DATA_STORAGE_KEY, JSON.stringify(data))
localStorage.setItem(CHART_DATA_TIMESTAMP_KEY, Date.now().toString())
} catch (error) {
console.error('保存图表数据到本地存储失败:', error)
}
}
//
const getChartDataFromStorage = () => {
try {
const timestamp = localStorage.getItem(CHART_DATA_TIMESTAMP_KEY)
if (!timestamp) return null
//
if (Date.now() - parseInt(timestamp) > CHART_DATA_EXPIRE_TIME) {
localStorage.removeItem(CHART_DATA_STORAGE_KEY)
localStorage.removeItem(CHART_DATA_TIMESTAMP_KEY)
return null
}
const data = localStorage.getItem(CHART_DATA_STORAGE_KEY)
return data ? JSON.parse(data) : null
} catch (error) {
console.error('从本地存储获取图表数据失败:', error)
return null
}
}
//
const getChartData = async () => {
try {
const res = await generateChartData({
message: "ph柱状图"
})
if (res.success) {
initChart(res.data)
console.log('完整的响应数据:', res);
console.log('图表配置数据:', res.data?.echart_options);
if (!res.data?.echart_options) {
console.error('图表配置数据结构不完整');
// 使
const storageData = getChartDataFromStorage()
if (storageData) {
console.log('使用本地存储的图表数据');
initChart(storageData)
} else {
initChart(getDefaultChartData().echart_options)
}
return;
}
//
saveChartDataToStorage(res.data.echart_options)
initChart(res.data.echart_options)
} else {
console.error('获取图表数据失败:', res.message)
initChart(getDefaultChartData())
// 使
const storageData = getChartDataFromStorage()
if (storageData) {
console.log('使用本地存储的图表数据');
initChart(storageData)
} else {
initChart(getDefaultChartData().echart_options)
}
}
} catch (error) {
console.error('获取图表数据失败:', error)
initChart(getDefaultChartData())
// 使
const storageData = getChartDataFromStorage()
if (storageData) {
console.log('使用本地存储的图表数据');
initChart(storageData)
} else {
initChart(getDefaultChartData().echart_options)
}
}
}
//
const initChart = (data) => {
if (!chartRef.value) return
if (chart) {
chart.dispose()
const initChart = (options) => {
if (!chartRef.value) {
console.error('图表DOM引用未找到');
return;
}
chart = echarts.init(chartRef.value)
try {
if (chart) {
chart.dispose()
}
// 使
const option = {
backgroundColor: 'transparent',
title: {
...data.echart_options.title,
textStyle: {
color: '#fff'
}
},
tooltip: data.echart_options.tooltip,
legend: {
...data.echart_options.legend,
textStyle: {
color: '#fff'
}
},
xAxis: {
type: 'category',
data: data.echart_options.series[0].xAxis.data,
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
chart = echarts.init(chartRef.value)
//
if (!options.xAxis?.data && !options.series?.[0]?.data) {
console.error('数据结构不正确,使用默认配置');
options = getDefaultChartData().echart_options;
}
// yAxis
const yAxisConfig = Array.isArray(options.yAxis) ? options.yAxis[0] : options.yAxis;
const option = {
backgroundColor: 'transparent',
title: {
...(options.title || { text: 'pH值变化趋势' }),
textStyle: {
color: '#fff'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
yAxis: {
type: 'value',
name: 'pH值',
max: 8,
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
tooltip: options.tooltip || {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)'
},
splitLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.1)'
legend: {
...(options.legend || {}),
textStyle: {
color: '#fff'
}
}
},
series: [{
name: '酸性指数',
type: 'line',
smooth: true,
data: data.echart_options.series[0].xAxis.data.map(() => (Math.random() * 2 + 6).toFixed(1)), //
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.7)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
])
},
itemStyle: {
color: '#409EFF'
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: options.xAxis?.data || [],
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
yAxis: {
type: 'value',
name: 'pH值',
max: 8,
...(yAxisConfig || {}),
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)'
},
splitLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.1)'
}
}
},
series: Array.isArray(options.series) ? options.series.map(series => ({
...series,
yAxisIndex: 0, // 使 yAxis
areaStyle: series.areaStyle || {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.7)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
])
}
})) : [
{
name: 'pH值',
type: 'line',
smooth: true,
yAxisIndex: 0,
data: [],
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.7)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
])
}
}
]
}
//
if (!option.yAxis || !option.xAxis || !option.series) {
throw new Error('图表配置数据结构不完整');
}
chart.setOption(option)
//
nextTick(() => {
if (chart) {
chart.resize()
}
}]
})
} catch (error) {
console.error('图表初始化失败:', error);
// 使
try {
if (chart) {
const defaultOption = getDefaultChartData().echart_options;
chart.setOption(defaultOption);
}
} catch (retryError) {
console.error('使用默认配置重试失败:', retryError);
}
}
chart.setOption(option)
}
//
//
const getDefaultChartData = () => {
return {
echart_options: {
@ -169,18 +282,17 @@ const getDefaultChartData = () => {
type: "category",
data: []
},
yAxis: [
{
type: "value",
name: "pH值",
max: 8
}
],
yAxis: {
type: "value",
name: "pH值",
max: 8
},
series: [
{
name: "pH值",
type: "line",
smooth: true,
yAxisIndex: 0,
areaStyle: {},
data: []
}
@ -192,7 +304,7 @@ const getDefaultChartData = () => {
//
const showDeviceStatusNotification = (status) => {
//
playSound(status)
soundEffect.playStatusSound(status === 'online')
ElNotification({
title: status === 'online' ? '设备已接入' : '设备已接出',
@ -222,7 +334,6 @@ const initWebSocket = () => {
ws = new WebSocket('ws://127.0.0.1:6894')
ws.onopen = () => {
console.log('WebSocket连接成功')
wsConnected.value = true
wsRetryCount = 0 //
if (wsRetryTimer) {
@ -300,6 +411,7 @@ const initWebSocket = () => {
} else {
//
wsRetryTimer = setTimeout(() => {
console.log(`尝试WebSocket重连 (${wsRetryCount}/${MAX_RETRY_COUNT})...`)
initWebSocket()
}, 5000 * Math.min(wsRetryCount, 3)) // 15
@ -579,6 +691,13 @@ onMounted(() => {
document.addEventListener('click', handleUserInteraction)
document.addEventListener('keydown', handleUserInteraction)
// 使
const storageData = getChartDataFromStorage()
if (storageData) {
console.log('使用本地存储的图表数据初始化');
initChart(storageData)
}
// WebSocket
initWebSocket()
//

View File

@ -4,6 +4,7 @@ import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [vue()],
resolve: {
alias: {