Compare commits
10 Commits
fa85515d9b
...
3e4e455dbe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e4e455dbe | ||
|
|
62a7971f7a | ||
|
|
056c1c95bf | ||
|
|
7551409394 | ||
|
|
9081420148 | ||
|
|
220bf593aa | ||
|
|
bcb0c77111 | ||
|
|
ccf101167a | ||
|
|
207913a253 | ||
|
|
f0b6d900ac |
24
README.md
24
README.md
@@ -624,6 +624,30 @@ public async updateRule(token: string | null = null, rules: RuleData[]): Promise
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2024.11.26
|
||||||
|
|
||||||
|
- 对话框大图显示
|
||||||
|
- 删除告警按钮
|
||||||
|
- 主页定时刷新
|
||||||
|
- 布局滚动查看调整
|
||||||
|
- 用户删除限制
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2024.12.3
|
||||||
|
|
||||||
|
- 告警设置
|
||||||
|
- 消息队列设置
|
||||||
|
- 声音设置
|
||||||
|
- 弹窗动画添加
|
||||||
|
- 弹窗队列添加,错误重试机制(2s*5次)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
240
package-lock.json
generated
240
package-lock.json
generated
@@ -21,6 +21,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
|
"pinia": "^2.2.6",
|
||||||
"vue": "^3.4.29",
|
"vue": "^3.4.29",
|
||||||
"vue-router": "^4.4.0"
|
"vue-router": "^4.4.0"
|
||||||
},
|
},
|
||||||
@@ -63,27 +64,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
"version": "7.24.8",
|
"version": "7.25.9",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
|
||||||
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
|
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-validator-identifier": {
|
"node_modules/@babel/helper-validator-identifier": {
|
||||||
"version": "7.24.7",
|
"version": "7.25.9",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
|
||||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.25.3",
|
"version": "7.26.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.25.3.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.2.tgz",
|
||||||
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
|
"integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.25.2"
|
"@babel/types": "^7.26.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
@@ -104,13 +105,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/types": {
|
"node_modules/@babel/types": {
|
||||||
"version": "7.25.2",
|
"version": "7.26.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.25.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.26.0.tgz",
|
||||||
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
|
"integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.24.8",
|
"@babel/helper-string-parser": "^7.25.9",
|
||||||
"@babel/helper-validator-identifier": "^7.24.7",
|
"@babel/helper-validator-identifier": "^7.25.9"
|
||||||
"to-fast-properties": "^2.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -963,49 +963,49 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-core": {
|
"node_modules/@vue/compiler-core": {
|
||||||
"version": "3.4.37",
|
"version": "3.5.13",
|
||||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.4.37.tgz",
|
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
|
||||||
"integrity": "sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==",
|
"integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.24.7",
|
"@babel/parser": "^7.25.3",
|
||||||
"@vue/shared": "3.4.37",
|
"@vue/shared": "3.5.13",
|
||||||
"entities": "^5.0.0",
|
"entities": "^4.5.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-dom": {
|
"node_modules/@vue/compiler-dom": {
|
||||||
"version": "3.4.37",
|
"version": "3.5.13",
|
||||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.4.37.tgz",
|
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
|
||||||
"integrity": "sha512-rIiSmL3YrntvgYV84rekAtU/xfogMUJIclUMeIKEtVBFngOL3IeZHhsH3UaFEgB5iFGpj6IW+8YuM/2Up+vVag==",
|
"integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-core": "3.4.37",
|
"@vue/compiler-core": "3.5.13",
|
||||||
"@vue/shared": "3.4.37"
|
"@vue/shared": "3.5.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-sfc": {
|
"node_modules/@vue/compiler-sfc": {
|
||||||
"version": "3.4.37",
|
"version": "3.5.13",
|
||||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.4.37.tgz",
|
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
|
||||||
"integrity": "sha512-vCfetdas40Wk9aK/WWf8XcVESffsbNkBQwS5t13Y/PcfqKfIwJX2gF+82th6dOpnpbptNMlMjAny80li7TaCIg==",
|
"integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.24.7",
|
"@babel/parser": "^7.25.3",
|
||||||
"@vue/compiler-core": "3.4.37",
|
"@vue/compiler-core": "3.5.13",
|
||||||
"@vue/compiler-dom": "3.4.37",
|
"@vue/compiler-dom": "3.5.13",
|
||||||
"@vue/compiler-ssr": "3.4.37",
|
"@vue/compiler-ssr": "3.5.13",
|
||||||
"@vue/shared": "3.4.37",
|
"@vue/shared": "3.5.13",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.30.10",
|
"magic-string": "^0.30.11",
|
||||||
"postcss": "^8.4.40",
|
"postcss": "^8.4.48",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-ssr": {
|
"node_modules/@vue/compiler-ssr": {
|
||||||
"version": "3.4.37",
|
"version": "3.5.13",
|
||||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.4.37.tgz",
|
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
|
||||||
"integrity": "sha512-TyAgYBWrHlFrt4qpdACh8e9Ms6C/AZQ6A6xLJaWrCL8GCX5DxMzxyeFAEMfU/VFr4tylHm+a2NpfJpcd7+20XA==",
|
"integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.4.37",
|
"@vue/compiler-dom": "3.5.13",
|
||||||
"@vue/shared": "3.4.37"
|
"@vue/shared": "3.5.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-vue2": {
|
"node_modules/@vue/compiler-vue2": {
|
||||||
@@ -1048,49 +1048,49 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/reactivity": {
|
"node_modules/@vue/reactivity": {
|
||||||
"version": "3.4.37",
|
"version": "3.5.13",
|
||||||
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.4.37.tgz",
|
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz",
|
||||||
"integrity": "sha512-UmdKXGx0BZ5kkxPqQr3PK3tElz6adTey4307NzZ3whZu19i5VavYal7u2FfOmAzlcDVgE8+X0HZ2LxLb/jgbYw==",
|
"integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.4.37"
|
"@vue/shared": "3.5.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/runtime-core": {
|
"node_modules/@vue/runtime-core": {
|
||||||
"version": "3.4.37",
|
"version": "3.5.13",
|
||||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.4.37.tgz",
|
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
|
||||||
"integrity": "sha512-MNjrVoLV/sirHZoD7QAilU1Ifs7m/KJv4/84QVbE6nyAZGQNVOa1HGxaOzp9YqCG+GpLt1hNDC4RbH+KtanV7w==",
|
"integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/reactivity": "3.4.37",
|
"@vue/reactivity": "3.5.13",
|
||||||
"@vue/shared": "3.4.37"
|
"@vue/shared": "3.5.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/runtime-dom": {
|
"node_modules/@vue/runtime-dom": {
|
||||||
"version": "3.4.37",
|
"version": "3.5.13",
|
||||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.4.37.tgz",
|
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
|
||||||
"integrity": "sha512-Mg2EwgGZqtwKrqdL/FKMF2NEaOHuH+Ks9TQn3DHKyX//hQTYOun+7Tqp1eo0P4Ds+SjltZshOSRq6VsU0baaNg==",
|
"integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/reactivity": "3.4.37",
|
"@vue/reactivity": "3.5.13",
|
||||||
"@vue/runtime-core": "3.4.37",
|
"@vue/runtime-core": "3.5.13",
|
||||||
"@vue/shared": "3.4.37",
|
"@vue/shared": "3.5.13",
|
||||||
"csstype": "^3.1.3"
|
"csstype": "^3.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/server-renderer": {
|
"node_modules/@vue/server-renderer": {
|
||||||
"version": "3.4.37",
|
"version": "3.5.13",
|
||||||
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.4.37.tgz",
|
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
|
||||||
"integrity": "sha512-jZ5FAHDR2KBq2FsRUJW6GKDOAG9lUTX8aBEGq4Vf6B/35I9fPce66BornuwmqmKgfiSlecwuOb6oeoamYMohkg==",
|
"integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-ssr": "3.4.37",
|
"@vue/compiler-ssr": "3.5.13",
|
||||||
"@vue/shared": "3.4.37"
|
"@vue/shared": "3.5.13"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "3.4.37"
|
"vue": "3.5.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/shared": {
|
"node_modules/@vue/shared": {
|
||||||
"version": "3.4.37",
|
"version": "3.5.13",
|
||||||
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.4.37.tgz",
|
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz",
|
||||||
"integrity": "sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg=="
|
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@vue/tsconfig": {
|
"node_modules/@vue/tsconfig": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
@@ -1407,9 +1407,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/entities": {
|
"node_modules/entities": {
|
||||||
"version": "5.0.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmmirror.com/entities/-/entities-5.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
|
||||||
"integrity": "sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==",
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12"
|
"node": ">=0.12"
|
||||||
},
|
},
|
||||||
@@ -1725,9 +1725,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.11",
|
"version": "0.30.13",
|
||||||
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.11.tgz",
|
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.13.tgz",
|
||||||
"integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
|
"integrity": "sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||||
}
|
}
|
||||||
@@ -1936,9 +1936,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||||
},
|
},
|
||||||
"node_modules/pidtree": {
|
"node_modules/pidtree": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
@@ -1962,10 +1962,60 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pinia": {
|
||||||
|
"version": "2.2.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.2.6.tgz",
|
||||||
|
"integrity": "sha512-vIsR8JkDN5Ga2vAxqOE2cJj4VtsHnzpR1Fz30kClxlh0yCHfec6uoMeM3e/ddqmwFUejK3NlrcQa/shnpyT4hA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^6.6.3",
|
||||||
|
"vue-demi": "^0.14.10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.4.0",
|
||||||
|
"typescript": ">=4.4.4",
|
||||||
|
"vue": "^2.6.14 || ^3.5.11"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pinia/node_modules/vue-demi": {
|
||||||
|
"version": "0.14.10",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||||
|
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.41",
|
"version": "8.4.49",
|
||||||
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.41.tgz",
|
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz",
|
||||||
"integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
|
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -1982,8 +2032,8 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.1",
|
"picocolors": "^1.1.1",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
@@ -2139,9 +2189,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -2159,14 +2209,6 @@
|
|||||||
"node": ">=12.22"
|
"node": ">=12.22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/to-fast-properties": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||||
@@ -2257,15 +2299,15 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/vue": {
|
"node_modules/vue": {
|
||||||
"version": "3.4.37",
|
"version": "3.5.13",
|
||||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.4.37.tgz",
|
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz",
|
||||||
"integrity": "sha512-3vXvNfkKTBsSJ7JP+LyR7GBuwQuckbWvuwAid3xbqK9ppsKt/DUvfqgZ48fgOLEfpy1IacL5f8QhUVl77RaI7A==",
|
"integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.4.37",
|
"@vue/compiler-dom": "3.5.13",
|
||||||
"@vue/compiler-sfc": "3.4.37",
|
"@vue/compiler-sfc": "3.5.13",
|
||||||
"@vue/runtime-dom": "3.4.37",
|
"@vue/runtime-dom": "3.5.13",
|
||||||
"@vue/server-renderer": "3.4.37",
|
"@vue/server-renderer": "3.5.13",
|
||||||
"@vue/shared": "3.4.37"
|
"@vue/shared": "3.5.13"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "*"
|
"typescript": "*"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
|
"pinia": "^2.2.6",
|
||||||
"vue": "^3.4.29",
|
"vue": "^3.4.29",
|
||||||
"vue-router": "^4.4.0"
|
"vue-router": "^4.4.0"
|
||||||
},
|
},
|
||||||
|
|||||||
114
src/App.vue
114
src/App.vue
@@ -1,119 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app" class="app-container">
|
<div id="app" class="app-container">
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
<el-dialog title="告警提示" v-model="globalDialogVisible" width="50%" @close="handleDialogClose">
|
<GlobalDialog />
|
||||||
<el-row style="margin-bottom: 2vh;">
|
|
||||||
<el-col :span="17" style="text-align: center;">
|
|
||||||
<img :src="globalDialogContent.snapshotUrl" alt="告警图片" v-if="globalDialogContent.snapshotUrl"
|
|
||||||
style="max-width: 100%;" />
|
|
||||||
<!-- 可选的视频展示 -->
|
|
||||||
<!-- <video v-if="globalDialogContent.videoUrl" :src="globalDialogContent.videoUrl" controls style="max-width: 100%;"></video> -->
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="7" >
|
|
||||||
<el-row class="dialog-event-col">
|
|
||||||
<el-col :span="24"><strong >告警编号</strong>:{{ globalDialogContent.id }}</el-col>
|
|
||||||
<el-col :span="24"><strong >告警点位</strong>:{{ globalDialogContent.camera?.name }}</el-col>
|
|
||||||
<el-col :span="24"><strong >告警类型</strong>:{{ globalDialogContent.types }}</el-col>
|
|
||||||
<el-col :span="24"><strong >告警时间</strong>:{{ globalDialogContent.started_at }}</el-col>
|
|
||||||
</el-row>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import GlobalDialog from '@/components/GlobalDialog.vue';
|
||||||
import eventBus from '@/utils/eventBus';
|
|
||||||
import { BoxApi } from '@/utils/boxApi';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
interface GlobalDialogContent {
|
|
||||||
id: number | null;
|
|
||||||
camera_id: number | null;
|
|
||||||
camera: { name: string };
|
|
||||||
types: string | null;
|
|
||||||
started_at: string | null; // 支持 null 或字符串类型
|
|
||||||
snapshotUrl: string;
|
|
||||||
videoUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const apiInstance = new BoxApi();
|
|
||||||
|
|
||||||
const globalDialogVisible = ref(false); // 控制全局对话框的可见性
|
|
||||||
const globalDialogContent = ref<GlobalDialogContent>({
|
|
||||||
id: null,
|
|
||||||
camera_id: null,
|
|
||||||
camera: { name: '' },
|
|
||||||
types: null,
|
|
||||||
started_at: null,
|
|
||||||
snapshotUrl: '',
|
|
||||||
videoUrl: ''
|
|
||||||
});
|
|
||||||
const algorithmMap = ref(new Map()); // 算法类型映射表
|
|
||||||
|
|
||||||
// 加载算法映射表
|
|
||||||
const loadAlgorithms = async () => {
|
|
||||||
const token = localStorage.getItem('alertToken');
|
|
||||||
if (token) {
|
|
||||||
const algorithms = await apiInstance.getAlgorithms(token);
|
|
||||||
algorithmMap.value = new Map(
|
|
||||||
algorithms.map((algo: { code_name: string; name: string }) => [algo.code_name, algo.name])
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error('Token 未找到,请登录');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 显示告警详情对话框
|
|
||||||
const showDialog = async (data: any) => {
|
|
||||||
const token = localStorage.getItem('alertToken');
|
|
||||||
if (!token) {
|
|
||||||
console.error('Token 未找到,请登录');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log('showDialog>>>>>>>>>>>>>', data);
|
|
||||||
// 获取告警事件的详细信息
|
|
||||||
const eventDetails = await apiInstance.getEventById(data.id, token);
|
|
||||||
const snapshot = eventDetails.mediums.find((item: any) => item.name === 'snapshot');
|
|
||||||
const video = eventDetails.mediums.find((item: any) => item.name === 'video');
|
|
||||||
|
|
||||||
// 更新对话框内容
|
|
||||||
globalDialogContent.value = {
|
|
||||||
id: eventDetails.id,
|
|
||||||
camera_id: eventDetails.camera_id,
|
|
||||||
camera: eventDetails.camera,
|
|
||||||
types: algorithmMap.value.get(eventDetails.types),
|
|
||||||
started_at: formatDateTime(eventDetails.started_at) ,
|
|
||||||
snapshotUrl: snapshot?.file || '',
|
|
||||||
videoUrl: video?.file || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
globalDialogVisible.value = true; // 显示对话框
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDateTime = (isoString: string): string => dayjs(isoString).format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
|
|
||||||
// 关闭对话框并重置内容
|
|
||||||
const handleDialogClose = () => {
|
|
||||||
globalDialogVisible.value = false;
|
|
||||||
globalDialogContent.value = {
|
|
||||||
id: null,
|
|
||||||
camera_id: null,
|
|
||||||
camera: { name: '' },
|
|
||||||
types: null,
|
|
||||||
started_at: null,
|
|
||||||
snapshotUrl: '',
|
|
||||||
videoUrl: ''
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 生命周期钩子
|
|
||||||
onMounted(() => {
|
|
||||||
loadAlgorithms(); // 加载算法数据
|
|
||||||
eventBus.on('showDialog', showDialog); // 监听事件总线的 showDialog 事件
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -123,9 +18,4 @@ onMounted(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.dialog-event-col{
|
|
||||||
font-size: 14px;
|
|
||||||
gap: 30px;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
body{
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
|||||||
@@ -152,6 +152,21 @@ const filterCameras = () => {
|
|||||||
// console.log("filteredCameras>>:", filteredCameras.value);
|
// console.log("filteredCameras>>:", filteredCameras.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 名称+ID查询
|
||||||
|
// const filterCameras = () => {
|
||||||
|
// const keyword = searchKeyword.value.trim().toLowerCase();
|
||||||
|
// if (!keyword) {
|
||||||
|
// filteredCameras.value = [...cameras.value];
|
||||||
|
// } else {
|
||||||
|
// filteredCameras.value = cameras.value.filter(camera => {
|
||||||
|
// const idMatch = camera.id.toString().toLowerCase().includes(keyword);
|
||||||
|
// const nameMatch = camera.name.toLowerCase().includes(keyword);
|
||||||
|
// return idMatch || nameMatch;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
const selectCameraById = (cameraId) => {
|
const selectCameraById = (cameraId) => {
|
||||||
const camera = cameras.value.find(c => c.id === cameraId);
|
const camera = cameras.value.find(c => c.id === cameraId);
|
||||||
if (camera && !selectedCameras.value.some(c => c.id === camera.id)) {
|
if (camera && !selectedCameras.value.some(c => c.id === camera.id)) {
|
||||||
@@ -175,7 +190,7 @@ const handleSettings = async (cameraId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveResult = ({ success, message }) => {
|
const handleSaveResult = ({ success, message }) => {
|
||||||
console.log('收到子组件保存结果事件:', { success, message });
|
// console.log('收到子组件保存结果事件:', { success, message });
|
||||||
|
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message,
|
message,
|
||||||
@@ -517,16 +532,17 @@ onBeforeUnmount(() => {
|
|||||||
width: 68vw;
|
width: 68vw;
|
||||||
height: 55vh;
|
height: 55vh;
|
||||||
max-height: 58vh;
|
max-height: 58vh;
|
||||||
overflow-y: scroll;
|
/* overflow-y: scroll; */
|
||||||
scrollbar-width: none;
|
overflow-y: auto;
|
||||||
|
scrollbar-width:thin;
|
||||||
/* background-color: #ffffff; */
|
/* background-color: #ffffff; */
|
||||||
background-color: #F1F1F1;
|
background-color: #F1F1F1;
|
||||||
/* background-color: #c71515; */
|
/* background-color: #c71515; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.camera-grid::-webkit-scrollbar {
|
/* .camera-grid::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
} */
|
||||||
|
|
||||||
.grid-item {
|
.grid-item {
|
||||||
margin: 1vh;
|
margin: 1vh;
|
||||||
|
|||||||
220
src/components/GlobalDialog.vue
Normal file
220
src/components/GlobalDialog.vue
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog title="告警提示" v-model="globalDialogVisible" width="50%" @close="handleDialogClose">
|
||||||
|
<el-row style="margin-bottom: 2vh;">
|
||||||
|
<el-col :span="17" style="text-align: center;">
|
||||||
|
<img :src="globalDialogContent.snapshotUrl" alt="告警图片" v-if="globalDialogContent.snapshotUrl"
|
||||||
|
style="max-width: 100%;" />
|
||||||
|
<!-- 可选的视频展示 -->
|
||||||
|
<!-- <video v-if="globalDialogContent.videoUrl" :src="globalDialogContent.videoUrl" controls style="max-width: 100%;"></video> -->
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="7">
|
||||||
|
<el-row class="dialog-event-col">
|
||||||
|
<el-col :span="24"><strong>告警编号</strong>:{{ globalDialogContent.id }}</el-col>
|
||||||
|
<el-col :span="24"><strong>告警点位</strong>:{{ globalDialogContent.camera?.name }}</el-col>
|
||||||
|
<el-col :span="24"><strong>告警类型</strong>:{{ globalDialogContent.types }}</el-col>
|
||||||
|
<el-col :span="24"><strong>告警时间</strong>:{{ globalDialogContent.started_at }}</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, onUnmounted,watch } from 'vue';
|
||||||
|
import eventBus from '@/utils/eventBus';
|
||||||
|
import { BoxApi } from '@/utils/boxApi';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
interface GlobalDialogContent {
|
||||||
|
id: number | null;
|
||||||
|
camera_id: number | null;
|
||||||
|
camera: { name: string };
|
||||||
|
types: string | null;
|
||||||
|
started_at: string | null; // 支持 null 或字符串类型
|
||||||
|
snapshotUrl: string;
|
||||||
|
videoUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiInstance = new BoxApi();
|
||||||
|
|
||||||
|
const globalDialogVisible = ref(false); // 控制全局对话框的可见性
|
||||||
|
const globalDialogContent = ref<GlobalDialogContent>({
|
||||||
|
id: null,
|
||||||
|
camera_id: null,
|
||||||
|
camera: { name: '' },
|
||||||
|
types: null,
|
||||||
|
started_at: null,
|
||||||
|
snapshotUrl: '',
|
||||||
|
videoUrl: ''
|
||||||
|
});
|
||||||
|
const algorithmMap = ref(new Map()); // 算法类型映射表
|
||||||
|
|
||||||
|
|
||||||
|
const requestQueue: any[] = []; // 消息队列
|
||||||
|
let isProcessing = false; // 标志是否正在处理队列
|
||||||
|
|
||||||
|
// 加载算法映射表
|
||||||
|
const loadAlgorithms = async () => {
|
||||||
|
const token = localStorage.getItem('alertToken');
|
||||||
|
if (token) {
|
||||||
|
const algorithms = await apiInstance.getAlgorithms(token);
|
||||||
|
algorithmMap.value = new Map(
|
||||||
|
algorithms.map((algo: { code_name: string; name: string }) => [algo.code_name, algo.name])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error('Token 未找到,请登录');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchWithRetry = async (fetchFn: () => Promise<any>, retries = 5, delay = 2000): Promise<any> => {
|
||||||
|
for (let i = 0; i < retries; i++) {
|
||||||
|
try {
|
||||||
|
return await fetchFn();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Retry ${i + 1} failed:`, error);
|
||||||
|
if (i < retries - 1) await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('All retries failed');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// const showDialog = async (data: any) => {
|
||||||
|
// const token = localStorage.getItem('alertToken');
|
||||||
|
// if (!token) {
|
||||||
|
// console.error('Token 未找到,请登录');
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// console.log('弹窗接收>>>>>>>>>>>>>', data);
|
||||||
|
|
||||||
|
// const eventDetails = await apiInstance.getEventById(data.id, token);
|
||||||
|
// console.log('showDialog>>>>>>>>>>>>>', eventDetails);
|
||||||
|
// const snapshot = eventDetails.mediums.find((item: any) => item.name === 'snapshot');
|
||||||
|
// const video = eventDetails.mediums.find((item: any) => item.name === 'video');
|
||||||
|
|
||||||
|
// globalDialogContent.value = {
|
||||||
|
// id: eventDetails.id,
|
||||||
|
// camera_id: eventDetails.camera_id,
|
||||||
|
// camera: eventDetails.camera,
|
||||||
|
// types: algorithmMap.value.get(eventDetails.types),
|
||||||
|
// started_at: formatDateTime(eventDetails.started_at),
|
||||||
|
// snapshotUrl: snapshot?.file || '',
|
||||||
|
// videoUrl: video?.file || ''
|
||||||
|
// };
|
||||||
|
// globalDialogVisible.value = true;
|
||||||
|
// };
|
||||||
|
|
||||||
|
const processQueue = async () => {
|
||||||
|
if (isProcessing || requestQueue.length === 0) return; // 正在处理或队列为空时不执行
|
||||||
|
isProcessing = true; // 标记正在处理
|
||||||
|
const data = requestQueue.shift(); // 从队列中取出数据
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('alertToken');
|
||||||
|
if (!token) throw new Error('Token 未找到,请登录');
|
||||||
|
|
||||||
|
// 使用延时重试获取数据
|
||||||
|
const eventDetails = await fetchWithRetry(() => apiInstance.getEventById(data.id, token));
|
||||||
|
// console.log('processQueue>>>>>>>>>>>>>', eventDetails);
|
||||||
|
if (!eventDetails || !eventDetails.mediums) {
|
||||||
|
console.error('Event details or mediums not found:', eventDetails);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const snapshot = eventDetails.mediums.find((item: any) => item.name === 'snapshot');
|
||||||
|
const video = eventDetails.mediums.find((item: any) => item.name === 'video');
|
||||||
|
|
||||||
|
globalDialogContent.value = {
|
||||||
|
id: eventDetails.id,
|
||||||
|
camera_id: eventDetails.camera_id,
|
||||||
|
camera: eventDetails.camera,
|
||||||
|
types: algorithmMap.value.get(eventDetails.types) || '未知类型',
|
||||||
|
started_at: formatDateTime(eventDetails.started_at),
|
||||||
|
snapshotUrl: snapshot?.file || '',
|
||||||
|
videoUrl: video?.file || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
globalDialogVisible.value = true; // 显示对话框
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing data in queue:', error);
|
||||||
|
} finally {
|
||||||
|
isProcessing = false;
|
||||||
|
processQueue(); // 处理队列中的下一个请求
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const enqueueRequest = (data: any) => {
|
||||||
|
requestQueue.push(data);
|
||||||
|
processQueue();
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDateTime = (isoString: string): string => dayjs(isoString).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
||||||
|
// 关闭对话框并重置内容
|
||||||
|
const handleDialogClose = () => {
|
||||||
|
globalDialogVisible.value = false;
|
||||||
|
globalDialogContent.value = {
|
||||||
|
id: null,
|
||||||
|
camera_id: null,
|
||||||
|
camera: { name: '' },
|
||||||
|
types: null,
|
||||||
|
started_at: null,
|
||||||
|
snapshotUrl: '',
|
||||||
|
videoUrl: ''
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(globalDialogContent, (newValue, oldValue) => {
|
||||||
|
|
||||||
|
if (newValue !== oldValue) {
|
||||||
|
|
||||||
|
const dialogElement = document.querySelector('.dialog-event-col');
|
||||||
|
if (dialogElement) {
|
||||||
|
dialogElement.classList.add('border-blink');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
dialogElement.classList.remove('border-blink');
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadAlgorithms();
|
||||||
|
// eventBus.on('showDialog', showDialog);
|
||||||
|
eventBus.on('showDialog', enqueueRequest);
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
// eventBus.off('showDialog', showDialog);
|
||||||
|
eventBus.off('showDialog', enqueueRequest);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dialog-event-col {
|
||||||
|
font-size: 14px;
|
||||||
|
gap: 30px;
|
||||||
|
padding: 20px;
|
||||||
|
/* transition: border 0.3s ease-in-out; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-blink {
|
||||||
|
animation: borderBlink 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes borderBlink {
|
||||||
|
0% {
|
||||||
|
border: 2px solid red;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
border: 2px solid red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -26,34 +26,46 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, nextTick } from 'vue';
|
import { ref, onMounted, nextTick, onBeforeUnmount } from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import { BoxApi } from '@/utils/boxApi.ts';
|
import { BoxApi } from '@/utils/boxApi.ts';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
import { useGlobalTimerStore } from '@/stores/globalTimerStore';
|
||||||
|
|
||||||
|
|
||||||
const activeName = ref('first');
|
|
||||||
const hourlyCounts = ref(Array(24).fill(0));
|
|
||||||
const dailyCounts = ref(Array(7).fill(0));
|
|
||||||
const monthlyCounts = ref(Array(30).fill(0));
|
|
||||||
|
|
||||||
const todayLineChartDom = ref(null);
|
const activeName = ref('first');
|
||||||
const weekLineChartDom = ref(null);
|
const hourlyCounts = ref(Array(24).fill(0));
|
||||||
const monthLineChartDom = ref(null);
|
const dailyCounts = ref(Array(7).fill(0));
|
||||||
|
const monthlyCounts = ref(Array(30).fill(0));
|
||||||
|
|
||||||
const chartInstance = ref(null);
|
const todayLineChartDom = ref(null);
|
||||||
|
const weekLineChartDom = ref(null);
|
||||||
|
const monthLineChartDom = ref(null);
|
||||||
|
|
||||||
const weekDates = ref([]);
|
const chartInstance = ref(null);
|
||||||
const monthDates = ref([]);
|
|
||||||
|
|
||||||
const apiInstance = new BoxApi();
|
const weekDates = ref([]);
|
||||||
|
const monthDates = ref([]);
|
||||||
|
|
||||||
// 设置时间范围获取计数
|
const apiInstance = new BoxApi();
|
||||||
const getEventCount = async (timeBefore, timeAfter) => {
|
const globalTimerStore = useGlobalTimerStore();
|
||||||
|
|
||||||
|
const updateChartData = async () => {
|
||||||
|
const range =
|
||||||
|
activeName.value === 'first' ? 'day' :
|
||||||
|
activeName.value === 'second' ? 'week' : 'month';
|
||||||
|
|
||||||
|
await updateCounts(range);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 设置时间范围获取计数
|
||||||
|
const getEventCount = async (timeBefore, timeAfter) => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('alertToken');
|
const token = localStorage.getItem('alertToken');
|
||||||
const response = await apiInstance.getEventsByParams(token, 1, 1, timeBefore, timeAfter);
|
const response = await apiInstance.getEventsByParams(token, 1, 1, timeBefore, timeAfter);
|
||||||
@@ -62,10 +74,10 @@
|
|||||||
console.error("Error fetching event count:", error);
|
console.error("Error fetching event count:", error);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生成图表的配置选项
|
// 生成图表的配置选项
|
||||||
const createChartOption = (title, xAxisData, seriesData) => ({
|
const createChartOption = (title, xAxisData, seriesData) => ({
|
||||||
// title: {
|
// title: {
|
||||||
// text: title,
|
// text: title,
|
||||||
// left: 'center',
|
// left: 'center',
|
||||||
@@ -98,7 +110,7 @@
|
|||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: '10%',
|
top: '10%',
|
||||||
left: '5%',
|
left: '7%',
|
||||||
right: '5%',
|
right: '5%',
|
||||||
bottom: '20%',
|
bottom: '20%',
|
||||||
},
|
},
|
||||||
@@ -124,10 +136,10 @@
|
|||||||
lineStyle: { color: 'rgb(60,178,239)', width: 2 },
|
lineStyle: { color: 'rgb(60,178,239)', width: 2 },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新计数数据
|
// 更新计数数据
|
||||||
const updateCounts = async (range) => {
|
const updateCounts = async (range) => {
|
||||||
let timeAfter, timeBefore = dayjs().format();
|
let timeAfter, timeBefore = dayjs().format();
|
||||||
|
|
||||||
if (range === 'day') {
|
if (range === 'day') {
|
||||||
@@ -158,103 +170,118 @@
|
|||||||
}
|
}
|
||||||
delayedInitChart(monthLineChartDom, createChartOption('近30天告警趋势', monthDates.value, monthlyCounts.value));
|
delayedInitChart(monthLineChartDom, createChartOption('近30天告警趋势', monthDates.value, monthlyCounts.value));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理选项卡切换
|
// 处理选项卡切换
|
||||||
const handleClick = async (tab) => {
|
const handleClick = async (tab) => {
|
||||||
const range = tab.props.name === 'first' ? 'day' : tab.props.name === 'second' ? 'week' : 'month';
|
const range = tab.props.name === 'first' ? 'day' : tab.props.name === 'second' ? 'week' : 'month';
|
||||||
await updateCounts(range);
|
await updateCounts(range);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化日期
|
// 初始化日期
|
||||||
const calculateWeekDates = () => {
|
const calculateWeekDates = () => {
|
||||||
weekDates.value = Array.from({ length: 7 }, (_, i) => dayjs().subtract(i, 'day').format('YYYY-MM-DD')).reverse();
|
weekDates.value = Array.from({ length: 7 }, (_, i) => dayjs().subtract(i, 'day').format('YYYY-MM-DD')).reverse();
|
||||||
};
|
};
|
||||||
const calculateMonthDates = () => {
|
const calculateMonthDates = () => {
|
||||||
monthDates.value = Array.from({ length: 30 }, (_, i) => dayjs().subtract(i, 'day').format('YYYY-MM-DD')).reverse();
|
monthDates.value = Array.from({ length: 30 }, (_, i) => dayjs().subtract(i, 'day').format('YYYY-MM-DD')).reverse();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 图表延迟初始化
|
// 图表延迟初始化
|
||||||
const delayedInitChart = debounce((domRef, option) => {
|
const delayedInitChart = debounce((domRef, option) => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (domRef.value) {
|
if (domRef.value) {
|
||||||
chartInstance.value = echarts.init(domRef.value);
|
chartInstance.value = echarts.init(domRef.value);
|
||||||
chartInstance.value.setOption(option);
|
chartInstance.value.setOption(option);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
// 组件挂载时调用
|
// 组件挂载时调用
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
calculateWeekDates();
|
calculateWeekDates();
|
||||||
calculateMonthDates();
|
calculateMonthDates();
|
||||||
await handleClick({ props: { name: 'first' } });
|
await handleClick({ props: { name: 'first' } });
|
||||||
|
// 注册定时器回调
|
||||||
|
globalTimerStore.registerCallback(updateChartData);
|
||||||
|
|
||||||
|
// 启动全局定时器
|
||||||
|
globalTimerStore.startTimer();
|
||||||
window.addEventListener('resize', debounce(() => {
|
window.addEventListener('resize', debounce(() => {
|
||||||
if (chartInstance.value) chartInstance.value.resize();
|
if (chartInstance.value) chartInstance.value.resize();
|
||||||
}, 300));
|
}, 300));
|
||||||
});
|
|
||||||
</script>
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
globalTimerStore.unregisterCallback(updateChartData);
|
||||||
|
globalTimerStore.stopTimer();
|
||||||
|
window.removeEventListener('resize', debounce(() => {
|
||||||
|
if (chartInstance.value) chartInstance.value.resize();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.alert-card {
|
.alert-card {
|
||||||
background-color: #001529;
|
background-color: #001529;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
/* padding: 10px; */
|
/* padding: 10px; */
|
||||||
/* margin: 10px; */
|
/* margin: 10px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-container {
|
.alert-container {
|
||||||
background-color: #001529;
|
background-color: #001529;
|
||||||
margin: 0vh 1vw 1vh 1vw;
|
margin: 0vh 1vw 1vh 1vw;
|
||||||
/* margin-top: 0; */
|
/* margin-top: 0; */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .alert-header {
|
/* .alert-header {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-bottom: 1px solid #3a4b5c;
|
border-bottom: 1px solid #3a4b5c;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
.chart-container {
|
|
||||||
|
.chart-container {
|
||||||
/* min-height: 350px; */
|
/* min-height: 350px; */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 15vh;
|
height: 15vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-tabs__item {
|
::v-deep .el-tabs__item {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-left: 1vh;
|
margin-left: 1vh;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-tabs__item.is-active {
|
::v-deep .el-tabs__item.is-active {
|
||||||
color: #2ea0ec;
|
color: #2ea0ec;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ::v-deep .el-tabs___nav-wrap.is-top::after{
|
/* ::v-deep .el-tabs___nav-wrap.is-top::after{
|
||||||
border: none;
|
border: none;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
/* ::v-deep .el-tabs__active-bar.is-top{
|
/* ::v-deep .el-tabs__active-bar.is-top{
|
||||||
padding: 0 204px;
|
padding: 0 204px;
|
||||||
box-sizing: border-box !important;
|
box-sizing: border-box !important;
|
||||||
background-clip: content-box !important;
|
background-clip: content-box !important;
|
||||||
} */
|
} */
|
||||||
|
.el-tabs__active-bar {
|
||||||
|
|
||||||
.el-tabs__active-bar {
|
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
::v-deep .el-tabs__nav-wrap::after {
|
::v-deep .el-tabs__nav-wrap::after {
|
||||||
/* width: 15vw; */
|
/* width: 15vw; */
|
||||||
position: static !important;
|
position: static !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -19,16 +19,14 @@
|
|||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<el-table :data="tableData" @row-click="handleRowClick" class="table-part">
|
<el-table :data="tableData" @row-click="handleRowClick" class="table-part">
|
||||||
<el-table-column v-show="false" prop="id" label="告警编号" v-if="false"></el-table-column>
|
<el-table-column v-show="false" prop="id" label="告警编号" v-if="false"></el-table-column>
|
||||||
<el-table-column label="告警类型" :width="adjustedWidths[0]" align="center"
|
<el-table-column label="告警类型" :width="adjustedWidths[0]" align="center" :show-overflow-tooltip="true">
|
||||||
:show-overflow-tooltip="true">
|
|
||||||
<template v-slot="scope">
|
<template v-slot="scope">
|
||||||
{{ typeMapping[scope.row.types] }}
|
{{ typeMapping[scope.row.types] }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="camera.name" label="告警位置" :width="adjustedWidths[1]" align="center"
|
<el-table-column prop="camera.name" label="告警位置" :width="adjustedWidths[1]" align="center"
|
||||||
:show-overflow-tooltip="true"></el-table-column>
|
:show-overflow-tooltip="true"></el-table-column>
|
||||||
<el-table-column label="告警时间" :width="adjustedWidths[2]" align="center"
|
<el-table-column label="告警时间" :width="adjustedWidths[2]" align="center" :show-overflow-tooltip="true">
|
||||||
:show-overflow-tooltip="true">
|
|
||||||
<template v-slot="scope">
|
<template v-slot="scope">
|
||||||
{{ formatDateTime(scope.row.ended_at) }}
|
{{ formatDateTime(scope.row.ended_at) }}
|
||||||
</template>
|
</template>
|
||||||
@@ -62,7 +60,7 @@
|
|||||||
<el-col :span="12" class="dialog-left">
|
<el-col :span="12" class="dialog-left">
|
||||||
<el-row gutter class="dialog-image-container">
|
<el-row gutter class="dialog-image-container">
|
||||||
<template v-if="hasSnapshot">
|
<template v-if="hasSnapshot">
|
||||||
<el-image :src="snapshotFile"></el-image>
|
<el-image :src="snapshotFile" @click="handleImageClick(snapshotFile)" style="cursor: pointer;"></el-image>
|
||||||
</template>
|
</template>
|
||||||
<!-- <template v-if="hasVideo">
|
<!-- <template v-if="hasVideo">
|
||||||
<video :src="videoFile" controls></video>
|
<video :src="videoFile" controls></video>
|
||||||
@@ -109,46 +107,58 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog v-model="previewVisible" width="60%" custom-class="image-preview-dialog" :close-on-click-modal="true">
|
||||||
|
<img :src="previewImage" alt="预览图片" style="width: 100%; height: auto; display: block; margin: auto;" />
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
|
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
|
||||||
import { BoxApi } from '@/utils/boxApi.ts';
|
import { BoxApi } from '@/utils/boxApi.ts';
|
||||||
|
import { useGlobalTimerStore } from '@/stores/globalTimerStore';
|
||||||
|
|
||||||
const boxApi = new BoxApi();
|
const boxApi = new BoxApi();
|
||||||
const tableData = ref([]);
|
const tableData = ref([]);
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const remark = ref("");
|
const remark = ref("");
|
||||||
const selectedRow = ref({});
|
const selectedRow = ref({});
|
||||||
const typeMapping = reactive({});
|
const typeMapping = reactive({});
|
||||||
const currentPage = ref(1);
|
const currentPage = ref(1);
|
||||||
const pageSize = ref(20);
|
const pageSize = ref(20);
|
||||||
const totalItems = ref(0);
|
const totalItems = ref(0);
|
||||||
const displayTotalItems = ref(0);
|
const displayTotalItems = ref(0);
|
||||||
const token = ref(null);
|
const token = ref(null);
|
||||||
const apiInstance = new BoxApi();
|
const apiInstance = new BoxApi();
|
||||||
const statusMapping = {
|
const statusMapping = {
|
||||||
'pending': '待处理',
|
'pending': '待处理',
|
||||||
'assigned': '处理中',
|
'assigned': '处理中',
|
||||||
'closed': '已处理'
|
'closed': '已处理'
|
||||||
};
|
};
|
||||||
const duration = ref('');
|
const duration = ref('');
|
||||||
|
|
||||||
const hasSnapshot = ref(false);
|
const hasSnapshot = ref(false);
|
||||||
const hasVideo = ref(false);
|
const hasVideo = ref(false);
|
||||||
const snapshotFile = ref("");
|
const snapshotFile = ref("");
|
||||||
const videoFile = ref("");
|
const videoFile = ref("");
|
||||||
const originalWidths = [97, 150, 160]; // 默认宽度
|
const originalWidths = [97, 150, 160]; // 默认宽度
|
||||||
const adjustedWidths = ref([...originalWidths]);
|
const adjustedWidths = ref([...originalWidths]);
|
||||||
const baseWidth = 2150;
|
const baseWidth = 2150;
|
||||||
|
const previewVisible = ref(false); // 控制预览弹窗显示
|
||||||
|
const previewImage = ref(''); // 预览的图片路径
|
||||||
|
|
||||||
const formatDateTimeToISO = (datetime) => {
|
const handleImageClick = (imagePath) => {
|
||||||
|
previewImage.value = imagePath; // 设置预览图片路径
|
||||||
|
previewVisible.value = true; // 显示预览弹窗
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDateTimeToISO = (datetime) => {
|
||||||
return new Date(datetime).toISOString().replace('.000', '');
|
return new Date(datetime).toISOString().replace('.000', '');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const adjustColumnWidths = () => {
|
const adjustColumnWidths = () => {
|
||||||
const currentWidth = window.innerWidth;
|
const currentWidth = window.innerWidth;
|
||||||
// console.log(">>>>>>>>>>", currentWidth);
|
// console.log(">>>>>>>>>>", currentWidth);
|
||||||
const scale = currentWidth / baseWidth;
|
const scale = currentWidth / baseWidth;
|
||||||
@@ -163,17 +173,17 @@
|
|||||||
// nextTick(() => {
|
// nextTick(() => {
|
||||||
|
|
||||||
// });
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
// const tableConfig = ref({
|
// const tableConfig = ref({
|
||||||
// header: ['告警类型', '告警位置', '告警时间'],
|
// header: ['告警类型', '告警位置', '告警时间'],
|
||||||
// data: [],
|
// data: [],
|
||||||
// rowNum: 5,
|
// rowNum: 5,
|
||||||
// columnWidth: [100, 160, 190],
|
// columnWidth: [100, 160, 190],
|
||||||
// carousel: 'single',
|
// carousel: 'single',
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const fetchTypeMapping = async (token) => {
|
const fetchTypeMapping = async (token) => {
|
||||||
try {
|
try {
|
||||||
const algorithms = await boxApi.getAlgorithms(token);
|
const algorithms = await boxApi.getAlgorithms(token);
|
||||||
// console.log("Algorithms:", algorithms);
|
// console.log("Algorithms:", algorithms);
|
||||||
@@ -183,9 +193,9 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching algorithms:", error);
|
console.error("Error fetching algorithms:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchEvents = async () => {
|
const fetchEvents = async () => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('alertToken');
|
const token = localStorage.getItem('alertToken');
|
||||||
const limit = 1000;
|
const limit = 1000;
|
||||||
@@ -198,7 +208,7 @@
|
|||||||
const timeAfter = formatDateTimeToISO(startOfToday);
|
const timeAfter = formatDateTimeToISO(startOfToday);
|
||||||
// console.log("Start of today:", timeAfter);
|
// console.log("Start of today:", timeAfter);
|
||||||
// const firstResponse = await apiInstance.getEventsByParams(token, limit, currentPage);
|
// const firstResponse = await apiInstance.getEventsByParams(token, limit, currentPage);
|
||||||
const firstResponse = await apiInstance.getEventsByParams(token, limit, currentPage,timeBefore, timeAfter);
|
const firstResponse = await apiInstance.getEventsByParams(token, limit, currentPage, timeBefore, timeAfter);
|
||||||
// console.log("Total items:", firstResponse);
|
// console.log("Total items:", firstResponse);
|
||||||
|
|
||||||
const total = firstResponse.count;
|
const total = firstResponse.count;
|
||||||
@@ -221,9 +231,9 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching events data:", error);
|
console.error("Error fetching events data:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDateParams = () => {
|
const getDateParams = () => {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
||||||
const endOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
|
const endOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
|
||||||
@@ -232,8 +242,8 @@
|
|||||||
startOfToday,
|
startOfToday,
|
||||||
endOfToday,
|
endOfToday,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const formatDateTime = (datetime) => {
|
const formatDateTime = (datetime) => {
|
||||||
const date = new Date(datetime);
|
const date = new Date(datetime);
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||||
@@ -242,18 +252,18 @@
|
|||||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||||
const seconds = date.getSeconds().toString().padStart(2, '0');
|
const seconds = date.getSeconds().toString().padStart(2, '0');
|
||||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateDuration = (started_at, ended_at) => {
|
const calculateDuration = (started_at, ended_at) => {
|
||||||
const start = new Date(started_at);
|
const start = new Date(started_at);
|
||||||
const end = new Date(ended_at);
|
const end = new Date(ended_at);
|
||||||
const durationMs = end - start;
|
const durationMs = end - start;
|
||||||
const minutes = Math.floor(durationMs / 60000);
|
const minutes = Math.floor(durationMs / 60000);
|
||||||
const seconds = ((durationMs % 60000) / 1000).toFixed(0);
|
const seconds = ((durationMs % 60000) / 1000).toFixed(0);
|
||||||
return `${minutes}分${seconds < 10 ? '0' : ''}${seconds}秒`;
|
return `${minutes}分${seconds < 10 ? '0' : ''}${seconds}秒`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRowClick = (row) => {
|
const handleRowClick = (row) => {
|
||||||
selectedRow.value = row;
|
selectedRow.value = row;
|
||||||
duration.value = calculateDuration(row.started_at, row.ended_at);
|
duration.value = calculateDuration(row.started_at, row.ended_at);
|
||||||
row.formatted_started_at = formatDateTime(row.started_at);
|
row.formatted_started_at = formatDateTime(row.started_at);
|
||||||
@@ -275,35 +285,41 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handlePageChange = (page) => {
|
const handlePageChange = (page) => {
|
||||||
currentPage.value = page;
|
currentPage.value = page;
|
||||||
fetchEvents();
|
fetchEvents();
|
||||||
};
|
};
|
||||||
|
|
||||||
// const calculateColumnWidths = () => {
|
// const calculateColumnWidths = () => {
|
||||||
// const containerWidth = document.querySelector('.table-container').clientWidth;
|
// const containerWidth = document.querySelector('.table-container').clientWidth;
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
||||||
token.value = localStorage.getItem('alertToken');
|
token.value = localStorage.getItem('alertToken');
|
||||||
await fetchTypeMapping(token.value);
|
await fetchTypeMapping(token.value);
|
||||||
await fetchEvents();
|
await fetchEvents();
|
||||||
await adjustColumnWidths();
|
await adjustColumnWidths();
|
||||||
window.addEventListener('resize', adjustColumnWidths);
|
window.addEventListener('resize', adjustColumnWidths);
|
||||||
});
|
const globalTimerStore = useGlobalTimerStore();
|
||||||
|
globalTimerStore.registerCallback(fetchEvents);
|
||||||
|
globalTimerStore.startTimer();
|
||||||
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('resize', adjustColumnWidths);
|
window.removeEventListener('resize', adjustColumnWidths);
|
||||||
});
|
const globalTimerStore = useGlobalTimerStore();
|
||||||
</script>
|
globalTimerStore.unregisterCallback(fetchEvents);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.alert-container {
|
.alert-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -312,67 +328,67 @@
|
|||||||
margin: 0 0 0 1vw;
|
margin: 0 0 0 1vw;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-continer {
|
.table-continer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-row:hover {
|
.table-row:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.title-count {
|
.title-count {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
/* background-color: transparent; */
|
/* background-color: transparent; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* .total-text{
|
/* .total-text{
|
||||||
padding-right: 1vw;
|
padding-right: 1vw;
|
||||||
} */
|
} */
|
||||||
/* title-row内容水平上下居中 */
|
/* title-row内容水平上下居中 */
|
||||||
.title-row {
|
.title-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* total-row左右等分,分别靠左和靠右 */
|
/* total-row左右等分,分别靠左和靠右 */
|
||||||
.total-row {
|
.total-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
/* justify-content: space-between; */
|
/* justify-content: space-between; */
|
||||||
justify-content: right;
|
justify-content: right;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding-right: 0vw;
|
padding-right: 0vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-row {
|
.dialog-row {
|
||||||
gap: 30px;
|
gap: 30px;
|
||||||
display: flex;
|
display: flex;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-image-container {
|
.dialog-image-container {
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dialog .el-image,
|
.el-dialog .el-image,
|
||||||
.el-dialog video {
|
.el-dialog video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 基础表格样式 */
|
/* 基础表格样式 */
|
||||||
::v-deep .el-table {
|
::v-deep .el-table {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
/* font-weight: bold; */
|
/* font-weight: bold; */
|
||||||
@@ -380,11 +396,11 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 14vh;
|
height: 14vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 表头和单元格基本样式 */
|
/* 表头和单元格基本样式 */
|
||||||
|
|
||||||
::v-deep .el-table td {
|
::v-deep .el-table td {
|
||||||
border: none;
|
border: none;
|
||||||
background-color: #001529;
|
background-color: #001529;
|
||||||
transition: border 0.3s ease, background-color 0.3s ease;
|
transition: border 0.3s ease, background-color 0.3s ease;
|
||||||
@@ -394,40 +410,42 @@
|
|||||||
/* white-space: nowrap; */
|
/* white-space: nowrap; */
|
||||||
/* overflow: hidden; */
|
/* overflow: hidden; */
|
||||||
/* text-overflow: ellipsis; */
|
/* text-overflow: ellipsis; */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 去掉中间数据的分割线 */
|
/* 去掉中间数据的分割线 */
|
||||||
/* ::v-deep .el-table .el-table__row>td{
|
/* ::v-deep .el-table .el-table__row>td{
|
||||||
border: none;
|
border: none;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
.table-container >>> .el-table__row>td{
|
.table-container>>>.el-table__row>td {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.table-container >>> .el-table th.is-leaf{
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
::v-deep .el-table__inner-wrapper::before{
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .el-table thead th {
|
.table-container>>>.el-table th.is-leaf {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-table__inner-wrapper::before {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-table thead th {
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background-color: #001529;
|
background-color: #001529;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-table .el-table__cell:hover {
|
::v-deep .el-table .el-table__cell:hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: rgb(238, 150, 49);
|
color: rgb(238, 150, 49);
|
||||||
/* font-size: 26px; */
|
/* font-size: 26px; */
|
||||||
/* 保持文本为白色 */
|
/* 保持文本为白色 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tooltip__popper {
|
.el-tooltip__popper {
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
max-width:50%
|
max-width: 50%
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -13,7 +13,9 @@ import { ref, onMounted, onBeforeUnmount } from 'vue';
|
|||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import { BoxApi } from '@/utils/boxApi.ts';
|
import { BoxApi } from '@/utils/boxApi.ts';
|
||||||
import { DCaret } from '@element-plus/icons-vue';
|
import { DCaret } from '@element-plus/icons-vue';
|
||||||
|
import { useGlobalTimerStore } from '@/stores/globalTimerStore';
|
||||||
|
|
||||||
|
const globalTimerStore = useGlobalTimerStore();
|
||||||
const chartContainer = ref(null);
|
const chartContainer = ref(null);
|
||||||
let myChart = null;
|
let myChart = null;
|
||||||
const cameras = ref([]);
|
const cameras = ref([]);
|
||||||
@@ -98,7 +100,7 @@ const option = ref({
|
|||||||
data: [], // 动态填充告警数量
|
data: [], // 动态填充告警数量
|
||||||
type: "bar",
|
type: "bar",
|
||||||
barWidth: 10,
|
barWidth: 10,
|
||||||
padding:[0],
|
padding: [0],
|
||||||
showBackground: true,
|
showBackground: true,
|
||||||
barBorderRadius: [30, 0, 0, 30],
|
barBorderRadius: [30, 0, 0, 30],
|
||||||
backgroundStyle: { color: 'rgba(9, 68, 131, .2)' },
|
backgroundStyle: { color: 'rgba(9, 68, 131, .2)' },
|
||||||
@@ -236,18 +238,14 @@ const fetchCameras = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 清理轮播和事件监听
|
const resizeChart = () => {
|
||||||
onBeforeUnmount(() => {
|
if (myChart && !myChart.isDisposed()) {
|
||||||
if (dataZoomMoveTimer) {
|
myChart.resize();
|
||||||
clearInterval(dataZoomMoveTimer);
|
} else {
|
||||||
dataZoomMoveTimer = null; // 确保计时器被清空
|
console.warn('Attempted to resize a disposed ECharts instance.');
|
||||||
}
|
}
|
||||||
if (myChart) {
|
};
|
||||||
window.removeEventListener('resize', resizeChart); // 确保事件监听器被移除
|
|
||||||
myChart.dispose();
|
|
||||||
myChart = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -259,16 +257,26 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// 监听窗口变化事件,调整图表大小
|
// 监听窗口变化事件,调整图表大小
|
||||||
window.addEventListener('resize', resizeChart);
|
window.addEventListener('resize', resizeChart);
|
||||||
|
|
||||||
|
globalTimerStore.registerCallback(fetchCameras);
|
||||||
|
globalTimerStore.startTimer();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 清理轮播和事件监听
|
||||||
const resizeChart = () => {
|
onBeforeUnmount(() => {
|
||||||
if (myChart && !myChart.isDisposed()) {
|
if (dataZoomMoveTimer) {
|
||||||
myChart.resize();
|
clearInterval(dataZoomMoveTimer);
|
||||||
} else {
|
dataZoomMoveTimer = null; // 确保计时器被清空
|
||||||
console.warn('Attempted to resize a disposed ECharts instance.');
|
|
||||||
}
|
}
|
||||||
};
|
if (myChart) {
|
||||||
|
window.removeEventListener('resize', resizeChart); // 确保事件监听器被移除
|
||||||
|
myChart.dispose();
|
||||||
|
myChart = null;
|
||||||
|
}
|
||||||
|
globalTimerStore.unregisterCallback(fetchCameras);
|
||||||
|
globalTimerStore.stopTimer();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// const resizeChart = () => {
|
// const resizeChart = () => {
|
||||||
@@ -283,23 +291,25 @@ const resizeChart = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.leftMiddle-box{
|
.leftMiddle-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 41vh;
|
height: 41vh;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
}
|
}
|
||||||
.show-bt{
|
|
||||||
|
.show-bt {
|
||||||
background-color: #001529;
|
background-color: #001529;
|
||||||
border: #001529;
|
border: #001529;
|
||||||
width: 6vw;
|
width: 79px;
|
||||||
height: 4vh;
|
height: 30px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
font-size: 15px;
|
font-size: 0.9rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-container {
|
.chart-container {
|
||||||
/* margin: 0 1vw 0 1vw; */
|
/* margin: 0 1vw 0 1vw; */
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<!-- 右侧分为两行 -->
|
<!-- 右侧分为两行 -->
|
||||||
<el-col :sm="24" :md="16">
|
<el-col :sm="24" :md="16">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :sm="24" :md="24">
|
<el-col :sm="24" :md="24" class="inner-title-text">
|
||||||
通道总数:
|
通道总数:
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<!-- 右侧分为两行 -->
|
<!-- 右侧分为两行 -->
|
||||||
<el-col :sm="24" :md="16">
|
<el-col :sm="24" :md="16">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :sm="24" :md="24">
|
<el-col :sm="24" :md="24" class="inner-title-text">
|
||||||
在线:
|
在线:
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
<!-- 右侧分为两行 -->
|
<!-- 右侧分为两行 -->
|
||||||
<el-col :sm="24" :md="16">
|
<el-col :sm="24" :md="16">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :sm="24" :md="24">
|
<el-col :sm="24" :md="24" class="inner-title-text">
|
||||||
离线:
|
离线:
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
<!-- 右侧分为两行 -->
|
<!-- 右侧分为两行 -->
|
||||||
<el-col :sm="24" :md="16">
|
<el-col :sm="24" :md="16">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :sm="24" :md="24">
|
<el-col :sm="24" :md="24" class="inner-title-text">
|
||||||
事件总数:
|
事件总数:
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
<!-- 右侧分为两行 -->
|
<!-- 右侧分为两行 -->
|
||||||
<el-col :sm="24" :md="16">
|
<el-col :sm="24" :md="16">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :sm="24" :md="24">
|
<el-col :sm="24" :md="24" class="inner-title-text">
|
||||||
已处理:
|
已处理:
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
<!-- 右侧分为两行 -->
|
<!-- 右侧分为两行 -->
|
||||||
<el-col :sm="24" :md="16">
|
<el-col :sm="24" :md="16">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :sm="24" :md="24">
|
<el-col :sm="24" :md="24" class="inner-title-text">
|
||||||
未处理:
|
未处理:
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -163,6 +163,7 @@ import CameraOffline from '@/icons/CameraOffline.vue';
|
|||||||
import EventAll from '@/icons/EventAll.vue';
|
import EventAll from '@/icons/EventAll.vue';
|
||||||
import EventClosed from '@/icons/EventClosed.vue';
|
import EventClosed from '@/icons/EventClosed.vue';
|
||||||
import EventPending from '@/icons/EventPending.vue';
|
import EventPending from '@/icons/EventPending.vue';
|
||||||
|
import { useGlobalTimerStore } from '@/stores/globalTimerStore';
|
||||||
|
|
||||||
const apiInstance = new BoxApi();
|
const apiInstance = new BoxApi();
|
||||||
const cameraCount = ref(0);
|
const cameraCount = ref(0);
|
||||||
@@ -320,8 +321,19 @@ onMounted(() => {
|
|||||||
// getMonthData();
|
// getMonthData();
|
||||||
fetchCameras();
|
fetchCameras();
|
||||||
fetchEvents();
|
fetchEvents();
|
||||||
|
const globalTimerStore = useGlobalTimerStore();
|
||||||
|
globalTimerStore.registerCallback(fetchCameras);
|
||||||
|
globalTimerStore.registerCallback(fetchEvents);
|
||||||
|
|
||||||
|
globalTimerStore.startTimer();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
const globalTimerStore = useGlobalTimerStore();
|
||||||
|
|
||||||
|
// 注销回调
|
||||||
|
globalTimerStore.unregisterCallback(fetchCameras);
|
||||||
|
globalTimerStore.unregisterCallback(fetchEvents);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -332,8 +344,12 @@ onMounted(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 1vh;
|
margin: 1vh;
|
||||||
padding: 2vh 0;
|
padding: 1vh 1vw;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width:none;
|
||||||
|
}
|
||||||
|
.count-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -341,14 +357,19 @@ onMounted(() => {
|
|||||||
.bottom-row {
|
.bottom-row {
|
||||||
background-color: #001529;
|
background-color: #001529;
|
||||||
color: aliceblue;
|
color: aliceblue;
|
||||||
padding: 0;
|
padding: 0.5vh;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inner-title-text{
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.inner-count-text {
|
.inner-count-text {
|
||||||
color: rgb(91, 224, 241);
|
color: rgb(91, 224, 241);
|
||||||
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-div {
|
.tab-div {
|
||||||
@@ -357,7 +378,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
::v-deep .el-tabs__item {
|
::v-deep .el-tabs__item {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 13px;
|
font-size: 0.8rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-left: 1vh;
|
margin-left: 1vh;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
|||||||
@@ -3,18 +3,18 @@
|
|||||||
|
|
||||||
<div class="search-row">
|
<div class="search-row">
|
||||||
<div class="bt-search">
|
<div class="bt-search">
|
||||||
<el-button type="primary" @click="handleFilter" class="alert-bt">点击查询</el-button>
|
<el-button type="primary" @click="handleFilter" class="alert-bt"><el-icon><Search /></el-icon>点击查询</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="start-col">
|
<div class="start-col">
|
||||||
<el-date-picker v-model="filterParams.timeAfter" :teleported="false" type="datetime" placeholder="请选择开始时间" prefix-icon="CaretBottom" popper-class="popperClass" ></el-date-picker>
|
<el-date-picker v-model="filterParams.timeAfter" :teleported="false" type="datetime" placeholder="请选择开始时间"
|
||||||
|
prefix-icon="CaretBottom" popper-class="popperClass"></el-date-picker>
|
||||||
</div>
|
</div>
|
||||||
<div class="end-col">
|
<div class="end-col">
|
||||||
<el-date-picker v-model="filterParams.timeBefore" :teleported="false" type="datetime" placeholder="请选择结束时间" prefix-icon="CaretBottom" popper-class="popperClass" ></el-date-picker>
|
<el-date-picker v-model="filterParams.timeBefore" :teleported="false" type="datetime" placeholder="请选择结束时间"
|
||||||
|
prefix-icon="CaretBottom" popper-class="popperClass"></el-date-picker>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div id="3d-bar-chart" class="myPanChart" ></div>
|
<div id="3d-bar-chart" class="myPanChart"></div>
|
||||||
|
|
||||||
<!-- <el-row>
|
<!-- <el-row>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
@@ -31,9 +31,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted,onBeforeUnmount,computed} from 'vue';
|
import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue';
|
||||||
import { BoxApi } from '@/utils/boxApi.ts';
|
import { BoxApi } from '@/utils/boxApi.ts';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
import { Search } from '@element-plus/icons-vue';
|
||||||
|
import { useGlobalTimerStore } from '@/stores/globalTimerStore';
|
||||||
|
|
||||||
|
const globalTimerStore = useGlobalTimerStore();
|
||||||
|
|
||||||
const apiInstance = new BoxApi();
|
const apiInstance = new BoxApi();
|
||||||
const typeMapping = reactive({});
|
const typeMapping = reactive({});
|
||||||
@@ -60,22 +64,118 @@ const renderChart = () => {
|
|||||||
const chartInstance = echarts.init(chartDom);
|
const chartInstance = echarts.init(chartDom);
|
||||||
|
|
||||||
const colorList = [
|
const colorList = [
|
||||||
// new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|
||||||
// { offset: 0, color: "rgba(69,233,254,1)" },
|
|
||||||
// { offset: 1, color: "rgba(69,233,254,0.3)" }
|
|
||||||
// ]),
|
|
||||||
new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
||||||
{ offset: 0, color: "rgba(255,181,111,1)" },
|
{ offset: 0, color: "rgba(135, 206, 250,0.7)" },
|
||||||
{ offset: 1, color: "rgba(255,181,111,0.3)" }
|
{ offset: 1, color: "rgba(135, 206, 250,0.3)" }
|
||||||
]),
|
]),
|
||||||
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||||
{ offset: 0, color: "rgba(101,122,250,1)" },
|
{ offset: 0, color: "rgba(95, 158, 160,0.8)" },
|
||||||
{ offset: 1, color: "rgba(101,122,250,0.3)" }
|
{ offset: 1, color: "rgba(95, 158, 160,0.3)" }
|
||||||
|
]),
|
||||||
|
new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(255, 99, 71, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(255, 99, 71, 0.3)" }
|
||||||
|
]),
|
||||||
|
new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(218, 112, 214,0.6)" },
|
||||||
|
{ offset: 1, color: "rgba(218, 112, 214,0.3)" }
|
||||||
|
]),
|
||||||
|
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(138, 43, 226,0.7)" },
|
||||||
|
{ offset: 1, color: "rgba(138, 43, 226,0.3)" }
|
||||||
]),
|
]),
|
||||||
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
{ offset: 0, color: "rgba(45,190,146,1)" },
|
{ offset: 0, color: "rgba(75, 0, 130,0.8)" },
|
||||||
{ offset: 1, color: "rgba(45,190,146,0.3)" }
|
{ offset: 1, color: "rgba(75, 0, 130,0.3)" }
|
||||||
]),
|
]),
|
||||||
|
new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(173, 216, 230,0.7)" },
|
||||||
|
{ offset: 1, color: "rgba(173, 216, 230,0.3)" }
|
||||||
|
]),
|
||||||
|
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(100, 149, 237,0.8)" },
|
||||||
|
{ offset: 1, color: "rgba(100, 149, 237,0.3)" }
|
||||||
|
]),
|
||||||
|
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: "rgba(25, 25, 112,0.9)" },
|
||||||
|
{ offset: 1, color: "rgba(25, 25, 112,0.3)" }
|
||||||
|
]),
|
||||||
|
new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(64, 224, 208,0.7)" },
|
||||||
|
{ offset: 1, color: "rgba(64, 224, 208,0.3)" }
|
||||||
|
]),
|
||||||
|
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(32, 178, 170,0.8)" },
|
||||||
|
{ offset: 1, color: "rgba(32, 178, 170,0.3)" }
|
||||||
|
]),
|
||||||
|
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: "rgba(47, 79, 79,0.9)" },
|
||||||
|
{ offset: 1, color: "rgba(47, 79, 79,0.3)" }
|
||||||
|
]),
|
||||||
|
//深海绿 //海浪青 //薄荷青 //幽夜蓝 //极地蓝 //浅冰蓝 //深紫罗兰 //静夜紫 //薰衣草紫 //薄暮紫 //冰川青 //清晨蓝
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: "rgba(123, 104, 238,0.7)" },
|
||||||
|
{ offset: 1, color: "rgba(123, 104, 238,0.3)" }
|
||||||
|
]),
|
||||||
|
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: "rgba(255, 165, 0, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(255, 165, 0, 0.3)" }
|
||||||
|
]), // 橙色
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(255, 182, 193, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(255, 182, 193, 0.3)" }
|
||||||
|
]), // 浅粉红
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(250, 128, 114, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(250, 128, 114, 0.3)" }
|
||||||
|
]), // 鲑鱼橙
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(240, 128, 128, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(240, 128, 128, 0.3)" }
|
||||||
|
]), // 浅珊瑚红
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: "rgba(255, 140, 0, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(255, 140, 0, 0.3)" }
|
||||||
|
]), // 深橙色
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(255, 215, 0, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(255, 215, 0, 0.3)" }
|
||||||
|
]), // 金色
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(255, 228, 181, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(255, 228, 181, 0.3)" }
|
||||||
|
]), // 小麦色
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: "rgba(255, 160, 122, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(255, 160, 122, 0.3)" }
|
||||||
|
]), // 浅鲑鱼色
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(250, 250, 210, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(250, 250, 210, 0.3)" }
|
||||||
|
]), // 浅金黄
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||||
|
{ offset: 0, color: "rgba(255, 223, 186, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(255, 223, 186, 0.3)" }
|
||||||
|
]), // 暖杏色
|
||||||
|
|
||||||
|
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: "rgba(245, 222, 179, 1)" },
|
||||||
|
{ offset: 1, color: "rgba(245, 222, 179, 0.3)" }
|
||||||
|
]), // 小麦暖黄
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -134,7 +234,7 @@ const renderChart = () => {
|
|||||||
radius: ['10%', '90%'],
|
radius: ['10%', '90%'],
|
||||||
center: ['50%', '50%'],
|
center: ['50%', '50%'],
|
||||||
avoidLabelOverlap: true,
|
avoidLabelOverlap: true,
|
||||||
padding:[0,10],
|
padding: [0, 10],
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'inside',
|
position: 'inside',
|
||||||
@@ -202,9 +302,9 @@ const handleFilter = () => {
|
|||||||
const timeAfter = filterParams.timeAfter ? formatDateTimeToISO(new Date(filterParams.timeAfter)) : null;
|
const timeAfter = filterParams.timeAfter ? formatDateTimeToISO(new Date(filterParams.timeAfter)) : null;
|
||||||
const timeBefore = filterParams.timeBefore ? formatDateTimeToISO(new Date(filterParams.timeBefore)) : null;
|
const timeBefore = filterParams.timeBefore ? formatDateTimeToISO(new Date(filterParams.timeBefore)) : null;
|
||||||
fetchTypeCounts(timeAfter, timeBefore); // 重新统计数量,添加时间条件
|
fetchTypeCounts(timeAfter, timeBefore); // 重新统计数量,添加时间条件
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const formatDateTime = (datetime) => {
|
const formatDateTime = (datetime) => {
|
||||||
const date = new Date(datetime);
|
const date = new Date(datetime);
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
@@ -238,10 +338,14 @@ onMounted(async () => {
|
|||||||
await fetchTypeCounts(); // 初次加载时不加时间条件
|
await fetchTypeCounts(); // 初次加载时不加时间条件
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
await renderChart();
|
await renderChart();
|
||||||
|
globalTimerStore.registerCallback(handleFilter);
|
||||||
|
globalTimerStore.startTimer();
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
|
globalTimerStore.unregisterCallback(handleFilter);
|
||||||
|
globalTimerStore.stopTimer();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -249,7 +353,7 @@ onBeforeUnmount(() => {
|
|||||||
/* .statistics-container {
|
/* .statistics-container {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
} */
|
} */
|
||||||
.alert-container{
|
.alert-container {
|
||||||
height: auto;
|
height: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -262,7 +366,7 @@ onBeforeUnmount(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 5vh;
|
height: 5vh;
|
||||||
} */
|
} */
|
||||||
.search-row{
|
.search-row {
|
||||||
width: 95%;
|
width: 95%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: #001529;
|
background-color: #001529;
|
||||||
@@ -271,11 +375,13 @@ onBeforeUnmount(() => {
|
|||||||
align-items: left;
|
align-items: left;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
gap:1vh;
|
gap: 1vh;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.start-col,.end-col,.bt-search{
|
.start-col,
|
||||||
|
.end-col,
|
||||||
|
.bt-search {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -289,18 +395,18 @@ onBeforeUnmount(() => {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.myPanChart{
|
.myPanChart {
|
||||||
width: 95%;
|
width: 95%;
|
||||||
height: 25vh;
|
height: 25vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .search-row .el-input__wrapper{
|
::v-deep .search-row .el-input__wrapper {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-bt{
|
.alert-bt {
|
||||||
background-color: #001529;
|
background-color: #001529;
|
||||||
/* background-color: #4a4a4a; */
|
/* background-color: #4a4a4a; */
|
||||||
border: #001529;
|
border: #001529;
|
||||||
|
|||||||
@@ -1,18 +1,74 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="settings-container">
|
<div class="settings-container">
|
||||||
<el-row class="popup-row">
|
<el-row class="tip-row">
|
||||||
<el-col :sm="24" :md="24">弹窗设置</el-col>
|
|
||||||
|
<div class="model-row">
|
||||||
|
<el-col :sm="24" :md="8">
|
||||||
|
<el-card style="max-width: 480px" >
|
||||||
|
<template #header>
|
||||||
|
弹窗模式设置
|
||||||
|
</template>
|
||||||
|
<el-row>
|
||||||
<el-col :sm="24" :md="24">
|
<el-col :sm="24" :md="24">
|
||||||
<el-checkbox v-model="isInteractivePopupEnabled" @change="handleInteractiveChange" style="color: aliceblue;">
|
<el-checkbox v-model="isInteractivePopupEnabled" @change="handleInteractiveChange">
|
||||||
开启交互式弹窗
|
开启交互式弹窗
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :sm="24" :md="24">
|
<el-col :sm="24" :md="24">
|
||||||
<el-checkbox v-model="isResponsivePopupEnabled" @change="handleResponsiveChange" style="color: aliceblue;">
|
<el-checkbox v-model="isResponsivePopupEnabled" @change="handleResponsiveChange">
|
||||||
开启响应式弹窗
|
开启响应式弹窗
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
<template #footer>(Tips: 不支持同时生效,关闭状态无声音)</template>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :sm="24" :md="8">
|
||||||
|
<el-card style="max-width: 480px" >
|
||||||
|
<template #header>
|
||||||
|
<span>弹窗队列设置</span>
|
||||||
|
</template>
|
||||||
|
<el-col :sm="24" :md="24" style="margin:1vh 0;">
|
||||||
|
<el-button @click="loadNextNotification" size="small" type="primary">加载更多</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :sm="24" :md="24">
|
||||||
|
<el-button @click="clearAllNotifications" size="small" type="danger">清空所有</el-button>
|
||||||
|
</el-col>
|
||||||
|
<template #footer>(Tips: 交互模式下加载/清空累积弹窗提示)</template>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :sm="24" :md="8" class="audio-card">
|
||||||
|
<el-card style="max-width: 480px" >
|
||||||
|
<template #header>
|
||||||
|
<span>弹窗声音设置</span>
|
||||||
|
</template>
|
||||||
|
<el-col :sm="24" :md="24">
|
||||||
|
开启提示声音:
|
||||||
|
<el-switch v-model="isSoundEnabled" @change="handleSoundSwitch">开启振荡器发声</el-switch>
|
||||||
|
</el-col>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :sm="24" :md="5" >
|
||||||
|
<span>音量设置: </span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :sm="24" :md="16">
|
||||||
|
<el-slider v-model="volume" :min="0" :max="1" :step="0.01" :disabled="isSoundEnabled"
|
||||||
|
@change="previewSound" size="small" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :sm="24" :md="5" >
|
||||||
|
<span>音调设置: </span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :sm="24" :md="16">
|
||||||
|
<el-slider v-model="frequency" :min="300" :max="1200" :step="10" :disabled="isSoundEnabled"
|
||||||
|
@change="previewSound" size="small" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<template #footer>(Tips: 开启后锁定声音设置)</template>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</div>
|
||||||
|
</el-row>
|
||||||
<el-row class="channel-row">
|
<el-row class="channel-row">
|
||||||
<Channel />
|
<Channel />
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -20,26 +76,90 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, inject, onMounted } from 'vue';
|
import { ref, inject, onMounted, onUnmounted, watch } from 'vue';
|
||||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||||
import type { GlobalWebSocket } from '@/utils/useGlobalWebSocket';
|
import type { GlobalWebSocket } from '@/utils/useGlobalWebSocket';
|
||||||
import Channel from '@/components/Channel.vue';
|
import Channel from '@/components/Channel.vue';
|
||||||
|
|
||||||
|
|
||||||
const isInteractivePopupEnabled = ref(false); // 交互式弹窗状态
|
const isInteractivePopupEnabled = ref(false); // 交互式弹窗状态
|
||||||
const isResponsivePopupEnabled = ref(false); // 响应式弹窗状态
|
const isResponsivePopupEnabled = ref(false); // 响应式弹窗状态
|
||||||
const globalWebSocket = inject<GlobalWebSocket>('globalWebSocket');
|
|
||||||
|
|
||||||
if (!globalWebSocket) {
|
const globalWebSocket = inject<GlobalWebSocket>('globalWebSocket');
|
||||||
throw new Error('globalWebSocket 注入失败');
|
if (!globalWebSocket) throw new Error('globalWebSocket 注入失败');
|
||||||
}
|
|
||||||
|
const { notificationSoundParams, setNotificationSoundParams } = globalWebSocket;
|
||||||
|
const isSoundEnabled = ref(notificationSoundParams.isSoundEnabled);
|
||||||
|
const volume = ref(notificationSoundParams.volume);
|
||||||
|
const frequency = ref(notificationSoundParams.frequency);
|
||||||
|
let audioContext: AudioContext | null = null;
|
||||||
|
const loadNextNotification = () => {
|
||||||
|
if (globalWebSocket) globalWebSocket.loadNextNotification();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAllNotifications = () => {
|
||||||
|
if (globalWebSocket) globalWebSocket.clearAllNotifications();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 初始化时加载弹窗模式状态
|
// 初始化时加载弹窗模式状态
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
isInteractivePopupEnabled.value = localStorage.getItem('isInteractivePopupEnabled') === 'true';
|
isInteractivePopupEnabled.value = localStorage.getItem('isInteractivePopupEnabled') === 'true';
|
||||||
isResponsivePopupEnabled.value = localStorage.getItem('isResponsivePopupEnabled') === 'true';
|
isResponsivePopupEnabled.value = localStorage.getItem('isResponsivePopupEnabled') === 'true';
|
||||||
|
isSoundEnabled.value = notificationSoundParams.isSoundEnabled;
|
||||||
|
volume.value = notificationSoundParams.volume;
|
||||||
|
frequency.value = notificationSoundParams.frequency;
|
||||||
updateWebSocketConnection();
|
updateWebSocketConnection();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSoundSwitch = () => {
|
||||||
|
setNotificationSoundParams({
|
||||||
|
isSoundEnabled: isSoundEnabled.value,
|
||||||
|
volume: volume.value,
|
||||||
|
frequency: frequency.value,
|
||||||
|
});
|
||||||
|
ElMessage.success(isSoundEnabled.value ? '提示音已开启,当前参数已锁定' : '提示音已关闭');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 预览提示音
|
||||||
|
const previewSound = () => {
|
||||||
|
if (audioContext) {
|
||||||
|
audioContext.close();
|
||||||
|
}
|
||||||
|
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
const oscillator = audioContext.createOscillator();
|
||||||
|
const gainNode = audioContext.createGain();
|
||||||
|
|
||||||
|
oscillator.type = 'triangle';
|
||||||
|
oscillator.frequency.setValueAtTime(frequency.value, audioContext.currentTime);
|
||||||
|
gainNode.gain.setValueAtTime(volume.value, audioContext.currentTime);
|
||||||
|
|
||||||
|
oscillator.connect(gainNode);
|
||||||
|
gainNode.connect(audioContext.destination);
|
||||||
|
|
||||||
|
oscillator.start();
|
||||||
|
setTimeout(() => {
|
||||||
|
oscillator.stop();
|
||||||
|
audioContext?.close();
|
||||||
|
audioContext = null;
|
||||||
|
}, 300); // 持续播放 300ms
|
||||||
|
};
|
||||||
|
|
||||||
|
watch([isSoundEnabled, volume, frequency], () => {
|
||||||
|
setNotificationSoundParams({
|
||||||
|
isSoundEnabled: isSoundEnabled.value,
|
||||||
|
volume: volume.value,
|
||||||
|
frequency: frequency.value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// const handleInteractiveChange = () => {
|
// const handleInteractiveChange = () => {
|
||||||
// if (isInteractivePopupEnabled.value) {
|
// if (isInteractivePopupEnabled.value) {
|
||||||
// isResponsivePopupEnabled.value = false;
|
// isResponsivePopupEnabled.value = false;
|
||||||
@@ -132,6 +252,10 @@ const updateWebSocketConnection = () => {
|
|||||||
globalWebSocket.closeWebSocket();
|
globalWebSocket.closeWebSocket();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
audioContext?.close();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -143,7 +267,9 @@ const updateWebSocketConnection = () => {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-row {
|
.tip-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
height: 20vh;
|
height: 20vh;
|
||||||
width: 80vw;
|
width: 80vw;
|
||||||
@@ -154,6 +280,62 @@ const updateWebSocketConnection = () => {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tip-row .el-card{
|
||||||
|
border: none !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-row .el-card .el-checkbox{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .tip-row .el-card__header {
|
||||||
|
background-color: #001529;
|
||||||
|
/* height: 3vh; */
|
||||||
|
padding: 1vh 0 1vh 1vw;
|
||||||
|
display: flex;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .tip-row .el-card__body {
|
||||||
|
background-color: #001529;
|
||||||
|
height: 12vh;
|
||||||
|
font-size: 16px;
|
||||||
|
/* border: 0px ; */
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .tip-row .el-card .is-always-shadow{
|
||||||
|
color: aqua;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .tip-row .el-card__footer {
|
||||||
|
background-color: #001529;
|
||||||
|
|
||||||
|
/* height: 3vh; */
|
||||||
|
padding: 0 1vw 0 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
border-top: none !important;
|
||||||
|
color: #a8a3a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.model-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .audio-card .el-card__body {
|
||||||
|
padding: 1vh 1vw;
|
||||||
|
/* background-color: aqua; */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.channel-row {
|
.channel-row {
|
||||||
margin: 1vh 2vw;
|
margin: 1vh 2vw;
|
||||||
width: 80vw;
|
width: 80vw;
|
||||||
|
|||||||
@@ -48,34 +48,38 @@
|
|||||||
<el-date-picker v-model="filterParams.timeBefore" type="datetime" placeholder="请选择结束时间"></el-date-picker>
|
<el-date-picker v-model="filterParams.timeBefore" type="datetime" placeholder="请选择结束时间"></el-date-picker>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="4" class="filter-buttons" :offset="15">
|
<el-col :span="8" class="filter-buttons" :offset="0">
|
||||||
<el-button type="primary" @click="handleFilter">查询</el-button>
|
<el-button type="primary" @click="handleFilter">查询</el-button>
|
||||||
<el-button @click="handleReset">重置</el-button>
|
<el-button @click="handleReset">重置</el-button>
|
||||||
<el-button :disabled="isExporting" @click="exportData">
|
<el-button :disabled="isExporting" @click="exportData">
|
||||||
{{ isExporting ? '正在导出,请勿重复点击' : '导出' }}
|
{{ isExporting ? '正在导出,请勿重复点击' : '导出' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
|
||||||
<el-col :span="1" :offset="0">
|
|
||||||
<el-button type="primary" :disabled="!selectedAlerts.length" @click="batchMarkAsProcessed">
|
<el-button type="primary" :disabled="!selectedAlerts.length" @click="batchMarkAsProcessed">
|
||||||
批量标记为已处理
|
批量标记为已处理
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<!-- <el-button type="danger" :disabled="!selectedAlerts.length" @click="handleDelete">删除选中</el-button> -->
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="15" :offset="0">
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row class="table-row">
|
<el-row class="table-row">
|
||||||
<el-col :span="24" class="table-col">
|
<el-col :span="24" class="table-col">
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<el-table :data="tableData" header-row-class-name="table-header" :fit="true" height="580"
|
<el-table :data="tableData" class="events-table" :fit="true"
|
||||||
@selection-change="handleSelectionChange">
|
@selection-change="handleSelectionChange">
|
||||||
<el-table-column type="selection" min-width="55"></el-table-column>
|
<el-table-column type="selection" min-width="55" align="center"></el-table-column>
|
||||||
<el-table-column prop="id" label="告警编号" min-width="100"></el-table-column>
|
<el-table-column type="index" label="序号" min-width="30"
|
||||||
<el-table-column label="告警类型" min-width="150">
|
:index="(index) => index + 1 + (currentPage - 1) * pageSize" align="center">
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="id" label="告警编号" min-width="100" align="center" v-if="showColumn"></el-table-column>
|
||||||
|
<el-table-column label="告警类型" min-width="100" align="center">
|
||||||
<template v-slot="scope">
|
<template v-slot="scope">
|
||||||
{{ typeMapping[scope.row.types] }}
|
{{ typeMapping[scope.row.types] }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="camera.name" label="告警位置" min-width="150"></el-table-column>
|
<el-table-column prop="camera.name" label="告警位置" min-width="100" align="center"></el-table-column>
|
||||||
<el-table-column label="告警时间" min-width="200">
|
<el-table-column label="告警时间" min-width="200" align="center">
|
||||||
<template v-slot="scope">
|
<template v-slot="scope">
|
||||||
{{ formatDateTime(scope.row.ended_at) }}
|
{{ formatDateTime(scope.row.ended_at) }}
|
||||||
</template>
|
</template>
|
||||||
@@ -149,7 +153,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
<!-- <div class="event-media" :class="{ 'center-media': mediums.length === 1 }"> -->
|
<!-- <div class="event-media" :class="{ 'center-media': mediums.length === 1 }"> -->
|
||||||
<div class="event-media" >
|
<div class="event-media">
|
||||||
<!-- 告警关联视频 -->
|
<!-- 告警关联视频 -->
|
||||||
<!-- <div v-if="hasVideo" class="media-container video-item">
|
<!-- <div v-if="hasVideo" class="media-container video-item">
|
||||||
<p>告警关联视频</p>
|
<p>告警关联视频</p>
|
||||||
@@ -200,9 +204,9 @@ import { ref, reactive, onMounted, computed } from 'vue';
|
|||||||
// import Statistics from '@/components/Statistics.vue';
|
// import Statistics from '@/components/Statistics.vue';
|
||||||
// import AlertChart from '@/components/AlertChart.vue';
|
// import AlertChart from '@/components/AlertChart.vue';
|
||||||
import { BoxApi } from '@/utils/boxApi.ts'; // 导入 BoxApi
|
import { BoxApi } from '@/utils/boxApi.ts'; // 导入 BoxApi
|
||||||
|
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
import Papa from 'papaparse';
|
import Papa from 'papaparse';
|
||||||
|
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
// 创建 BoxApi 实例
|
// 创建 BoxApi 实例
|
||||||
const boxApi = new BoxApi();
|
const boxApi = new BoxApi();
|
||||||
@@ -232,6 +236,7 @@ const displayTotalItems = ref(0); // 用于展示的数字
|
|||||||
const cameras = ref([]);
|
const cameras = ref([]);
|
||||||
|
|
||||||
const selectedAlerts = ref([]);
|
const selectedAlerts = ref([]);
|
||||||
|
const showColumn = ref(true);
|
||||||
|
|
||||||
const handleSelectionChange = (selectedRows) => {
|
const handleSelectionChange = (selectedRows) => {
|
||||||
selectedAlerts.value = selectedRows.map(row => row.id);
|
selectedAlerts.value = selectedRows.map(row => row.id);
|
||||||
@@ -250,6 +255,49 @@ const batchMarkAsProcessed = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
// 显示确认框
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`确认删除选中的 ${selectedAlerts.value.length} 条告警吗?`,
|
||||||
|
'删除确认',
|
||||||
|
{
|
||||||
|
confirmButtonText: '删除',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
// 调用 API 删除选中的告警
|
||||||
|
const results = await boxApi.delEvents(token.value, selectedAlerts.value);
|
||||||
|
|
||||||
|
// 检查结果,显示操作成功或失败
|
||||||
|
const failedDeletions = results.filter(item => !item.success);
|
||||||
|
if (failedDeletions.length === 0) {
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
} else {
|
||||||
|
ElMessage.error(
|
||||||
|
`部分删除失败:${failedDeletions
|
||||||
|
.map(item => `ID: ${item.id}`)
|
||||||
|
.join(', ')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新表格数据
|
||||||
|
await fetchEvents();
|
||||||
|
selectedAlerts.value = []; // 清空选中项
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除操作失败:', error);
|
||||||
|
ElMessage.error('删除失败,请稍后再试');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 用户取消删除操作
|
||||||
|
ElMessage.info('删除已取消');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const formatDateTimeToISO = (datetime) => {
|
const formatDateTimeToISO = (datetime) => {
|
||||||
return new Date(datetime).toISOString().replace('.000', '');
|
return new Date(datetime).toISOString().replace('.000', '');
|
||||||
@@ -447,9 +495,10 @@ const fetchTypeMapping = async (token) => {
|
|||||||
const fetchEvents = async () => {
|
const fetchEvents = async () => {
|
||||||
try {
|
try {
|
||||||
const { tableData: data, totalItems: total } = await boxApi.getEvents(token.value, pageSize.value, currentPage.value); // 使用 BoxApi 的 getEvents 方法
|
const { tableData: data, totalItems: total } = await boxApi.getEvents(token.value, pageSize.value, currentPage.value); // 使用 BoxApi 的 getEvents 方法
|
||||||
tableData.value = data;
|
// tableData.value = data;
|
||||||
|
tableData.value = data.sort((a, b) => new Date(b.ended_at) - new Date(a.ended_at));
|
||||||
totalItems.value = total;
|
totalItems.value = total;
|
||||||
animateNumberChange(total);
|
// animateNumberChange(total);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching events data:", error);
|
console.error("Error fetching events data:", error);
|
||||||
}
|
}
|
||||||
@@ -588,7 +637,8 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.table-container {
|
.table-container {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: 80%;
|
height: 100%;
|
||||||
|
height: 56vh;
|
||||||
/* min-height: 50vh; */
|
/* min-height: 50vh; */
|
||||||
/* max-height: 70vh; */
|
/* max-height: 70vh; */
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
@@ -600,6 +650,11 @@ onMounted(async () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::v-deep .events-table .el-table__inner-wrapper{
|
||||||
|
height: 55vh;
|
||||||
|
max-height: 56vh;
|
||||||
|
}
|
||||||
|
|
||||||
.pagination-container {
|
.pagination-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
@@ -608,10 +663,10 @@ onMounted(async () => {
|
|||||||
margin: 0vh 6vw 5vh 1vw;
|
margin: 0vh 6vw 5vh 1vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-header {
|
/* .table-header {
|
||||||
background-color: #f7f8fa;
|
background-color: #f7f8fa;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
} */
|
||||||
|
|
||||||
::v-deep .el-table th.el-table__cell {
|
::v-deep .el-table th.el-table__cell {
|
||||||
background-color: #e9eefca4;
|
background-color: #e9eefca4;
|
||||||
|
|||||||
@@ -114,7 +114,7 @@
|
|||||||
if (newVal) {
|
if (newVal) {
|
||||||
// cameraDataZ.value = JSON.parse(JSON.stringify(newVal));
|
// cameraDataZ.value = JSON.parse(JSON.stringify(newVal));
|
||||||
Object.assign(cameraDataZ.value, JSON.parse(JSON.stringify(props.cameraData)));
|
Object.assign(cameraDataZ.value, JSON.parse(JSON.stringify(props.cameraData)));
|
||||||
console.log("cameraDataZ:", cameraDataZ.value);
|
// console.log("cameraDataZ:", cameraDataZ.value);
|
||||||
formatCameraData(cameraDataZ.value);
|
formatCameraData(cameraDataZ.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,7 +299,7 @@
|
|||||||
// mode: rule.mode,
|
// mode: rule.mode,
|
||||||
// })),
|
// })),
|
||||||
};
|
};
|
||||||
console.log("页面的cameraUpdate》》》》》》》》》》》:", cameraUpdate);
|
// console.log("页面的cameraUpdate》》》》》》》》》》》:", cameraUpdate);
|
||||||
|
|
||||||
// 构造 ruleUpdate 数据
|
// 构造 ruleUpdate 数据
|
||||||
const ruleUpdate = CameraDialog.value.rules.map((rule) => ({
|
const ruleUpdate = CameraDialog.value.rules.map((rule) => ({
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<dv-decoration-11 class="custom-decoration" style="width:25vw;height:70px;">
|
<dv-decoration-11 class="custom-decoration" style="width:25vw;height:70px;">
|
||||||
<div color-green font-700 bg="~ dark/0">
|
<div color-green font-700 bg="~ dark/0">
|
||||||
<dv-decoration7 style="width:20vw;height:30px;">
|
<dv-decoration7 style="width:20vw;height:30px;">
|
||||||
<div color-white font-400 >
|
<div color-white font-400>
|
||||||
告警数据面板
|
告警数据面板
|
||||||
</div>
|
</div>
|
||||||
</dv-decoration7>
|
</dv-decoration7>
|
||||||
@@ -54,11 +54,11 @@
|
|||||||
<div class="section bottom-left corner-style">左下
|
<div class="section bottom-left corner-style">左下
|
||||||
<div class="section hiden"></div>
|
<div class="section hiden"></div>
|
||||||
</div> -->
|
</div> -->
|
||||||
<dv-border-box-13 title="告警数据概览(数据计算数字)" class="section top-left">
|
<dv-border-box-13 title="告警数据概览(数据计算数字)" class="section top-left"><strong>告警数据统计</strong>
|
||||||
<LeftTop />
|
<LeftTop />
|
||||||
</dv-border-box-13>
|
</dv-border-box-13>
|
||||||
<dv-border-box-13 title="点位告警数量(不同点位的数量)" class="section middle-left">不同点位告警的数量
|
<dv-border-box-13 title="点位告警数量(不同点位的数量)" class="section middle-left"><strong>告警点位数据</strong>
|
||||||
<LeftMiddle/>
|
<LeftMiddle />
|
||||||
</dv-border-box-13>
|
</dv-border-box-13>
|
||||||
<!-- <dv-border-box-13 title="今日告警列表(告警详情)" class="section bottom-left ">今日告警列表(告警详情)
|
<!-- <dv-border-box-13 title="今日告警列表(告警详情)" class="section bottom-left ">今日告警列表(告警详情)
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
<!-- 中部区域 -->
|
<!-- 中部区域 -->
|
||||||
<div class="center-section">
|
<div class="center-section">
|
||||||
<dv-border-box8 class="center-top">
|
<dv-border-box8 class="center-top">
|
||||||
<CenterTop/>
|
<CenterTop />
|
||||||
<!-- <dv-border-box8 class="center-top-header">警戒画面</dv-border-box8>
|
<!-- <dv-border-box8 class="center-top-header">警戒画面</dv-border-box8>
|
||||||
<div class="center-top-grids">
|
<div class="center-top-grids">
|
||||||
<div class="grid-item">栅格左上</div>
|
<div class="grid-item">栅格左上</div>
|
||||||
@@ -79,20 +79,20 @@
|
|||||||
</dv-border-box8>
|
</dv-border-box8>
|
||||||
<div class="center-bottom">
|
<div class="center-bottom">
|
||||||
<!-- <dv-border-box-13 class="center-bottom-left">中下左</dv-border-box-13> -->
|
<!-- <dv-border-box-13 class="center-bottom-left">中下左</dv-border-box-13> -->
|
||||||
<dv-border-box-13 class="center-bottom-right">告警数量分布情况
|
<dv-border-box-13 class="center-bottom-right"><strong>告警数量分布</strong>
|
||||||
<CenterBottom/>
|
<CenterBottom />
|
||||||
</dv-border-box-13>
|
</dv-border-box-13>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧区域 -->
|
<!-- 右侧区域 -->
|
||||||
<div class="right-section">
|
<div class="right-section">
|
||||||
<dv-border-box-13 class="section top-right corner-style">时间段告警总数分布
|
<dv-border-box-13 class="section top-right corner-style"><strong>告警种类数据</strong>
|
||||||
<RightTop/>
|
<RightTop />
|
||||||
</dv-border-box-13>
|
</dv-border-box-13>
|
||||||
<!-- <dv-border-box-13 class="section middle-right corner-style">告警数量分布</dv-border-box-13> -->
|
<!-- <dv-border-box-13 class="section middle-right corner-style">告警数量分布</dv-border-box-13> -->
|
||||||
<dv-border-box-13 class="section bottom-right corner-style">告警种类划分
|
<dv-border-box-13 class="section bottom-right corner-style"><strong>今日告警数据</strong>
|
||||||
<LeftBottom/>
|
<LeftBottom />
|
||||||
</dv-border-box-13>
|
</dv-border-box-13>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,7 +122,7 @@ import CenterTop from '@/components/Max/CenterTop.vue';
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
padding: 4vh 10vw 10vh 7vw;
|
padding: 4vh 10vw 10vh 9vw;
|
||||||
/* background-color: rgb(121, 184, 243); */
|
/* background-color: rgb(121, 184, 243); */
|
||||||
background-color: #001529;
|
background-color: #001529;
|
||||||
/* background-image: url('/bg05.png'); */
|
/* background-image: url('/bg05.png'); */
|
||||||
@@ -133,10 +133,12 @@ import CenterTop from '@/components/Max/CenterTop.vue';
|
|||||||
position: relative;
|
position: relative;
|
||||||
color: black;
|
color: black;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-decoration{
|
.custom-decoration {
|
||||||
color: #70e5fa;;
|
color: #70e5fa;
|
||||||
|
;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
}
|
}
|
||||||
@@ -161,12 +163,13 @@ import CenterTop from '@/components/Max/CenterTop.vue';
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 80vw;
|
width: 82vw;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
gap: 1vh;
|
gap: 1vh;
|
||||||
position: relative;
|
position: relative;
|
||||||
/* 为了在背景下显示 */
|
/* 为了在背景下显示 */
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@@ -194,25 +197,31 @@ import CenterTop from '@/components/Max/CenterTop.vue';
|
|||||||
|
|
||||||
.title-right {
|
.title-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column; /* 设置为垂直布局 */
|
flex-direction: column;
|
||||||
|
/* 设置为垂直布局 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.first-row {
|
.first-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start; /* 第一行内容靠左 */
|
justify-content: flex-start;
|
||||||
|
/* 第一行内容靠左 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.second-row {
|
.second-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end; /* 第二行内容靠右 */
|
justify-content: flex-end;
|
||||||
|
/* 第二行内容靠右 */
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-section {
|
.main-section {
|
||||||
|
width: 84vw;
|
||||||
height: 72vh;
|
height: 72vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1vw;
|
gap: 1vw;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-section,
|
.left-section,
|
||||||
@@ -240,18 +249,20 @@ import CenterTop from '@/components/Max/CenterTop.vue';
|
|||||||
/* box-shadow: 0 2px 5px rgba(221, 204, 204, 0.5); */
|
/* box-shadow: 0 2px 5px rgba(221, 204, 204, 0.5); */
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-left{
|
.top-left {
|
||||||
position: relative;
|
/* position: relative; */
|
||||||
padding: 1vh 1vw;
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
/* padding: 1vh 1vw; */
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
width: 20vw;
|
width: 22vw;
|
||||||
height: 20vh;
|
height: 20vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.middle-left{
|
.middle-left {
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 22vw;
|
width: 22vw;
|
||||||
@@ -259,14 +270,14 @@ import CenterTop from '@/components/Max/CenterTop.vue';
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-right{
|
.top-right {
|
||||||
width: 22vw;
|
width: 22vw;
|
||||||
height: 46vh;
|
height: 46vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
/* padding: 1.5vh; */
|
/* padding: 1.5vh; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-left{
|
.bottom-left {
|
||||||
/* color: white; */
|
/* color: white; */
|
||||||
/* position: relative; */
|
/* position: relative; */
|
||||||
padding-left: 0.5vw;
|
padding-left: 0.5vw;
|
||||||
|
|||||||
@@ -1,164 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="settings-container">
|
<div class="glowing-border">
|
||||||
<el-row class="popup-row">
|
<div class="content">
|
||||||
<el-col :sm="24" :md="24">弹窗设置</el-col>
|
<!-- 在这里插入你想要的内容 -->
|
||||||
<el-col :sm="24" :md="24">
|
</div>
|
||||||
<el-checkbox v-model="isInteractivePopupEnabled" @change="handleInteractiveChange" style="color: aliceblue;">
|
|
||||||
开启交互式弹窗
|
|
||||||
</el-checkbox>
|
|
||||||
</el-col>
|
|
||||||
<el-col :sm="24" :md="24">
|
|
||||||
<el-checkbox v-model="isResponsivePopupEnabled" @change="handleResponsiveChange" style="color: aliceblue;">
|
|
||||||
开启响应式弹窗
|
|
||||||
</el-checkbox>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row class="channel-row">
|
|
||||||
<Channel />
|
|
||||||
</el-row>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, inject, onMounted } from 'vue';
|
// 这里可以留空或者添加其他逻辑
|
||||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
|
||||||
import type { GlobalWebSocket } from '@/utils/useGlobalWebSocket';
|
|
||||||
import Channel from '@/components/Channel.vue';
|
|
||||||
|
|
||||||
const isInteractivePopupEnabled = ref(false); // 交互式弹窗状态
|
|
||||||
const isResponsivePopupEnabled = ref(false); // 响应式弹窗状态
|
|
||||||
const globalWebSocket = inject<GlobalWebSocket>('globalWebSocket');
|
|
||||||
|
|
||||||
if (!globalWebSocket) {
|
|
||||||
throw new Error('globalWebSocket 注入失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化时加载弹窗模式状态
|
|
||||||
onMounted(() => {
|
|
||||||
isInteractivePopupEnabled.value = localStorage.getItem('isInteractivePopupEnabled') === 'true';
|
|
||||||
isResponsivePopupEnabled.value = localStorage.getItem('isResponsivePopupEnabled') === 'true';
|
|
||||||
updateWebSocketConnection();
|
|
||||||
});
|
|
||||||
|
|
||||||
// const handleInteractiveChange = () => {
|
|
||||||
// if (isInteractivePopupEnabled.value) {
|
|
||||||
// isResponsivePopupEnabled.value = false;
|
|
||||||
// localStorage.setItem('isResponsivePopupEnabled', 'false');
|
|
||||||
// }
|
|
||||||
// localStorage.setItem('isInteractivePopupEnabled', String(isInteractivePopupEnabled.value));
|
|
||||||
// updateWebSocketConnection();
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleResponsiveChange = () => {
|
|
||||||
// if (isResponsivePopupEnabled.value) {
|
|
||||||
// isInteractivePopupEnabled.value = false;
|
|
||||||
// localStorage.setItem('isInteractivePopupEnabled', 'false');
|
|
||||||
// }
|
|
||||||
// localStorage.setItem('isResponsivePopupEnabled', String(isResponsivePopupEnabled.value));
|
|
||||||
// updateWebSocketConnection();
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleInteractiveChange = () => {
|
|
||||||
if (isInteractivePopupEnabled.value) {
|
|
||||||
ElMessageBox.confirm('是否开启交互式弹窗提示?', '确认提示', {
|
|
||||||
confirmButtonText: '是',
|
|
||||||
cancelButtonText: '否',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
isResponsivePopupEnabled.value = false;
|
|
||||||
localStorage.setItem('isResponsivePopupEnabled', 'false');
|
|
||||||
|
|
||||||
localStorage.setItem('isInteractivePopupEnabled', 'true');
|
|
||||||
updateWebSocketConnection();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
isInteractivePopupEnabled.value = false;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ElMessageBox.confirm('是否关闭交互式弹窗?', '确认提示', {
|
|
||||||
confirmButtonText: '是',
|
|
||||||
cancelButtonText: '否',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
localStorage.setItem('isInteractivePopupEnabled', 'false');
|
|
||||||
updateWebSocketConnection();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
isInteractivePopupEnabled.value = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const handleResponsiveChange = () => {
|
|
||||||
if (isResponsivePopupEnabled.value) {
|
|
||||||
ElMessageBox.confirm('是否开启响应式弹窗提示?', '确认提示', {
|
|
||||||
confirmButtonText: '是',
|
|
||||||
cancelButtonText: '否',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
isInteractivePopupEnabled.value = false;
|
|
||||||
localStorage.setItem('isInteractivePopupEnabled', 'false');
|
|
||||||
localStorage.setItem('isResponsivePopupEnabled', 'true');
|
|
||||||
updateWebSocketConnection();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
isResponsivePopupEnabled.value = false;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ElMessageBox.confirm('是否关闭响应式弹窗?', '确认提示', {
|
|
||||||
confirmButtonText: '是',
|
|
||||||
cancelButtonText: '否',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
localStorage.setItem('isResponsivePopupEnabled', 'false');
|
|
||||||
updateWebSocketConnection();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
isResponsivePopupEnabled.value = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据开关状态更新 WebSocket 连接
|
|
||||||
const updateWebSocketConnection = () => {
|
|
||||||
if (isInteractivePopupEnabled.value || isResponsivePopupEnabled.value) {
|
|
||||||
globalWebSocket.connectWebSocket();
|
|
||||||
} else {
|
|
||||||
globalWebSocket.closeWebSocket();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.settings-container {
|
/* 外层容器 */
|
||||||
display: flex;
|
.glowing-border {
|
||||||
flex-direction: column;
|
width: 400px; /* 设置容器宽度 */
|
||||||
background-color: #F1F1F1;
|
height: 200px; /* 设置容器高度 */
|
||||||
|
background-color: #1a1a1a; /* 背景颜色 */
|
||||||
|
border-radius: 15px; /* 圆角 */
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 0 20px 5px rgba(138, 43, 226, 0.5);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* .glowing-border::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
left: -20px;
|
||||||
|
width: calc(100% + 40px);
|
||||||
|
height: calc(100% + 40px);
|
||||||
|
border-radius: 20px;
|
||||||
|
background: linear-gradient(45deg, rgba(138, 43, 226, 0.8), rgba(75, 0, 130, 0.8));
|
||||||
|
filter: blur(15px);
|
||||||
|
z-index: -1;
|
||||||
|
animation: glow 3s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
.popup-row {
|
justify-content: center;
|
||||||
margin-bottom: 20px;
|
|
||||||
height: 20vh;
|
|
||||||
width: 80vw;
|
|
||||||
padding: 1vh 1vw;
|
|
||||||
margin: 1vh 2vw;
|
|
||||||
background-color: #001529;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: white;
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-row {
|
|
||||||
margin: 1vh 2vw;
|
@keyframes glow {
|
||||||
width: 80vw;
|
0% {
|
||||||
height: 70vh;
|
opacity: 0.7;
|
||||||
background-color: #001529;
|
transform: scale(1);
|
||||||
border-radius: 8px;
|
}
|
||||||
}
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
} */
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -35,8 +35,7 @@
|
|||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<div class="table-part">
|
<div class="table-part">
|
||||||
<el-table v-loading="loading" :data="tableData"
|
<el-table v-loading="loading" :data="tableData" class="user-table" border max-height="700px">
|
||||||
class="user-table" border max-height="700px">
|
|
||||||
<el-table-column v-if="getColumnVisibility('selection')" type="selection"
|
<el-table-column v-if="getColumnVisibility('selection')" type="selection"
|
||||||
min-width="55"></el-table-column>
|
min-width="55"></el-table-column>
|
||||||
<el-table-column v-if="getColumnVisibility('id')" prop="id" label="序号"
|
<el-table-column v-if="getColumnVisibility('id')" prop="id" label="序号"
|
||||||
@@ -45,11 +44,21 @@
|
|||||||
min-width="180"></el-table-column>
|
min-width="180"></el-table-column>
|
||||||
<el-table-column v-if="getColumnVisibility('email')" prop="email" label="邮箱"
|
<el-table-column v-if="getColumnVisibility('email')" prop="email" label="邮箱"
|
||||||
min-width="200"></el-table-column>
|
min-width="200"></el-table-column>
|
||||||
<el-table-column v-if="getColumnVisibility('actions')" label="操作" min-width="200" align="center">
|
<el-table-column v-if="getColumnVisibility('actions')" label="操作" min-width="200"
|
||||||
|
align="center">
|
||||||
|
<!-- 用户编辑限制 -->
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button type="text" size="small"
|
<el-tooltip v-if="scope.row.isLocked" content="此用户无法被编辑或删除">
|
||||||
@click="showEditUserDialog(scope.row)">编辑</el-button>
|
<el-button type="text" size="small" disabled>
|
||||||
<el-button type="text" size="small" @click="confirmDelete(scope.row)">删除</el-button>
|
编辑
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-button v-else type="text" size="small" @click="showEditUserDialog(scope.row)">
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button type="text" size="small" :disabled="scope.row.isLocked"
|
||||||
|
@click="confirmDelete(scope.row)">删除
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -138,9 +147,10 @@ const refreshTable = async () => {
|
|||||||
try {
|
try {
|
||||||
const users = await apiInstance.getAllUsers(); // 调用getAllUsers方法获取用户
|
const users = await apiInstance.getAllUsers(); // 调用getAllUsers方法获取用户
|
||||||
tableData.value = users.map((user: any, index: number) => ({
|
tableData.value = users.map((user: any, index: number) => ({
|
||||||
id: index + 1, // 增加id索引
|
id: index + 1,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
email: user.email
|
email: user.email,
|
||||||
|
isLocked: ['csmixc','admin'].includes(user.username)
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('刷新用户列表失败');
|
ElMessage.error('刷新用户列表失败');
|
||||||
@@ -267,7 +277,7 @@ const handleDeleteSelected = () => {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-table{
|
.user-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|||||||
@@ -13,16 +13,20 @@ import '@kjgl77/datav-vue3/dist/style.css';
|
|||||||
import mitt from 'mitt';
|
import mitt from 'mitt';
|
||||||
import Antd from 'ant-design-vue';
|
import Antd from 'ant-design-vue';
|
||||||
import 'ant-design-vue/dist/reset.css';
|
import 'ant-design-vue/dist/reset.css';
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
const globalWebSocket = useGlobalWebSocket();
|
const globalWebSocket = useGlobalWebSocket();
|
||||||
|
const pinia = createPinia();
|
||||||
|
|
||||||
app.provide('globalWebSocket', globalWebSocket);
|
app.provide('globalWebSocket', globalWebSocket);
|
||||||
// app.provide('axios', axiosInstance);
|
// app.provide('axios', axiosInstance);
|
||||||
app.use(ElementPlus, { locale: zhCn });
|
app.use(ElementPlus, { locale: zhCn });
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(DataVVue3);
|
app.use(DataVVue3);
|
||||||
app.use(Antd);
|
app.use(Antd);
|
||||||
|
app.use(pinia)
|
||||||
|
|
||||||
// 导航守卫,检查登录状态
|
// 导航守卫,检查登录状态
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
|
|||||||
59
src/stores/globalTimerStore.ts
Normal file
59
src/stores/globalTimerStore.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import {ref} from 'vue'
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
// 定义回调类型
|
||||||
|
type TimerCallback = () => void;
|
||||||
|
|
||||||
|
export const useGlobalTimerStore = defineStore('globalTimer', () => {
|
||||||
|
const intervalId = ref<number | null>(null); // 定时器 ID
|
||||||
|
const refreshIntervalMs = ref(300000); // 默认刷新间隔(5 分钟)
|
||||||
|
const registeredCallbacks = ref<TimerCallback[]>([]); // 注册的回调函数列表
|
||||||
|
|
||||||
|
// 注册回调
|
||||||
|
const registerCallback = (callback: TimerCallback) => {
|
||||||
|
if (typeof callback === 'function' && !registeredCallbacks.value.includes(callback)) {
|
||||||
|
registeredCallbacks.value.push(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 注销回调
|
||||||
|
const unregisterCallback = (callback: TimerCallback) => {
|
||||||
|
const index = registeredCallbacks.value.indexOf(callback);
|
||||||
|
if (index !== -1) {
|
||||||
|
registeredCallbacks.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 启动定时器
|
||||||
|
const startTimer = () => {
|
||||||
|
if (!intervalId.value) {
|
||||||
|
intervalId.value = window.setInterval(() => {
|
||||||
|
registeredCallbacks.value.forEach((callback) => callback());
|
||||||
|
}, refreshIntervalMs.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 停止定时器
|
||||||
|
const stopTimer = () => {
|
||||||
|
if (intervalId.value) {
|
||||||
|
clearInterval(intervalId.value);
|
||||||
|
intervalId.value = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置定时器间隔
|
||||||
|
const setRefreshInterval = (interval: number) => {
|
||||||
|
refreshIntervalMs.value = interval;
|
||||||
|
stopTimer();
|
||||||
|
startTimer();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
refreshIntervalMs,
|
||||||
|
registerCallback,
|
||||||
|
unregisterCallback,
|
||||||
|
startTimer,
|
||||||
|
stopTimer,
|
||||||
|
setRefreshInterval,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -19,10 +19,7 @@ class BoxApi {
|
|||||||
private readonly apiAllCameras: string = "/camera/cameras/get_all";
|
private readonly apiAllCameras: string = "/camera/cameras/get_all";
|
||||||
private readonly superCamera: string = "/camera/cameras";
|
private readonly superCamera: string = "/camera/cameras";
|
||||||
private readonly superRule: string = "/rules";
|
private readonly superRule: string = "/rules";
|
||||||
private readonly getEventByIdUrl: string = "/event/events/retrieves";
|
private readonly superEvents: string = "/event/events";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private readonly loginConfig: object = {
|
private readonly loginConfig: object = {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -85,7 +82,7 @@ class BoxApi {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await this.axios.post(this.apiLogin, loginData, this.loginConfig)
|
const res = await this.axios.post(this.apiLogin, loginData, this.loginConfig)
|
||||||
console.log(res)
|
// console.log(res)
|
||||||
if (res.data.err.ec === 0) {
|
if (res.data.err.ec === 0) {
|
||||||
this.token = res.data.ret.token;
|
this.token = res.data.ret.token;
|
||||||
await this.updateCodemap(this.token);
|
await this.updateCodemap(this.token);
|
||||||
@@ -253,7 +250,7 @@ class BoxApi {
|
|||||||
public async updateCamera(token: string | null = null, cameraId: number, jsonData: CameraData): Promise<any> {
|
public async updateCamera(token: string | null = null, cameraId: number, jsonData: CameraData): Promise<any> {
|
||||||
const url = `${this.superCamera}/${cameraId}`;
|
const url = `${this.superCamera}/${cameraId}`;
|
||||||
// const { rules, ...cameraData } = jsonData;
|
// const { rules, ...cameraData } = jsonData;
|
||||||
console.log("接口接收的摄像设置>>>>>>>>>>>>>>", jsonData);
|
// console.log("接口接收的摄像设置>>>>>>>>>>>>>>", jsonData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newCamera = {
|
const newCamera = {
|
||||||
@@ -300,6 +297,26 @@ class BoxApi {
|
|||||||
// throw error;
|
// throw error;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
public async delEvents(token: string | null = null, eventIds: number[]): Promise<{ id: number, success: boolean, message?: string }[]> {
|
||||||
|
const results: { id: number, success: boolean, message?: string }[] = [];
|
||||||
|
|
||||||
|
for (const eventId of eventIds) {
|
||||||
|
const url = `${this.superEvents}/${eventId}`;
|
||||||
|
try {
|
||||||
|
await this.axios.delete(url, this._authHeader(token));
|
||||||
|
results.push({ id: eventId, success: true });
|
||||||
|
} catch (error: any) {
|
||||||
|
results.push({
|
||||||
|
id: eventId,
|
||||||
|
success: false,
|
||||||
|
message: error.response?.data?.err?.dm || error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public async updateRule(token: string | null = null, rules: RuleData[]): Promise<any[]> {
|
public async updateRule(token: string | null = null, rules: RuleData[]): Promise<any[]> {
|
||||||
@@ -312,7 +329,7 @@ class BoxApi {
|
|||||||
...rule,
|
...rule,
|
||||||
schedule: rule.schedule || {},
|
schedule: rule.schedule || {},
|
||||||
};
|
};
|
||||||
console.log("接口接收的规则设置>>>>>>>>>>>>>>", cleanedRule);
|
// console.log("接口接收的规则设置>>>>>>>>>>>>>>", cleanedRule);
|
||||||
const res = await this.axios.patch(url, cleanedRule, this._authHeader(token));
|
const res = await this.axios.patch(url, cleanedRule, this._authHeader(token));
|
||||||
if (res.data.err.ec === 0) {
|
if (res.data.err.ec === 0) {
|
||||||
results.push({ id: rule.id, success: true, data: res.data.ret });
|
results.push({ id: rule.id, success: true, data: res.data.ret });
|
||||||
@@ -476,23 +493,25 @@ class BoxApi {
|
|||||||
|
|
||||||
public async getEventById(id: number, token: string | null = null): Promise<any> {
|
public async getEventById(id: number, token: string | null = null): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const url = `${this.getEventByIdUrl}`;
|
const url = `${this.superEvents}/retrieves`;
|
||||||
const params = { id };
|
const params = { id };
|
||||||
const res = await this.axios.get(url, {
|
const res = await this.axios.get(url, {
|
||||||
...this._authHeader(token),
|
...this._authHeader(token),
|
||||||
params: params
|
params: params
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.data.err.ec === 0) {
|
if (res.data.err.ec === 0 && res.data.ret.objects.length > 0) {
|
||||||
// return res.data.ret.objects[0];
|
|
||||||
return res.data.ret.objects[0];
|
return res.data.ret.objects[0];
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.data.err.dm);
|
throw new Error(res.data.err.dm || '未获取到有效数据');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(`getEventById error for ID ${id}:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// public async getOneEvent(token: string | null = null): Promise<any> {
|
// public async getOneEvent(token: string | null = null): Promise<any> {
|
||||||
// try {
|
// try {
|
||||||
// return await this.getEvents(1, 0, token);
|
// return await this.getEvents(1, 0, token);
|
||||||
|
|||||||
@@ -1,12 +1,53 @@
|
|||||||
import { ref } from 'vue';
|
import { ref, reactive } from 'vue';
|
||||||
import { ElMessage, ElNotification } from 'element-plus';
|
import { ElMessage, ElNotification } from 'element-plus';
|
||||||
import eventBus from '@/utils/eventBus';
|
import eventBus from '@/utils/eventBus';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { BoxApi } from '@/utils/boxApi';
|
import { BoxApi } from '@/utils/boxApi';
|
||||||
const apiInstance = new BoxApi();
|
const websocket = ref<WebSocket | null>(null); // WebSocket 实例
|
||||||
|
const isWebSocketConnected = ref(false); // WebSocket 连接状态
|
||||||
|
let heartbeatInterval: number | null = null; // 心跳定时器
|
||||||
|
const formatDateTime = (isoString: string): string => dayjs(isoString).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
||||||
|
const apiInstance = new BoxApi();
|
||||||
const algorithmMap = ref(new Map());
|
const algorithmMap = ref(new Map());
|
||||||
|
|
||||||
|
const maxVisibleNotifications = 2; // 最多同时显示的通知数量
|
||||||
|
const visibleNotifications: Map<string, any> = new Map(); // 当前显示的通知
|
||||||
|
const notificationQueue: any[] = []; // 等待显示的通知队列
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
webkitAudioContext?: typeof AudioContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// let notificationSoundParams = { volume: 0.5, frequency: 440 };
|
||||||
|
// const setNotificationSoundParams = (params: { volume: number; frequency: number }) => {
|
||||||
|
// notificationSoundParams = params;
|
||||||
|
// };
|
||||||
|
|
||||||
|
const notificationSoundParams = reactive({
|
||||||
|
volume: 0.5,
|
||||||
|
frequency: 440,
|
||||||
|
isSoundEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setNotificationSoundParams = (params: { volume?: number; frequency?: number; isSoundEnabled?: boolean }) => {
|
||||||
|
Object.assign(notificationSoundParams, params);
|
||||||
|
|
||||||
|
// 更新 localStorage
|
||||||
|
if (params.volume !== undefined) localStorage.setItem('volume', String(notificationSoundParams.volume));
|
||||||
|
if (params.frequency !== undefined) localStorage.setItem('frequency', String(notificationSoundParams.frequency));
|
||||||
|
if (params.isSoundEnabled !== undefined) localStorage.setItem('isSoundEnabled', String(notificationSoundParams.isSoundEnabled));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化:从 localStorage 加载参数
|
||||||
|
const initializeNotificationSoundParams = () => {
|
||||||
|
notificationSoundParams.volume = parseFloat(localStorage.getItem('volume') || '0.5');
|
||||||
|
notificationSoundParams.frequency = parseFloat(localStorage.getItem('frequency') || '440');
|
||||||
|
notificationSoundParams.isSoundEnabled = localStorage.getItem('isSoundEnabled') === 'true';
|
||||||
|
};
|
||||||
|
initializeNotificationSoundParams();
|
||||||
|
|
||||||
// 加载算法映射表
|
// 加载算法映射表
|
||||||
const loadAlgorithms = async () => {
|
const loadAlgorithms = async () => {
|
||||||
const token = localStorage.getItem('alertToken');
|
const token = localStorage.getItem('alertToken');
|
||||||
@@ -20,17 +61,14 @@ const loadAlgorithms = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const websocket = ref<WebSocket | null>(null); // WebSocket 实例
|
|
||||||
const isWebSocketConnected = ref(false); // WebSocket 连接状态
|
|
||||||
let heartbeatInterval: number | null = null; // 心跳定时器
|
|
||||||
const formatDateTime = (isoString: string): string => dayjs(isoString).format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
// 连接 WebSocket
|
// 连接 WebSocket
|
||||||
const connectWebSocket = () => {
|
const connectWebSocket = async () => {
|
||||||
const rememberedAddress = localStorage.getItem('rememberedAddress'); // 获取存储的主机地址
|
const rememberedAddress = localStorage.getItem('rememberedAddress'); // 获取存储的主机地址
|
||||||
if (rememberedAddress) {
|
if (rememberedAddress) {
|
||||||
websocket.value = new WebSocket(`ws://${rememberedAddress}:8080/ws/event`);
|
// websocket.value = new WebSocket(`ws://${rememberedAddress}:8080/ws/event`);
|
||||||
// websocket.value = new WebSocket(`ws://192.168.28.33:8080/ws/event`);
|
websocket.value = new WebSocket(`ws://172.19.7.9:8080/ws/event`);
|
||||||
loadAlgorithms();
|
await loadAlgorithms();
|
||||||
websocket.value.onopen = () => {
|
websocket.value.onopen = () => {
|
||||||
ElMessage.success('全局 WebSocket 连接成功');
|
ElMessage.success('全局 WebSocket 连接成功');
|
||||||
isWebSocketConnected.value = true;
|
isWebSocketConnected.value = true;
|
||||||
@@ -38,6 +76,17 @@ const connectWebSocket = () => {
|
|||||||
};
|
};
|
||||||
websocket.value.onmessage = (event) => {
|
websocket.value.onmessage = (event) => {
|
||||||
const data = JSON.parse(event.data); // 解析收到的数据
|
const data = JSON.parse(event.data); // 解析收到的数据
|
||||||
|
|
||||||
|
const isInteractive = localStorage.getItem('isInteractivePopupEnabled') === 'true';
|
||||||
|
const isResponsive = localStorage.getItem('isResponsivePopupEnabled') === 'true';
|
||||||
|
if (!isInteractive && !isResponsive) {
|
||||||
|
return; // 弹窗关闭,直接返回
|
||||||
|
}
|
||||||
|
|
||||||
|
let num = 0;
|
||||||
|
num += 1;
|
||||||
|
// console.log(`${new Date().toISOString()}收到新的第${num}弹窗信息:`, data);
|
||||||
|
playNotificationSound();
|
||||||
handlePopupNotification(data); // 根据模式处理弹窗通知
|
handlePopupNotification(data); // 根据模式处理弹窗通知
|
||||||
};
|
};
|
||||||
websocket.value.onclose = handleClose; // 处理连接关闭
|
websocket.value.onclose = handleClose; // 处理连接关闭
|
||||||
@@ -47,6 +96,33 @@ const connectWebSocket = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const playNotificationSound = () => {
|
||||||
|
// if (!isWebSocketConnected.value) return;
|
||||||
|
if (!notificationSoundParams.isSoundEnabled) return;
|
||||||
|
try {
|
||||||
|
const audioContext = new (window.AudioContext || window.webkitAudioContext!)();
|
||||||
|
const oscillator = audioContext.createOscillator();
|
||||||
|
const gainNode = audioContext.createGain();
|
||||||
|
|
||||||
|
oscillator.type = 'triangle';
|
||||||
|
oscillator.frequency.setValueAtTime(notificationSoundParams.frequency, audioContext.currentTime);
|
||||||
|
gainNode.gain.setValueAtTime(notificationSoundParams.volume, audioContext.currentTime);
|
||||||
|
|
||||||
|
oscillator.connect(gainNode);
|
||||||
|
gainNode.connect(audioContext.destination);
|
||||||
|
|
||||||
|
oscillator.start();
|
||||||
|
setTimeout(() => {
|
||||||
|
oscillator.stop();
|
||||||
|
audioContext.close();
|
||||||
|
}, 350); // 持续播放 200 毫秒
|
||||||
|
} catch (error) {
|
||||||
|
console.error('播放提示音时出错:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 关闭 WebSocket
|
// 关闭 WebSocket
|
||||||
const closeWebSocket = () => {
|
const closeWebSocket = () => {
|
||||||
if (websocket.value) {
|
if (websocket.value) {
|
||||||
@@ -90,30 +166,92 @@ const handleError = () => {
|
|||||||
stopHeartbeat(); // 停止心跳
|
stopHeartbeat(); // 停止心跳
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 显示通知
|
||||||
|
const showNotification = (notification: any) => {
|
||||||
|
const id = `${Date.now()}-${Math.random()}`; // 唯一 ID
|
||||||
|
notification.id = id;
|
||||||
|
|
||||||
|
if (visibleNotifications.size < maxVisibleNotifications) {
|
||||||
|
// 如果未达到最大显示数量,直接显示
|
||||||
|
const elNotification = ElNotification({
|
||||||
|
...notification,
|
||||||
|
onClose: () => {
|
||||||
|
// 从显示列表中移除
|
||||||
|
visibleNotifications.delete(id);
|
||||||
|
|
||||||
|
// 显示下一条队列中的通知
|
||||||
|
if (notificationQueue.length > 0) {
|
||||||
|
showNotification(notificationQueue.shift());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用用户定义的关闭回调(如果有)
|
||||||
|
notification.onClose?.();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
visibleNotifications.set(id, elNotification);
|
||||||
|
} else {
|
||||||
|
// 如果达到最大显示数量,存入队列
|
||||||
|
notificationQueue.push(notification);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadNextNotification = () => {
|
||||||
|
// 清空当前所有显示的通知
|
||||||
|
visibleNotifications.forEach((notif) => notif.close());
|
||||||
|
visibleNotifications.clear();
|
||||||
|
|
||||||
|
// 从队列中加载下一条通知
|
||||||
|
if (notificationQueue.length > 0) {
|
||||||
|
const nextNotification = notificationQueue.shift();
|
||||||
|
if (nextNotification) {
|
||||||
|
showNotification(nextNotification);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// console.log('通知队列为空');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 手动清空所有通知
|
||||||
|
const clearAllNotifications = () => {
|
||||||
|
visibleNotifications.forEach((notif) => notif.close());
|
||||||
|
visibleNotifications.clear();
|
||||||
|
notificationQueue.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const seenNotificationIds = new Set();
|
||||||
// 根据弹窗模式处理通知
|
// 根据弹窗模式处理通知
|
||||||
const handlePopupNotification = (data: any) => {
|
const handlePopupNotification = (data: any) => {
|
||||||
const isInteractive = localStorage.getItem('isInteractivePopupEnabled') === 'true'; // 是否为交互式弹窗
|
const isInteractive = localStorage.getItem('isInteractivePopupEnabled') === 'true'; // 是否为交互式弹窗
|
||||||
const isResponsive = localStorage.getItem('isResponsivePopupEnabled') === 'true'; // 是否为响应式弹窗
|
const isResponsive = localStorage.getItem('isResponsivePopupEnabled') === 'true'; // 是否为响应式弹窗
|
||||||
|
|
||||||
|
if (seenNotificationIds.has(data.id)) return;
|
||||||
|
seenNotificationIds.add(data.id);
|
||||||
|
setTimeout(() => seenNotificationIds.delete(data.id), 30000);
|
||||||
|
|
||||||
|
|
||||||
if (isResponsive) {
|
if (isResponsive) {
|
||||||
// 响应式模式:直接显示对话框
|
// 响应式模式:直接显示对话框
|
||||||
|
// console.log('handlePopupNotification triggered:', data);
|
||||||
eventBus.emit('showDialog', data);
|
eventBus.emit('showDialog', data);
|
||||||
} else if (isInteractive) {
|
} else if (isInteractive) {
|
||||||
|
|
||||||
const formattedTime = formatDateTime(data.started_at);
|
const formattedTime = formatDateTime(data.started_at);
|
||||||
ElNotification({
|
showNotification({
|
||||||
title: '新告警',
|
title: '新告警',
|
||||||
message: `
|
message: `
|
||||||
<div style="max-height: 200px; overflow-y: auto;">
|
<div style="max-height: 200px; overflow-y: auto;">
|
||||||
<p><strong>告警编号:</strong>${data.id || '未知'}</p>
|
<p><strong>告警编号:</strong>${data.id || '未知'}</p>
|
||||||
<p><strong>告警点位:</strong>${data.camera?.name|| '未知'}</p>
|
<p><strong>告警点位:</strong>${data.camera?.name || '未知'}</p>
|
||||||
<p><strong>告警类型:</strong>${algorithmMap.value.get(data.types) || '未知'}</p>
|
<p><strong>告警类型:</strong>${algorithmMap.value.get(data.types) || '未知'}</p>
|
||||||
<p><strong>告警时间:</strong>${formattedTime || '未知'}</p>
|
<p><strong>告警时间:</strong>${formattedTime || '未知'}</p>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
dangerouslyUseHTMLString: true,
|
dangerouslyUseHTMLString: true,
|
||||||
duration: 5000,
|
duration: 10000,
|
||||||
customClass: 'custom-notification',
|
customClass: 'custom-notification',
|
||||||
|
position: 'bottom-right',
|
||||||
|
type: 'info',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
eventBus.emit('showDialog', data); // 点击通知触发对话框
|
eventBus.emit('showDialog', data); // 点击通知触发对话框
|
||||||
}
|
}
|
||||||
@@ -126,10 +264,22 @@ export interface GlobalWebSocket {
|
|||||||
connectWebSocket: () => void; // 连接 WebSocket
|
connectWebSocket: () => void; // 连接 WebSocket
|
||||||
closeWebSocket: () => void; // 关闭 WebSocket
|
closeWebSocket: () => void; // 关闭 WebSocket
|
||||||
isWebSocketConnected: typeof isWebSocketConnected; // WebSocket 连接状态
|
isWebSocketConnected: typeof isWebSocketConnected; // WebSocket 连接状态
|
||||||
|
loadNextNotification: () => void; // 加载下一条通知
|
||||||
|
clearAllNotifications: () => void; // 清空所有通知
|
||||||
|
setNotificationSoundParams: (params: { volume?: number; frequency?: number; isSoundEnabled?: boolean }) => void;
|
||||||
|
notificationSoundParams: {
|
||||||
|
volume: number;
|
||||||
|
frequency: number;
|
||||||
|
isSoundEnabled: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGlobalWebSocket = (): GlobalWebSocket => ({
|
export const useGlobalWebSocket = (): GlobalWebSocket => ({
|
||||||
connectWebSocket,
|
connectWebSocket,
|
||||||
closeWebSocket,
|
closeWebSocket,
|
||||||
isWebSocketConnected,
|
isWebSocketConnected,
|
||||||
|
loadNextNotification, // 导出加载下一条通知的方法
|
||||||
|
clearAllNotifications, // 导出清空所有通知的方法
|
||||||
|
notificationSoundParams,
|
||||||
|
setNotificationSoundParams,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user