初始化
This commit is contained in:
1
.env.development
Normal file
1
.env.development
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_APP_BASE_API=/api
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
|||||||
678
package-lock.json
generated
678
package-lock.json
generated
@@ -9,11 +9,16 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.4.4",
|
"@types/node": "^20.4.4",
|
||||||
|
"axios": "^1.18.1",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
"echarts-liquidfill": "^3.1.0",
|
"echarts-liquidfill": "^3.1.0",
|
||||||
|
"jsencrypt": "^3.5.4",
|
||||||
|
"naive-ui": "^2.44.1",
|
||||||
"ol": "^7.4.0",
|
"ol": "^7.4.0",
|
||||||
|
"pinia": "^3.0.4",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"vue": "^3.2.47"
|
"vue": "^3.2.47",
|
||||||
|
"vue-router": "^4.6.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^4.1.0",
|
"@vitejs/plugin-vue": "^4.1.0",
|
||||||
@@ -429,6 +434,30 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@css-render/plugin-bem": {
|
||||||
|
"version": "0.15.14",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@css-render/plugin-bem/-/plugin-bem-0.15.14.tgz",
|
||||||
|
"integrity": "sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"css-render": "~0.15.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@css-render/vue3-ssr": {
|
||||||
|
"version": "0.15.14",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@css-render/vue3-ssr/-/vue3-ssr-0.15.14.tgz",
|
||||||
|
"integrity": "sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/hash": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz",
|
||||||
|
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.18.20",
|
"version": "0.18.20",
|
||||||
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
||||||
@@ -852,6 +881,12 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@juggle/resize-observer": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@mapbox/jsonlint-lines-primitives": {
|
"node_modules/@mapbox/jsonlint-lines-primitives": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
|
||||||
@@ -914,6 +949,21 @@
|
|||||||
"node": ">= 8.0.0"
|
"node": ">= 8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lodash": {
|
||||||
|
"version": "4.17.24",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz",
|
||||||
|
"integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/lodash-es": {
|
||||||
|
"version": "4.17.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
||||||
|
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.19.43",
|
"version": "20.19.43",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.43.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.43.tgz",
|
||||||
@@ -1089,6 +1139,39 @@
|
|||||||
"@vue/shared": "3.5.38"
|
"@vue/shared": "3.5.38"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vue/devtools-api": {
|
||||||
|
"version": "7.7.10",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.10.tgz",
|
||||||
|
"integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-kit": "^7.7.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/devtools-kit": {
|
||||||
|
"version": "7.7.10",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz",
|
||||||
|
"integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-shared": "^7.7.10",
|
||||||
|
"birpc": "^2.3.0",
|
||||||
|
"hookable": "^5.5.3",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
|
"perfect-debounce": "^1.0.0",
|
||||||
|
"speakingurl": "^14.0.1",
|
||||||
|
"superjson": "^2.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/devtools-shared": {
|
||||||
|
"version": "7.7.10",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz",
|
||||||
|
"integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"rfdc": "^1.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vue/language-core": {
|
"node_modules/@vue/language-core": {
|
||||||
"version": "1.8.27",
|
"version": "1.8.27",
|
||||||
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-1.8.27.tgz",
|
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-1.8.27.tgz",
|
||||||
@@ -1165,6 +1248,42 @@
|
|||||||
"integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==",
|
"integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/agent-base": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/async-validator": {
|
||||||
|
"version": "4.2.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
|
||||||
|
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.18.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.18.1.tgz",
|
||||||
|
"integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.16.0",
|
||||||
|
"form-data": "^4.0.5",
|
||||||
|
"https-proxy-agent": "^5.0.1",
|
||||||
|
"proxy-from-env": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@@ -1185,6 +1304,15 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/birpc": {
|
||||||
|
"version": "2.9.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz",
|
||||||
|
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||||
@@ -1229,6 +1357,19 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-bind-apply-helpers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001799",
|
"version": "1.0.30001799",
|
||||||
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
|
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
|
||||||
@@ -1250,6 +1391,18 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/computeds": {
|
"node_modules/computeds": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/computeds/-/computeds-0.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/computeds/-/computeds-0.0.1.tgz",
|
||||||
@@ -1280,6 +1433,22 @@
|
|||||||
"url": "https://github.com/sponsors/mesqueeb"
|
"url": "https://github.com/sponsors/mesqueeb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-render": {
|
||||||
|
"version": "0.15.14",
|
||||||
|
"resolved": "https://registry.npmmirror.com/css-render/-/css-render-0.15.14.tgz",
|
||||||
|
"integrity": "sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/hash": "~0.8.0",
|
||||||
|
"csstype": "~3.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-render/node_modules/csstype": {
|
||||||
|
"version": "3.0.11",
|
||||||
|
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.0.11.tgz",
|
||||||
|
"integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/csscolorparser": {
|
"node_modules/csscolorparser": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmmirror.com/csscolorparser/-/csscolorparser-1.0.3.tgz",
|
"resolved": "https://registry.npmmirror.com/csscolorparser/-/csscolorparser-1.0.3.tgz",
|
||||||
@@ -1292,6 +1461,25 @@
|
|||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/date-fns-tz": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/date-fns-tz/-/date-fns-tz-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"date-fns": "^3.0.0 || ^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/de-indent": {
|
"node_modules/de-indent": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz",
|
||||||
@@ -1303,7 +1491,6 @@
|
|||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
|
||||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -1317,6 +1504,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dunder-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/earcut": {
|
"node_modules/earcut": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmmirror.com/earcut/-/earcut-2.2.4.tgz",
|
"resolved": "https://registry.npmmirror.com/earcut/-/earcut-2.2.4.tgz",
|
||||||
@@ -1375,6 +1585,51 @@
|
|||||||
"errno": "cli.js"
|
"errno": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-define-property": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-object-atoms": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-set-tostringtag": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.18.20",
|
"version": "0.18.20",
|
||||||
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.18.20.tgz",
|
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.18.20.tgz",
|
||||||
@@ -1429,6 +1684,48 @@
|
|||||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/evtd": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/evtd/-/evtd-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.16.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
||||||
|
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.4",
|
||||||
|
"mime-types": "^2.1.35"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -1444,6 +1741,15 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gensync": {
|
"node_modules/gensync": {
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
@@ -1473,6 +1779,55 @@
|
|||||||
"node": ">=10.19"
|
"node": ">=10.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-intrinsic": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.1.1",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-proto": "^1.0.1",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
|
"has-symbols": "^1.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dunder-proto": "^1.0.1",
|
||||||
|
"es-object-atoms": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gopd": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/graceful-fs": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
@@ -1481,6 +1836,33 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/has-symbols": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-tostringtag": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hash-sum": {
|
"node_modules/hash-sum": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz",
|
||||||
@@ -1488,6 +1870,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/he": {
|
"node_modules/he": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
|
||||||
@@ -1498,6 +1892,34 @@
|
|||||||
"he": "bin/he"
|
"he": "bin/he"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/highlight.js": {
|
||||||
|
"version": "11.11.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz",
|
||||||
|
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hookable": {
|
||||||
|
"version": "5.5.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
|
||||||
|
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/https-proxy-agent": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "6",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
@@ -1566,6 +1988,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/jsencrypt": {
|
||||||
|
"version": "3.5.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/jsencrypt/-/jsencrypt-3.5.4.tgz",
|
||||||
|
"integrity": "sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/jsesc": {
|
"node_modules/jsesc": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz",
|
||||||
@@ -1630,6 +2058,18 @@
|
|||||||
"source-map": "~0.6.0"
|
"source-map": "~0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.18.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz",
|
||||||
|
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash-es": {
|
||||||
|
"version": "4.18.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz",
|
||||||
|
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@@ -1669,6 +2109,15 @@
|
|||||||
"integrity": "sha512-VJ6nB8emkO9VODI0Fk+TQ/0zKBTqmf/Pkt8Xv0kHstoc0iXRajA00DAid4Kc3K5xeFIOoiZrVxijEzj0GLVO2w==",
|
"integrity": "sha512-VJ6nB8emkO9VODI0Fk+TQ/0zKBTqmf/Pkt8Xv0kHstoc0iXRajA00DAid4Kc3K5xeFIOoiZrVxijEzj0GLVO2w==",
|
||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/math-intrinsics": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mime": {
|
"node_modules/mime": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz",
|
"resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz",
|
||||||
@@ -1683,6 +2132,27 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "9.0.9",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz",
|
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz",
|
||||||
@@ -1708,11 +2178,16 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mitt": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/muggle-string": {
|
"node_modules/muggle-string": {
|
||||||
@@ -1722,6 +2197,38 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/naive-ui": {
|
||||||
|
"version": "2.44.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.44.1.tgz",
|
||||||
|
"integrity": "sha512-reo8Esw0p58liZwbUutC7meW24Xbn3EwNv91zReWKm2W4JPu+zfgJRn/F7aO0BFmvN+h2brA2M5lRvYqLq4kuA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@css-render/plugin-bem": "^0.15.14",
|
||||||
|
"@css-render/vue3-ssr": "^0.15.14",
|
||||||
|
"@types/lodash": "^4.17.20",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"async-validator": "^4.2.5",
|
||||||
|
"css-render": "^0.15.14",
|
||||||
|
"csstype": "^3.1.3",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
|
"date-fns-tz": "^3.2.0",
|
||||||
|
"evtd": "^0.2.4",
|
||||||
|
"highlight.js": "^11.8.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"seemly": "^0.3.10",
|
||||||
|
"treemate": "^0.3.11",
|
||||||
|
"vdirs": "^0.1.8",
|
||||||
|
"vooks": "^0.2.12",
|
||||||
|
"vueuc": "^0.4.65"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.15",
|
"version": "3.3.15",
|
||||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz",
|
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz",
|
||||||
@@ -1848,6 +2355,12 @@
|
|||||||
"pbf": "bin/pbf"
|
"pbf": "bin/pbf"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/perfect-debounce": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -1867,6 +2380,27 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pinia": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^7.7.7"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=4.5.0",
|
||||||
|
"vue": "^3.5.11"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.15",
|
"version": "8.5.15",
|
||||||
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz",
|
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz",
|
||||||
@@ -1916,6 +2450,15 @@
|
|||||||
"integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==",
|
"integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prr": {
|
"node_modules/prr": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz",
|
||||||
@@ -1960,6 +2503,12 @@
|
|||||||
"protocol-buffers-schema": "^3.3.1"
|
"protocol-buffers-schema": "^3.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rfdc": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "3.30.0",
|
"version": "3.30.0",
|
||||||
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-3.30.0.tgz",
|
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-3.30.0.tgz",
|
||||||
@@ -2002,6 +2551,12 @@
|
|||||||
"node": ">=11.0.0"
|
"node": ">=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/seemly": {
|
||||||
|
"version": "0.3.10",
|
||||||
|
"resolved": "https://registry.npmmirror.com/seemly/-/seemly-0.3.10.tgz",
|
||||||
|
"integrity": "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "6.3.1",
|
"version": "6.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
|
||||||
@@ -2060,6 +2615,60 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/speakingurl": {
|
||||||
|
"version": "14.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz",
|
||||||
|
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/superjson": {
|
||||||
|
"version": "2.2.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz",
|
||||||
|
"integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"copy-anything": "^4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/superjson/node_modules/copy-anything": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-what": "^5.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/mesqueeb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/superjson/node_modules/is-what": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/mesqueeb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/treemate": {
|
||||||
|
"version": "0.3.11",
|
||||||
|
"resolved": "https://registry.npmmirror.com/treemate/-/treemate-0.3.11.tgz",
|
||||||
|
"integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"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",
|
||||||
@@ -2117,6 +2726,18 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vdirs": {
|
||||||
|
"version": "0.1.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vdirs/-/vdirs-0.1.8.tgz",
|
||||||
|
"integrity": "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"evtd": "^0.2.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "4.5.14",
|
"version": "4.5.14",
|
||||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.5.14.tgz",
|
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.5.14.tgz",
|
||||||
@@ -2173,6 +2794,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vooks": {
|
||||||
|
"version": "0.2.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vooks/-/vooks-0.2.12.tgz",
|
||||||
|
"integrity": "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"evtd": "^0.2.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue": {
|
"node_modules/vue": {
|
||||||
"version": "3.5.38",
|
"version": "3.5.38",
|
||||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.38.tgz",
|
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.38.tgz",
|
||||||
@@ -2194,6 +2827,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-router": {
|
||||||
|
"version": "4.6.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz",
|
||||||
|
"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^6.6.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-router/node_modules/@vue/devtools-api": {
|
||||||
|
"version": "6.6.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
|
||||||
|
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/vue-template-compiler": {
|
"node_modules/vue-template-compiler": {
|
||||||
"version": "2.7.16",
|
"version": "2.7.16",
|
||||||
"resolved": "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
|
"resolved": "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
|
||||||
@@ -2236,6 +2890,24 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vueuc": {
|
||||||
|
"version": "0.4.65",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vueuc/-/vueuc-0.4.65.tgz",
|
||||||
|
"integrity": "sha512-lXuMl+8gsBmruudfxnMF9HW4be8rFziylXFu1VHVNbLVhRTXXV4njvpRuJapD/8q+oFEMSfQMH16E/85VoWRyQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@css-render/vue3-ssr": "^0.15.10",
|
||||||
|
"@juggle/resize-observer": "^3.3.1",
|
||||||
|
"css-render": "^0.15.10",
|
||||||
|
"evtd": "^0.2.4",
|
||||||
|
"seemly": "^0.3.6",
|
||||||
|
"vdirs": "^0.1.4",
|
||||||
|
"vooks": "^0.2.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/web-worker": {
|
"node_modules/web-worker": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmmirror.com/web-worker/-/web-worker-1.5.0.tgz",
|
"resolved": "https://registry.npmmirror.com/web-worker/-/web-worker-1.5.0.tgz",
|
||||||
|
|||||||
11
package.json
11
package.json
@@ -11,18 +11,23 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.4.4",
|
"@types/node": "^20.4.4",
|
||||||
|
"axios": "^1.18.1",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
"echarts-liquidfill": "^3.1.0",
|
"echarts-liquidfill": "^3.1.0",
|
||||||
|
"jsencrypt": "^3.5.4",
|
||||||
|
"naive-ui": "^2.44.1",
|
||||||
"ol": "^7.4.0",
|
"ol": "^7.4.0",
|
||||||
|
"pinia": "^3.0.4",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"vue": "^3.2.47"
|
"vue": "^3.2.47",
|
||||||
|
"vue-router": "^4.6.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^4.1.0",
|
"@vitejs/plugin-vue": "^4.1.0",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^1.3.3",
|
||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3",
|
||||||
"vite": "^4.2.0",
|
"vite": "^4.2.0",
|
||||||
"vue-tsc": "^1.2.0",
|
"vue-tsc": "^1.2.0"
|
||||||
"@vitejs/plugin-vue-jsx": "^1.3.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
public/room.png
Normal file
BIN
public/room.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 999 B |
7
public/room.yaml
Normal file
7
public/room.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
image: room.pgm
|
||||||
|
mode: trinary
|
||||||
|
resolution: 0.05
|
||||||
|
origin: [-1.26, -1.77, 0]
|
||||||
|
negate: 0
|
||||||
|
occupied_thresh: 0.65
|
||||||
|
free_thresh: 0.25
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
||||||
import dashboard1 from "@/views/dashboard1/index.vue";
|
|
||||||
|
|
||||||
const DESIGN_WIDTH = 1920;
|
const DESIGN_WIDTH = 1920;
|
||||||
const DESIGN_HEIGHT = 1080;
|
const DESIGN_HEIGHT = 1080;
|
||||||
@@ -44,7 +43,7 @@ onBeforeUnmount(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="screen-adapter">
|
<div class="screen-adapter">
|
||||||
<div class="screen-adapter__inner" :style="adapterStyle">
|
<div class="screen-adapter__inner" :style="adapterStyle">
|
||||||
<Component :is="dashboard1" />
|
<RouterView />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
75
src/api/alarm.ts
Normal file
75
src/api/alarm.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
export interface RosInspectionRecordLineImageQueryDTO {
|
||||||
|
recognizeType?: number | string;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
keyword?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderItem {
|
||||||
|
property: string;
|
||||||
|
tableIndex: number;
|
||||||
|
asc?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageParam<T> {
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
orderList?: OrderItem[];
|
||||||
|
query?: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RosInspectionRecordLineImagePageQuery =
|
||||||
|
Partial<RosInspectionRecordLineImage> & {
|
||||||
|
keyword?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface RosInspectionRecordLineImageCar {
|
||||||
|
id?: string;
|
||||||
|
deviceKey?: string;
|
||||||
|
deviceNo?: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RosInspectionRecordLineImage {
|
||||||
|
recordId: string;
|
||||||
|
carId?: string;
|
||||||
|
car?: RosInspectionRecordLineImageCar;
|
||||||
|
recordActionId: string;
|
||||||
|
taskId?: string;
|
||||||
|
recognizeType?: number;
|
||||||
|
imageUrl?: string;
|
||||||
|
captureTime?: string;
|
||||||
|
poseX?: number;
|
||||||
|
poseY?: number;
|
||||||
|
poseYaw?: number;
|
||||||
|
recognitionStatus?: number;
|
||||||
|
prompt?: string;
|
||||||
|
recognitionResult?: string;
|
||||||
|
warning?: boolean;
|
||||||
|
warningValue?: string;
|
||||||
|
failReason?: string;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
createTime?: string;
|
||||||
|
updateTime?: string;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询近期告警列表
|
||||||
|
export function getRecentAlarmList(data: RosInspectionRecordLineImageQueryDTO) {
|
||||||
|
return request({
|
||||||
|
url: "/admin/rosInspectionRecordLineImage/warnList",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询告警列表
|
||||||
|
export function getAlarmPage(data: PageParam<RosInspectionRecordLineImagePageQuery>) {
|
||||||
|
return request({
|
||||||
|
url: "/admin/rosInspectionRecordLineImage/list",
|
||||||
|
method: "POST",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
73
src/api/rosCar.ts
Normal file
73
src/api/rosCar.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
// 小车分页列表
|
||||||
|
export function getRosCarList(data: unknown) {
|
||||||
|
return request({
|
||||||
|
url: "/admin/rosCar/list",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存巡检任务模板
|
||||||
|
export function saveRosInspectionTask(data: unknown) {
|
||||||
|
return request({
|
||||||
|
url: "/admin/rosInspectionTask/save",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 巡检任务模板详情
|
||||||
|
export function getRosInspectionTaskDetail(cardId: string) {
|
||||||
|
return request({
|
||||||
|
url: "/admin/rosInspectionTask/detail",
|
||||||
|
params: { cardId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询小车当前巡检状态
|
||||||
|
export function getCurrentRosInspection(carId: string) {
|
||||||
|
return request({
|
||||||
|
url: "/admin/rosCar/currentInspection",
|
||||||
|
params: { carId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下发巡检任务
|
||||||
|
export function dispatchRosInspectionTask(taskId: string) {
|
||||||
|
return request({
|
||||||
|
url: "/admin/rosInspectionTask/dispatch",
|
||||||
|
params: { taskId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止已下发的巡检任务
|
||||||
|
export function stopDispatchRosInspectionTask(taskId: string) {
|
||||||
|
return request({
|
||||||
|
url: "/admin/rosInspectionTask/stopDispatch",
|
||||||
|
params: { taskId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传文件并返回后端保存后的访问地址
|
||||||
|
export function uploadFile(file: File) {
|
||||||
|
const data = new FormData();
|
||||||
|
|
||||||
|
data.append("file", file);
|
||||||
|
|
||||||
|
return request({
|
||||||
|
url: "/admin/file/upload",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新小车地图文件地址
|
||||||
|
export function updateRosCarMapUrls(data: {
|
||||||
|
id: string;
|
||||||
|
mapPgmUrl?: string;
|
||||||
|
mapYamlUrl?: string;
|
||||||
|
}) {
|
||||||
|
return request({
|
||||||
|
url: "/admin/rosCar/updateMapUrls",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
52
src/api/user.js
Normal file
52
src/api/user.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import request, { BASE_URL } from '@/utils/request'
|
||||||
|
export function getPublicKey() {
|
||||||
|
return request({
|
||||||
|
url: 'admin/account/publicKey'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function login(data) {
|
||||||
|
return request({
|
||||||
|
url: 'admin/account/login',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function captcha() {
|
||||||
|
return BASE_URL + '/admin/account/captcha'
|
||||||
|
}
|
||||||
|
export function getInfo() {
|
||||||
|
return request({
|
||||||
|
url: 'admin/account/info',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: 'admin/account/update',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updatePwd(data) {
|
||||||
|
return request({
|
||||||
|
url: 'admin/account/updatePwd',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送重置密码验证码
|
||||||
|
export function getVerifyCode(data) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/sysUser/getVerifyCode',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//忘记密码
|
||||||
|
export function resetUserPassword(data) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/sysUser/resetUserPassword',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,11 +4,20 @@ import App from "./App.vue";
|
|||||||
import Chart from "./components/chart/index.vue";
|
import Chart from "./components/chart/index.vue";
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import echartsConfig from "./config/echarts.config";
|
import echartsConfig from "./config/echarts.config";
|
||||||
|
import { createDiscreteApi } from "naive-ui";
|
||||||
|
import { createPinia } from "pinia";
|
||||||
|
import router from "./router";
|
||||||
|
|
||||||
echarts.registerTheme("t-theme", echartsConfig);
|
echarts.registerTheme("t-theme", echartsConfig);
|
||||||
|
|
||||||
|
const { message, dialog } = createDiscreteApi(["message", "dialog"]);
|
||||||
|
window.$message = message;
|
||||||
|
window.$modal = dialog;
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.use(createPinia());
|
||||||
|
app.use(router);
|
||||||
app.component("TChart", Chart);
|
app.component("TChart", Chart);
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|||||||
97
src/router/index.ts
Normal file
97
src/router/index.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { getToken } from "@/utils/auth";
|
||||||
|
import {
|
||||||
|
createRouter,
|
||||||
|
createWebHashHistory,
|
||||||
|
type RouteRecordRaw,
|
||||||
|
} from "vue-router";
|
||||||
|
|
||||||
|
const dashboardDefaultPath = "/dashboard/integrated-center";
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
redirect: () => (getToken() ? dashboardDefaultPath : "/login"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/dashboard",
|
||||||
|
component: () => import("@/views/dashboard1/index.vue"),
|
||||||
|
redirect: dashboardDefaultPath,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "integrated-center",
|
||||||
|
name: "IntegratedCenter",
|
||||||
|
component: () =>
|
||||||
|
import("@/views/dashboard1/pages/IntegratedCenterPage.vue"),
|
||||||
|
meta: { title: "监管中心", tabId: 1, },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "equipment-management",
|
||||||
|
name: "EquipmentManagement",
|
||||||
|
component: () =>
|
||||||
|
import("@/views/dashboard1/pages/EquipmentManagementPage.vue"),
|
||||||
|
meta: { title: "设备管理", tabId: 2, },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "video-center",
|
||||||
|
name: "VideoCenter",
|
||||||
|
component: () => import("@/views/dashboard1/pages/VideoCenterPage.vue"),
|
||||||
|
meta: { title: "视频中心", tabId: 3, },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "alarm-management",
|
||||||
|
name: "AlarmManagement",
|
||||||
|
component: () =>
|
||||||
|
import("@/views/dashboard1/pages/AlarmManagementPage.vue"),
|
||||||
|
meta: { title: "告警管理", tabId: 4, },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "patrol-plan",
|
||||||
|
name: "PatrolPlan",
|
||||||
|
component: () => import("@/views/dashboard1/pages/PatrolPlanPage.vue"),
|
||||||
|
meta: { title: "巡检记录", tabId: 5, },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "data-report",
|
||||||
|
name: "DataReport",
|
||||||
|
component: () => import("@/views/dashboard1/pages/DataReportPage.vue"),
|
||||||
|
meta: { title: "统计分析", tabId: 6, },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "Login",
|
||||||
|
component: () => import("@/views/login/index.vue"),
|
||||||
|
meta: { title: "登录" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/:pathMatch(.*)*",
|
||||||
|
redirect: dashboardDefaultPath,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
|
||||||
|
const whiteList = ["/login"];
|
||||||
|
|
||||||
|
router.beforeEach((to) => {
|
||||||
|
const hasToken = Boolean(getToken());
|
||||||
|
|
||||||
|
if (!hasToken && !whiteList.includes(to.path)) {
|
||||||
|
return {
|
||||||
|
path: "/login",
|
||||||
|
query: { redirect: to.fullPath },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
export function resetRouter() {
|
||||||
|
// 当前项目暂无动态 addRoute 路由,保留该方法兼容登出流程。
|
||||||
|
}
|
||||||
|
|
||||||
|
export default router;
|
||||||
5
src/store/index.js
Normal file
5
src/store/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
export function setupStore(app) {
|
||||||
|
app.use(createPinia())
|
||||||
|
}
|
||||||
47
src/store/modules/app.js
Normal file
47
src/store/modules/app.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { nextTick } from 'vue'
|
||||||
|
import { useDark } from '@vueuse/core'
|
||||||
|
|
||||||
|
const sidebarStatusKey = 'sidebar_status'
|
||||||
|
|
||||||
|
export const useAppStore = defineStore('app', {
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
sidebarStatus: localStorage.getItem(sidebarStatusKey) ? localStorage.getItem(sidebarStatusKey) === '1' : true,
|
||||||
|
device: 'desktop',
|
||||||
|
config: {},
|
||||||
|
reloadFlag: true,
|
||||||
|
isDark: !useDark(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
sidebar() {
|
||||||
|
return this.sidebarStatus
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async toggleSideBar() {
|
||||||
|
this.sidebarStatus = !this.sidebarStatus
|
||||||
|
if (this.sidebarStatus) {
|
||||||
|
localStorage.setItem(sidebarStatusKey, '1')
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(sidebarStatusKey, '0')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeSideBar(withoutAnimation) {
|
||||||
|
localStorage.setItem(sidebarStatusKey, '0')
|
||||||
|
this.sidebarStatus = false
|
||||||
|
},
|
||||||
|
toggleDevice(device) {
|
||||||
|
this.device = device
|
||||||
|
},
|
||||||
|
async reloadPage() {
|
||||||
|
this.reloadFlag = false
|
||||||
|
await nextTick()
|
||||||
|
this.reloadFlag = true
|
||||||
|
},
|
||||||
|
toggleDark() {
|
||||||
|
this.isDark = !this.isDark
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
112
src/store/modules/user.js
Normal file
112
src/store/modules/user.js
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { getToken, removeToken, setToken } from "@/utils/auth.js";
|
||||||
|
import { getInfo, login as doLogin } from "@/api/user.js";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { resetRouter } from "@/router";
|
||||||
|
|
||||||
|
export const useUserStore = defineStore("user", {
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
userInfo: {
|
||||||
|
token: getToken() || "",
|
||||||
|
id: "",
|
||||||
|
avatar: "",
|
||||||
|
name: "",
|
||||||
|
profile: {},
|
||||||
|
permissions: [],
|
||||||
|
dictMap: {},
|
||||||
|
dictTreeMap: {},
|
||||||
|
dictIdMap: {},
|
||||||
|
dynamicAttrs: [],
|
||||||
|
companyLogStatus: null,
|
||||||
|
attrIds: [],
|
||||||
|
regionIds: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
userId() {
|
||||||
|
return this.userInfo?.id || "";
|
||||||
|
},
|
||||||
|
name() {
|
||||||
|
return this.userInfo?.name || "";
|
||||||
|
},
|
||||||
|
avatar() {
|
||||||
|
return this.userInfo?.profile?.avatar || "";
|
||||||
|
},
|
||||||
|
permissions() {
|
||||||
|
return this.userInfo?.permissions || [];
|
||||||
|
},
|
||||||
|
dictMap() {
|
||||||
|
return this.userInfo?.dictMap || {};
|
||||||
|
},
|
||||||
|
dictTreeMap() {
|
||||||
|
return this.userInfo?.dictTreeMap || {};
|
||||||
|
},
|
||||||
|
dictIdMap() {
|
||||||
|
return this.userInfo?.dictIdMap || {};
|
||||||
|
},
|
||||||
|
dynamicAttrs() {
|
||||||
|
return this.userInfo?.dynamicAttrs || [];
|
||||||
|
},
|
||||||
|
companyLogStatus() {
|
||||||
|
return this.userInfo?.companyLogStatus || 0;
|
||||||
|
},
|
||||||
|
attrIds() {
|
||||||
|
return this.userInfo?.attrIds || [];
|
||||||
|
},
|
||||||
|
regionIds() {
|
||||||
|
return this.userInfo?.regionIds || [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async login(userInfo) {
|
||||||
|
const res = await doLogin(userInfo);
|
||||||
|
const { token, tokenName } = res.data;
|
||||||
|
this.userInfo.token = token;
|
||||||
|
setToken(token, tokenName);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
async getInfo() {
|
||||||
|
const res = await getInfo();
|
||||||
|
const data = res.data;
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
profile,
|
||||||
|
permissions,
|
||||||
|
dynamicAttrs,
|
||||||
|
attrIds,
|
||||||
|
companyLogStatus,
|
||||||
|
regionIds,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
// 保留当前的 token,避免被覆盖
|
||||||
|
const currentToken = this.userInfo.token || getToken();
|
||||||
|
|
||||||
|
this.userInfo = {
|
||||||
|
token: currentToken,
|
||||||
|
id: profile.id,
|
||||||
|
name,
|
||||||
|
profile,
|
||||||
|
permissions,
|
||||||
|
dynamicAttrs,
|
||||||
|
attrIds,
|
||||||
|
companyLogStatus,
|
||||||
|
regionIds,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
// user logout
|
||||||
|
async logout() {
|
||||||
|
await this.resetToken();
|
||||||
|
await resetRouter();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
async resetToken() {
|
||||||
|
this.userInfo = {
|
||||||
|
profile: {},
|
||||||
|
permissions: [],
|
||||||
|
};
|
||||||
|
removeToken();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
63
src/types/js-modules.d.ts
vendored
Normal file
63
src/types/js-modules.d.ts
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
interface Window {
|
||||||
|
$message: {
|
||||||
|
error(message: string, options?: Record<string, unknown>): void;
|
||||||
|
success(message: string, options?: Record<string, unknown>): void;
|
||||||
|
warning(message: string, options?: Record<string, unknown>): void;
|
||||||
|
info(message: string, options?: Record<string, unknown>): void;
|
||||||
|
};
|
||||||
|
$modal: {
|
||||||
|
create(options: Record<string, unknown>): unknown;
|
||||||
|
warning(options: Record<string, unknown>): unknown;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@/utils/auth" {
|
||||||
|
export function getToken(): string | null;
|
||||||
|
export function getTokenName(): string | null;
|
||||||
|
export function setToken(tokenValue: string, tokenNameValue?: string): void;
|
||||||
|
export function removeToken(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@/utils/auth.js" {
|
||||||
|
export function getToken(): string | null;
|
||||||
|
export function getTokenName(): string | null;
|
||||||
|
export function setToken(tokenValue: string, tokenNameValue?: string): void;
|
||||||
|
export function removeToken(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@/store/modules/user" {
|
||||||
|
export function useUserStore(): {
|
||||||
|
login(userInfo: Record<string, unknown>): Promise<boolean>;
|
||||||
|
logout(): Promise<boolean>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@/store/modules/user.js" {
|
||||||
|
export function useUserStore(): {
|
||||||
|
login(userInfo: Record<string, unknown>): Promise<boolean>;
|
||||||
|
logout(): Promise<boolean>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@/utils/passwordEncrypt.js" {
|
||||||
|
export function encryptPasswordPayload<T extends Record<string, unknown>>(
|
||||||
|
payload: T,
|
||||||
|
fields: Array<keyof T>,
|
||||||
|
): Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@/utils/request" {
|
||||||
|
import type { AxiosRequestConfig } from "axios";
|
||||||
|
|
||||||
|
export const BASE_URL: string;
|
||||||
|
const request: <T = unknown>(config: AxiosRequestConfig) => Promise<T>;
|
||||||
|
export default request;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@/utils/request.js" {
|
||||||
|
import type { AxiosRequestConfig } from "axios";
|
||||||
|
|
||||||
|
export const BASE_URL: string;
|
||||||
|
const request: <T = unknown>(config: AxiosRequestConfig) => Promise<T>;
|
||||||
|
export default request;
|
||||||
|
}
|
||||||
35
src/utils/auth.js
Normal file
35
src/utils/auth.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const TokenKey = "token";
|
||||||
|
const TOKEN_NAME_KEY = "tokenName";
|
||||||
|
|
||||||
|
let token = null;
|
||||||
|
let tokenName = null;
|
||||||
|
|
||||||
|
export function getToken() {
|
||||||
|
const storedToken = token || window.localStorage.getItem(TokenKey);
|
||||||
|
if (!storedToken || storedToken === "null" || storedToken === "undefined") {
|
||||||
|
token = null;
|
||||||
|
window.localStorage.removeItem(TokenKey);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
token = storedToken;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setToken(tokenValue, tokenNameValue) {
|
||||||
|
token = tokenValue;
|
||||||
|
tokenName = tokenNameValue || "Authorization";
|
||||||
|
window.localStorage.setItem(TokenKey, token);
|
||||||
|
window.localStorage.setItem(TOKEN_NAME_KEY, tokenName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeToken() {
|
||||||
|
token = null;
|
||||||
|
tokenName = null;
|
||||||
|
window.localStorage.removeItem(TokenKey);
|
||||||
|
window.localStorage.removeItem(TOKEN_NAME_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTokenName() {
|
||||||
|
tokenName = tokenName || window.localStorage.getItem(TOKEN_NAME_KEY);
|
||||||
|
return tokenName;
|
||||||
|
}
|
||||||
38
src/utils/passwordEncrypt.js
Normal file
38
src/utils/passwordEncrypt.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { JSEncrypt } from 'jsencrypt'
|
||||||
|
import { getPublicKey } from '@/api/user'
|
||||||
|
|
||||||
|
export async function getPasswordPublicKey() {
|
||||||
|
const res = await getPublicKey()
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encryptPassword(plainText, publicKey) {
|
||||||
|
const encryptor = new JSEncrypt()
|
||||||
|
encryptor.setPublicKey(formatPublicKey(publicKey))
|
||||||
|
const encrypted = encryptor.encrypt(plainText)
|
||||||
|
if (!encrypted) {
|
||||||
|
throw new Error('密码加密失败,请刷新页面后重试')
|
||||||
|
}
|
||||||
|
return encrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function encryptPasswordPayload(payload, fields) {
|
||||||
|
const publicKey = await getPasswordPublicKey()
|
||||||
|
const nextPayload = { ...payload }
|
||||||
|
for (const field of fields) {
|
||||||
|
nextPayload[field] = encryptPassword(payload[field] || '', publicKey)
|
||||||
|
}
|
||||||
|
return nextPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPublicKey(publicKey) {
|
||||||
|
if (!publicKey) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (publicKey.includes('BEGIN PUBLIC KEY')) {
|
||||||
|
return publicKey
|
||||||
|
}
|
||||||
|
const normalized = publicKey.replace(/\s+/g, '')
|
||||||
|
const lines = normalized.match(/.{1,64}/g) || []
|
||||||
|
return ['-----BEGIN PUBLIC KEY-----', ...lines, '-----END PUBLIC KEY-----'].join('\n')
|
||||||
|
}
|
||||||
108
src/utils/request.js
Normal file
108
src/utils/request.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
import { getToken, getTokenName } from "@/utils/auth";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
|
export const BASE_URL = import.meta.env.VITE_APP_BASE_API || "/api";
|
||||||
|
|
||||||
|
// create an axios instance
|
||||||
|
const service = axios.create({
|
||||||
|
method: "POST",
|
||||||
|
baseURL: BASE_URL, // url = base url + request url
|
||||||
|
withCredentials: false, // dont send cookies when cross-domain requests
|
||||||
|
timeout: 20000, // request timeout
|
||||||
|
});
|
||||||
|
|
||||||
|
// request interceptor
|
||||||
|
service.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
// do something before request is sent
|
||||||
|
const token = getToken();
|
||||||
|
if (token) {
|
||||||
|
config.headers[getTokenName() || "Authorization"] = token; // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
// do something with request error
|
||||||
|
console.log(error); // for debug
|
||||||
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// response interceptor
|
||||||
|
service.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
if (response.data instanceof Blob) {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
const res = response.data;
|
||||||
|
// 状态码不是0, 说明出错了
|
||||||
|
if (res.code !== 200) {
|
||||||
|
showApiMessage(res);
|
||||||
|
|
||||||
|
if (res.code === 401) {
|
||||||
|
// to re-login
|
||||||
|
handleNoLogin();
|
||||||
|
}
|
||||||
|
return Promise.reject(res);
|
||||||
|
} else {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.log(error);
|
||||||
|
if (error.response && error.response.status === 401) {
|
||||||
|
if (error.response.data.msg) {
|
||||||
|
if (error.response.data.msg.includes("权限")) {
|
||||||
|
window.$message.error(error.response.data.msg, {
|
||||||
|
duration: 5 * 1000,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleNoLogin();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleNoLogin();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.$message.error(error.message, { duration: 5 * 1000 });
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default service;
|
||||||
|
|
||||||
|
function showApiMessage(res) {
|
||||||
|
window.$message.error(res.msg || "error", {
|
||||||
|
duration: 5 * 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹窗确认防抖
|
||||||
|
let noLoginAlert = null;
|
||||||
|
|
||||||
|
function handleNoLogin() {
|
||||||
|
if (noLoginAlert == null) {
|
||||||
|
noLoginAlert = window.$modal.create({
|
||||||
|
type: "warning",
|
||||||
|
preset: "dialog",
|
||||||
|
title: "登录失效",
|
||||||
|
content: "您的登录已过期,请重新登录",
|
||||||
|
positiveText: "确认退出",
|
||||||
|
maskClosable: false,
|
||||||
|
closable: false,
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
await useUserStore().logout();
|
||||||
|
router.replace({
|
||||||
|
path: "/login",
|
||||||
|
query: { redirect: router.currentRoute.value.path },
|
||||||
|
});
|
||||||
|
noLoginAlert = null;
|
||||||
|
},
|
||||||
|
onClose: () => (noLoginAlert = null),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("跳过确认退出弹窗");
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,44 +1,37 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, type Component } from "vue";
|
import { computed } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import SwitchPageBtn from "./components/btn";
|
import SwitchPageBtn from "./components/btn";
|
||||||
import backgroundVideo from "./assets/循环背景动画.mp4";
|
import backgroundVideo from "./assets/循环背景动画.mp4";
|
||||||
import AlarmManagementPage from "./pages/AlarmManagementPage.vue";
|
|
||||||
import DataReportPage from "./pages/DataReportPage.vue";
|
|
||||||
import EquipmentManagementPage from "./pages/EquipmentManagementPage.vue";
|
|
||||||
import IntegratedCenterPage from "./pages/IntegratedCenterPage.vue";
|
|
||||||
import PatrolPlanPage from "./pages/PatrolPlanPage.vue";
|
|
||||||
import VideoCenterPage from "./pages/VideoCenterPage.vue";
|
|
||||||
|
|
||||||
const currentTab = ref<number>(1);
|
interface DashboardTab {
|
||||||
|
id: number;
|
||||||
|
text: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
const tabs = {
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const tabs: Record<"left" | "right", DashboardTab[]> = {
|
||||||
left: [
|
left: [
|
||||||
{ id: 1, text: "监管中心" },
|
{ id: 1, text: "监管中心", path: "/dashboard/integrated-center" },
|
||||||
{ id: 2, text: "设备管理" },
|
{ id: 2, text: "设备管理", path: "/dashboard/equipment-management" },
|
||||||
{ id: 3, text: "视频中心" },
|
{ id: 3, text: "视频中心", path: "/dashboard/video-center" },
|
||||||
],
|
],
|
||||||
right: [
|
right: [
|
||||||
{ id: 4, text: "告警管理" },
|
{ id: 4, text: "告警管理", path: "/dashboard/alarm-management" },
|
||||||
{ id: 5, text: "巡检记录" },
|
{ id: 5, text: "巡检记录", path: "/dashboard/patrol-plan" },
|
||||||
{ id: 6, text: "统计分析" },
|
{ id: 6, text: "统计分析", path: "/dashboard/data-report" },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageComponents: Record<number, Component> = {
|
const activeTabId = computed(() => Number(route.meta.tabId) || 1);
|
||||||
1: IntegratedCenterPage,
|
|
||||||
2: EquipmentManagementPage,
|
|
||||||
3: VideoCenterPage,
|
|
||||||
4: AlarmManagementPage,
|
|
||||||
5: PatrolPlanPage,
|
|
||||||
6: DataReportPage,
|
|
||||||
};
|
|
||||||
|
|
||||||
const currentViewComponent = computed<Component>(
|
function switchPage(tab: DashboardTab) {
|
||||||
() => pageComponents[currentTab.value] ?? IntegratedCenterPage,
|
if (route.path !== tab.path) {
|
||||||
);
|
router.push(tab.path);
|
||||||
|
}
|
||||||
function switchPage(tab: number) {
|
|
||||||
currentTab.value = tab;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -58,27 +51,27 @@ function switchPage(tab: number) {
|
|||||||
<SwitchPageBtn
|
<SwitchPageBtn
|
||||||
v-for="tab in tabs.left"
|
v-for="tab in tabs.left"
|
||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
:active="currentTab === tab.id"
|
:active="activeTabId === tab.id"
|
||||||
:text="tab.text"
|
:text="tab.text"
|
||||||
direction="left"
|
direction="left"
|
||||||
@click="switchPage(tab.id)"
|
@click="switchPage(tab)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<SwitchPageBtn
|
<SwitchPageBtn
|
||||||
v-for="tab in tabs.right"
|
v-for="tab in tabs.right"
|
||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
:active="currentTab === tab.id"
|
:active="activeTabId === tab.id"
|
||||||
:text="tab.text"
|
:text="tab.text"
|
||||||
direction="right"
|
direction="right"
|
||||||
@click="switchPage(tab.id)"
|
@click="switchPage(tab)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content-stage">
|
<div class="content-stage">
|
||||||
<KeepAlive>
|
<RouterView v-slot="{ Component, route }">
|
||||||
<component :is="currentViewComponent" />
|
<component :is="Component" :key="route.fullPath" />
|
||||||
</KeepAlive>
|
</RouterView>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from "vue";
|
import { NImage } from "naive-ui";
|
||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import {
|
||||||
|
getAlarmPage,
|
||||||
|
type RosInspectionRecordLineImage,
|
||||||
|
} from "@/api/alarm";
|
||||||
|
import { BASE_URL } from "@/utils/request";
|
||||||
import { getRecentAlertPhoto } from "../shared/alertPhotos";
|
import { getRecentAlertPhoto } from "../shared/alertPhotos";
|
||||||
|
|
||||||
interface AlarmRecord {
|
interface AlarmRecord {
|
||||||
@@ -7,80 +13,55 @@ interface AlarmRecord {
|
|||||||
type: string;
|
type: string;
|
||||||
robot: string;
|
robot: string;
|
||||||
photo: string;
|
photo: string;
|
||||||
status: "待处理";
|
status: RecognitionStatusText;
|
||||||
time: string;
|
time: string;
|
||||||
|
position: string;
|
||||||
|
result: string;
|
||||||
|
warning: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const alarms: AlarmRecord[] = [
|
type RecognitionStatusText = "待执行" | "执行中" | "执行成功" | "执行失败" | "未知状态";
|
||||||
{
|
|
||||||
id: "AL-001",
|
|
||||||
type: "线缆破损",
|
|
||||||
robot: "巡检机器人 R1",
|
|
||||||
photo: getRecentAlertPhoto("线缆破损"),
|
|
||||||
status: "待处理",
|
|
||||||
time: "2026-06-23 10:24:18",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "AL-002",
|
|
||||||
type: "局部高温",
|
|
||||||
robot: "巡检机器人 Q1",
|
|
||||||
photo: getRecentAlertPhoto("局部高温"),
|
|
||||||
status: "待处理",
|
|
||||||
time: "2026-06-23 10:18:42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "AL-003",
|
|
||||||
type: "墙体裂缝",
|
|
||||||
robot: "巡检机器人 N3",
|
|
||||||
photo: getRecentAlertPhoto("墙体裂缝"),
|
|
||||||
status: "待处理",
|
|
||||||
time: "2026-06-23 09:56:11",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "AL-004",
|
|
||||||
type: "水渍渗漏",
|
|
||||||
robot: "巡检机器人 R2",
|
|
||||||
photo: getRecentAlertPhoto("局部高温"),
|
|
||||||
status: "待处理",
|
|
||||||
time: "2026-06-23 09:20:36",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "AL-005",
|
|
||||||
type: "应急门开启异常",
|
|
||||||
robot: "巡检机器人 N1",
|
|
||||||
photo: getRecentAlertPhoto("线缆破损"),
|
|
||||||
status: "待处理",
|
|
||||||
time: "2026-06-23 08:42:57",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const keyword = ref("");
|
interface AlarmPageResult {
|
||||||
const pageSize = 4;
|
total?: number;
|
||||||
|
data?: RosInspectionRecordLineImage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlarmPageResponse {
|
||||||
|
data?: AlarmPageResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recognizeTypeMap: Record<number, string> = {
|
||||||
|
100: "局部高温",
|
||||||
|
1: "电缆表面破损",
|
||||||
|
2: "桥架断裂",
|
||||||
|
3: "隧道积水",
|
||||||
|
4: "墙体裂缝",
|
||||||
|
};
|
||||||
|
|
||||||
|
const recognitionStatusMap: Record<number, RecognitionStatusText> = {
|
||||||
|
0: "待执行",
|
||||||
|
1: "执行中",
|
||||||
|
2: "执行成功",
|
||||||
|
3: "执行失败",
|
||||||
|
};
|
||||||
|
|
||||||
|
const alarmNo = ref("");
|
||||||
|
const recognizeType = ref("");
|
||||||
|
const startTime = ref("");
|
||||||
|
const endTime = ref("");
|
||||||
|
const alarms = ref<AlarmRecord[]>([]);
|
||||||
|
const totalAlarms = ref(0);
|
||||||
|
const listLoading = ref(false);
|
||||||
|
const listError = ref("");
|
||||||
|
const pageSize = 10;
|
||||||
const currentPage = ref(1);
|
const currentPage = ref(1);
|
||||||
|
|
||||||
const filteredAlarms = computed(() => {
|
|
||||||
const search = keyword.value.trim().toLowerCase();
|
|
||||||
|
|
||||||
if (!search) {
|
|
||||||
return alarms;
|
|
||||||
}
|
|
||||||
|
|
||||||
return alarms.filter((alarm) =>
|
|
||||||
[alarm.id, alarm.type, alarm.robot, alarm.status]
|
|
||||||
.join(" ")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(search),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const totalPages = computed(() =>
|
const totalPages = computed(() =>
|
||||||
Math.max(1, Math.ceil(filteredAlarms.value.length / pageSize)),
|
Math.max(1, Math.ceil(totalAlarms.value / pageSize)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const pagedAlarms = computed(() => {
|
const pagedAlarms = computed(() => alarms.value);
|
||||||
const start = (currentPage.value - 1) * pageSize;
|
|
||||||
return filteredAlarms.value.slice(start, start + pageSize);
|
|
||||||
});
|
|
||||||
|
|
||||||
const visiblePages = computed(() => {
|
const visiblePages = computed(() => {
|
||||||
const pages: number[] = [];
|
const pages: number[] = [];
|
||||||
@@ -95,20 +76,124 @@ const visiblePages = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function getAlarmStatusClass(status: AlarmRecord["status"]) {
|
function getAlarmStatusClass(status: AlarmRecord["status"]) {
|
||||||
const classMap = {
|
const classMap: Record<AlarmRecord["status"], string> = {
|
||||||
待处理: "alarm-status-handling",
|
待执行: "alarm-status-pending",
|
||||||
|
执行中: "alarm-status-handling",
|
||||||
|
执行成功: "alarm-status-finished",
|
||||||
|
执行失败: "alarm-status-false",
|
||||||
|
未知状态: "alarm-status-false",
|
||||||
};
|
};
|
||||||
|
|
||||||
return classMap[status];
|
return classMap[status];
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPage(page: number) {
|
function setPage(page: number) {
|
||||||
currentPage.value = page;
|
currentPage.value = Math.min(Math.max(page, 1), totalPages.value);
|
||||||
|
fetchAlarmList();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
|
fetchAlarmList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 请求告警分页列表并更新表格数据
|
||||||
|
async function fetchAlarmList() {
|
||||||
|
listLoading.value = true;
|
||||||
|
listError.value = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = (await getAlarmPage({
|
||||||
|
page: currentPage.value,
|
||||||
|
limit: pageSize,
|
||||||
|
query: {
|
||||||
|
recognizeType: normalizeRecognizeType(recognizeType.value),
|
||||||
|
startTime: normalizeDateTime(startTime.value),
|
||||||
|
endTime: normalizeDateTime(endTime.value),
|
||||||
|
id: alarmNo.value.trim() || undefined,
|
||||||
|
},
|
||||||
|
})) as AlarmPageResponse;
|
||||||
|
const result = response.data;
|
||||||
|
const rows = result?.data ?? [];
|
||||||
|
|
||||||
|
alarms.value = rows.map((record, index) => normalizeAlarmRecord(record, index));
|
||||||
|
totalAlarms.value = result?.total ?? rows.length;
|
||||||
|
} catch (error) {
|
||||||
|
listError.value = "近期告警加载失败";
|
||||||
|
alarms.value = [];
|
||||||
|
totalAlarms.value = 0;
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
listLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将后端告警记录转换为页面表格展示结构
|
||||||
|
function normalizeAlarmRecord(record: RosInspectionRecordLineImage, index: number): AlarmRecord {
|
||||||
|
const type = getRecognizeTypeText(record.recognizeType);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: record.id || record.recordActionId || record.recordId || `AL-${index + 1}`,
|
||||||
|
type,
|
||||||
|
robot: getCarName(record),
|
||||||
|
photo: buildFileAccessUrl(record.imageUrl || "") || getRecentAlertPhoto(type),
|
||||||
|
status: getRecognitionStatusText(record.recognitionStatus),
|
||||||
|
time: record.captureTime || record.createTime || "--",
|
||||||
|
position: formatPosition(record),
|
||||||
|
result: record.warningValue || record.recognitionResult || record.failReason || "--",
|
||||||
|
warning: Boolean(record.warning),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCarName(record: RosInspectionRecordLineImage) {
|
||||||
|
return record.car?.name || record.car?.deviceNo || record.car?.deviceKey || record.carId || "未知设备";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRecognizeTypeText(type?: number) {
|
||||||
|
return type ? recognizeTypeMap[type] ?? "未知告警" : "未知告警";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRecognitionStatusText(status?: number) {
|
||||||
|
return typeof status === "number" ? recognitionStatusMap[status] ?? "未知状态" : "未知状态";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 相对文件地址通过接口代理访问,避免开发环境直接请求后端静态路径失败
|
||||||
|
function buildFileAccessUrl(fileUrl: string) {
|
||||||
|
if (
|
||||||
|
!fileUrl ||
|
||||||
|
/^(https?:)?\/\//i.test(fileUrl) ||
|
||||||
|
fileUrl.startsWith("blob:") ||
|
||||||
|
fileUrl.startsWith("data:")
|
||||||
|
) {
|
||||||
|
return fileUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileUrl === BASE_URL || fileUrl.startsWith(`${BASE_URL}/`)) {
|
||||||
|
return fileUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${BASE_URL}${fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDateTime(value: string) {
|
||||||
|
return value ? value.replace("T", " ") : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeRecognizeType(value: string) {
|
||||||
|
return value ? Number(value) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPosition(record: RosInspectionRecordLineImage) {
|
||||||
|
if (typeof record.poseX !== "number" || typeof record.poseY !== "number") {
|
||||||
|
return "--";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `X:${record.poseX.toFixed(2)} Y:${record.poseY.toFixed(2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchAlarmList();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -117,16 +202,35 @@ function handleSearch() {
|
|||||||
<section class="page-card management-table-card">
|
<section class="page-card management-table-card">
|
||||||
<div class="management-toolbar">
|
<div class="management-toolbar">
|
||||||
<div class="toolbar-left">
|
<div class="toolbar-left">
|
||||||
<button class="page-action-btn ghost" type="button">导出告警</button>
|
<select v-model="recognizeType" class="toolbar-select">
|
||||||
|
<option value="">全部告警类型</option>
|
||||||
|
<option value="100">局部高温</option>
|
||||||
|
<option value="1">电缆表面破损</option>
|
||||||
|
<option value="2">桥架断裂</option>
|
||||||
|
<option value="3">隧道积水</option>
|
||||||
|
<option value="4">墙体裂缝</option>
|
||||||
|
</select>
|
||||||
<input
|
<input
|
||||||
v-model="keyword"
|
v-model="startTime"
|
||||||
class="toolbar-input"
|
class="toolbar-input alarm-time-input"
|
||||||
placeholder="搜索告警编号 / 告警类型 / 报警机器人"
|
type="datetime-local"
|
||||||
type="text"
|
|
||||||
@input="handleSearch"
|
|
||||||
/>
|
/>
|
||||||
|
<input
|
||||||
|
v-model="endTime"
|
||||||
|
class="toolbar-input alarm-time-input"
|
||||||
|
type="datetime-local"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
v-model="alarmNo"
|
||||||
|
class="toolbar-input"
|
||||||
|
placeholder="搜索告警编号"
|
||||||
|
type="text"
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
/>
|
||||||
|
<button class="page-action-btn primary" type="button" @click="handleSearch">搜索</button>
|
||||||
|
<button class="page-action-btn ghost" type="button" @click="fetchAlarmList">刷新</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">共 {{ filteredAlarms.length }} 条告警记录</div>
|
<div class="toolbar-right">共 {{ totalAlarms }} 条告警记录</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-shell">
|
<div class="table-shell">
|
||||||
@@ -139,16 +243,33 @@ function handleSearch() {
|
|||||||
<th>取证照片</th>
|
<th>取证照片</th>
|
||||||
<th>案件状态</th>
|
<th>案件状态</th>
|
||||||
<th>告警时间</th>
|
<th>告警时间</th>
|
||||||
<th>操作</th>
|
<th>位置</th>
|
||||||
|
<th>识别结果</th>
|
||||||
|
<th>是否预警</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="alarm in pagedAlarms" :key="alarm.id">
|
<tr v-if="listLoading">
|
||||||
|
<td class="table-empty" colspan="9">近期告警加载中...</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-else-if="listError">
|
||||||
|
<td class="table-empty" colspan="9">{{ listError }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-else-if="!pagedAlarms.length">
|
||||||
|
<td class="table-empty" colspan="9">暂无告警记录</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="alarm in pagedAlarms" v-else :key="alarm.id">
|
||||||
<td>{{ alarm.id }}</td>
|
<td>{{ alarm.id }}</td>
|
||||||
<td>{{ alarm.type }}</td>
|
<td>{{ alarm.type }}</td>
|
||||||
<td>{{ alarm.robot }}</td>
|
<td>{{ alarm.robot }}</td>
|
||||||
<td>
|
<td>
|
||||||
<img class="evidence-photo" :src="alarm.photo" :alt="`${alarm.type}取证照片`" />
|
<NImage
|
||||||
|
class="evidence-photo"
|
||||||
|
:src="alarm.photo"
|
||||||
|
:alt="`${alarm.type}取证照片`"
|
||||||
|
object-fit="contain"
|
||||||
|
:previewed-img-props="{ style: { maxWidth: '82vw', maxHeight: '82vh', objectFit: 'contain' } }"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span :class="['table-status', getAlarmStatusClass(alarm.status)]">
|
<span :class="['table-status', getAlarmStatusClass(alarm.status)]">
|
||||||
@@ -156,11 +277,9 @@ function handleSearch() {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ alarm.time }}</td>
|
<td>{{ alarm.time }}</td>
|
||||||
<td>
|
<td>{{ alarm.position }}</td>
|
||||||
<div class="table-actions">
|
<td>{{ alarm.result }}</td>
|
||||||
<button class="danger" type="button">删除</button>
|
<td>{{ alarm.warning ? "是" : "否" }}</td>
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -169,7 +288,7 @@ function handleSearch() {
|
|||||||
<div class="pagination-bar">
|
<div class="pagination-bar">
|
||||||
<button
|
<button
|
||||||
class="pagination-btn"
|
class="pagination-btn"
|
||||||
:disabled="currentPage === 1"
|
:disabled="currentPage === 1 || listLoading"
|
||||||
type="button"
|
type="button"
|
||||||
@click="setPage(currentPage - 1)"
|
@click="setPage(currentPage - 1)"
|
||||||
>
|
>
|
||||||
@@ -179,6 +298,7 @@ function handleSearch() {
|
|||||||
v-for="page in visiblePages"
|
v-for="page in visiblePages"
|
||||||
:key="page"
|
:key="page"
|
||||||
:class="['pagination-btn', { active: currentPage === page }]"
|
:class="['pagination-btn', { active: currentPage === page }]"
|
||||||
|
:disabled="listLoading"
|
||||||
type="button"
|
type="button"
|
||||||
@click="setPage(page)"
|
@click="setPage(page)"
|
||||||
>
|
>
|
||||||
@@ -186,7 +306,7 @@ function handleSearch() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="pagination-btn"
|
class="pagination-btn"
|
||||||
:disabled="currentPage === totalPages"
|
:disabled="currentPage === totalPages || listLoading"
|
||||||
type="button"
|
type="button"
|
||||||
@click="setPage(currentPage + 1)"
|
@click="setPage(currentPage + 1)"
|
||||||
>
|
>
|
||||||
@@ -197,3 +317,9 @@ function handleSearch() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.alarm-time-input {
|
||||||
|
width: 190px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,59 +1,77 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, nextTick, ref } from "vue";
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||||
import monitorVideo from "../assets/循环背景动画.mp4";
|
import { getRosCarList } from "@/api/rosCar";
|
||||||
|
|
||||||
type GridMode = 1 | 4 | 9;
|
type GridMode = 1 | 4 | 9;
|
||||||
|
type CameraType = "可见光" | "红外";
|
||||||
|
type DeviceStatus = "空闲" | "巡检" | "离线" | "充电中";
|
||||||
|
|
||||||
|
interface RosCarRecord {
|
||||||
|
id?: string;
|
||||||
|
deviceNo?: string;
|
||||||
|
name?: string;
|
||||||
|
batteryLevel?: number;
|
||||||
|
status?: number;
|
||||||
|
projectName?: string;
|
||||||
|
cameraWebrtcUrl?: string;
|
||||||
|
cameraNo?: string;
|
||||||
|
cameraWebrtcUrl2?: string;
|
||||||
|
cameraNo2?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RosCarListResult {
|
||||||
|
total?: number;
|
||||||
|
data?: RosCarRecord[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RosCarListResponse {
|
||||||
|
data?: RosCarListResult;
|
||||||
|
}
|
||||||
|
|
||||||
interface CameraItem {
|
interface CameraItem {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: CameraType;
|
||||||
status: string;
|
status: DeviceStatus;
|
||||||
|
url: string;
|
||||||
|
cameraNo?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeviceNode {
|
interface DeviceNode {
|
||||||
id: string;
|
id: string;
|
||||||
|
deviceNo?: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
project: string;
|
||||||
|
status: DeviceStatus;
|
||||||
|
battery: string;
|
||||||
cameras: CameraItem[];
|
cameras: CameraItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProjectNode {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
devices: DeviceNode[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ProjectCameraItem extends CameraItem {
|
interface ProjectCameraItem extends CameraItem {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
deviceName: string;
|
deviceName: string;
|
||||||
|
project: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectData: ProjectNode = {
|
interface SlotStreamState {
|
||||||
id: "project-1",
|
loading: boolean;
|
||||||
name: "西江隧道",
|
error: string;
|
||||||
devices: [
|
}
|
||||||
{
|
|
||||||
id: "ROBOT-001",
|
|
||||||
name: "巡检机器人 R1",
|
|
||||||
cameras: [
|
|
||||||
{ id: "CAM-001", name: "白光镜头", type: "白光", status: "在线" },
|
|
||||||
{ id: "CAM-002", name: "红外镜头", type: "红外", status: "在线" },
|
|
||||||
{ id: "CAM-003", name: "云台辅助镜头", type: "辅助", status: "在线" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ROBOT-002",
|
|
||||||
name: "巡检机器人 R2",
|
|
||||||
cameras: [
|
|
||||||
{ id: "CAM-004", name: "白光镜头", type: "白光", status: "在线" },
|
|
||||||
{ id: "CAM-005", name: "红外镜头", type: "红外", status: "在线" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const defaultProject = "视频中心";
|
||||||
const gridMode = ref<GridMode>(4);
|
const gridMode = ref<GridMode>(4);
|
||||||
const activeCameraId = ref(projectData.devices[0].cameras[0].id);
|
const activeSlotIndex = ref(0);
|
||||||
|
const videoSlots = ref<Array<ProjectCameraItem | null>>([]);
|
||||||
|
const slotVideoRefs = ref<Array<HTMLVideoElement | null>>([]);
|
||||||
|
const slotStreamStates = ref<SlotStreamState[]>([]);
|
||||||
|
const videoGridBoardRef = ref<HTMLElement | null>(null);
|
||||||
|
const videoGridBoardStyle = ref<Record<string, string>>({});
|
||||||
|
const slotPeerConnections = new Map<number, RTCPeerConnection>();
|
||||||
|
const slotRequestIds = new Map<number, number>();
|
||||||
|
let videoGridResizeObserver: ResizeObserver | null = null;
|
||||||
|
const devices = ref<DeviceNode[]>([]);
|
||||||
|
const listLoading = ref(false);
|
||||||
|
const listError = ref("");
|
||||||
const playbackDialogVisible = ref(false);
|
const playbackDialogVisible = ref(false);
|
||||||
const playbackStartAt = ref("2026-06-23T14:20");
|
const playbackStartAt = ref("2026-06-23T14:20");
|
||||||
const playbackEndAt = ref("2026-06-23T14:50");
|
const playbackEndAt = ref("2026-06-23T14:50");
|
||||||
@@ -61,46 +79,39 @@ const playbackProgress = ref(0);
|
|||||||
const playbackPaused = ref(true);
|
const playbackPaused = ref(true);
|
||||||
const playbackPlayerRef = ref<HTMLVideoElement | null>(null);
|
const playbackPlayerRef = ref<HTMLVideoElement | null>(null);
|
||||||
|
|
||||||
const currentProject = computed(() => projectData);
|
const slotCountMap: Record<GridMode, number> = {
|
||||||
|
1: 1,
|
||||||
|
4: 4,
|
||||||
|
9: 9,
|
||||||
|
};
|
||||||
|
|
||||||
const projectCameras = computed<ProjectCameraItem[]>(() =>
|
const currentProjectName = computed(() => devices.value[0]?.project || defaultProject);
|
||||||
currentProject.value.devices.flatMap((device) =>
|
|
||||||
|
const playableCameras = computed<ProjectCameraItem[]>(() =>
|
||||||
|
devices.value.flatMap((device) =>
|
||||||
device.cameras.map((camera) => ({
|
device.cameras.map((camera) => ({
|
||||||
...camera,
|
...camera,
|
||||||
deviceId: device.id,
|
deviceId: device.id,
|
||||||
deviceName: device.name,
|
deviceName: device.name,
|
||||||
|
project: device.project,
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeCamera = computed(
|
const cameraTotal = computed(() => playableCameras.value.length);
|
||||||
() =>
|
|
||||||
projectCameras.value.find((camera) => camera.id === activeCameraId.value) ??
|
|
||||||
projectCameras.value[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
const orderedCameras = computed(() => {
|
const visibleSlots = computed(() => {
|
||||||
const selected = activeCamera.value;
|
const targetCount = slotCountMap[gridMode.value];
|
||||||
|
|
||||||
return projectCameras.value
|
return Array.from(
|
||||||
.filter((camera) => camera.id !== selected.id)
|
{ length: targetCount },
|
||||||
.reduce<ProjectCameraItem[]>((list, camera) => {
|
(_, index) => videoSlots.value[index] ?? null,
|
||||||
list.push(camera);
|
);
|
||||||
return list;
|
|
||||||
}, [selected]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const visibleCameras = computed(() => {
|
const activeCamera = computed(() => visibleSlots.value[activeSlotIndex.value] ?? null);
|
||||||
const countMap: Record<GridMode, number> = {
|
|
||||||
1: 1,
|
|
||||||
4: 4,
|
|
||||||
9: 9,
|
|
||||||
};
|
|
||||||
const targetCount = countMap[gridMode.value];
|
|
||||||
const cameras = orderedCameras.value;
|
|
||||||
|
|
||||||
return Array.from({ length: targetCount }, (_, index) => cameras[index] ?? null);
|
const activeSlotLabel = computed(() => `画面 ${activeSlotIndex.value + 1}`);
|
||||||
});
|
|
||||||
|
|
||||||
const playbackRangeLabel = computed(() => {
|
const playbackRangeLabel = computed(() => {
|
||||||
const startLabel = playbackStartAt.value.replace("T", " ");
|
const startLabel = playbackStartAt.value.replace("T", " ");
|
||||||
@@ -108,14 +119,311 @@ const playbackRangeLabel = computed(() => {
|
|||||||
return `${startLabel} - ${endLabel}`;
|
return `${startLabel} - ${endLabel}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
function selectCamera(cameraId: string) {
|
// 根据视频墙实际可用宽高计算宫格尺寸,保证每个画面都是 16:9 且不会溢出卡片
|
||||||
activeCameraId.value = cameraId;
|
function updateVideoGridSize() {
|
||||||
|
const board = videoGridBoardRef.value;
|
||||||
|
|
||||||
|
if (!board) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = Math.sqrt(gridMode.value);
|
||||||
|
const rows = columns;
|
||||||
|
const gap = 8;
|
||||||
|
const availableWidth = board.clientWidth - gap * (columns - 1);
|
||||||
|
const availableHeight = board.clientHeight - gap * (rows - 1);
|
||||||
|
const cellWidthByContainerWidth = availableWidth / columns;
|
||||||
|
const cellWidthByContainerHeight = (availableHeight / rows) * (16 / 9);
|
||||||
|
const cellWidth = Math.max(0, Math.min(cellWidthByContainerWidth, cellWidthByContainerHeight));
|
||||||
|
const cellHeight = cellWidth * (9 / 16);
|
||||||
|
|
||||||
|
videoGridBoardStyle.value = {
|
||||||
|
gridTemplateColumns: `repeat(${columns}, ${cellWidth}px)`,
|
||||||
|
gridAutoRows: `${cellHeight}px`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGridMode(mode: GridMode) {
|
// 根据当前宫格数量补齐画面槽位,切换布局时保留已播放的视频
|
||||||
|
function ensureVideoSlotCount() {
|
||||||
|
const targetCount = slotCountMap[gridMode.value];
|
||||||
|
const previousCount = videoSlots.value.length;
|
||||||
|
|
||||||
|
for (let index = targetCount; index < previousCount; index += 1) {
|
||||||
|
stopSlotStream(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
videoSlots.value = Array.from(
|
||||||
|
{ length: targetCount },
|
||||||
|
(_, index) => videoSlots.value[index] ?? null,
|
||||||
|
);
|
||||||
|
slotVideoRefs.value = Array.from(
|
||||||
|
{ length: targetCount },
|
||||||
|
(_, index) => slotVideoRefs.value[index] ?? null,
|
||||||
|
);
|
||||||
|
slotStreamStates.value = Array.from(
|
||||||
|
{ length: targetCount },
|
||||||
|
(_, index) => slotStreamStates.value[index] ?? { loading: false, error: "" },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (activeSlotIndex.value >= targetCount) {
|
||||||
|
activeSlotIndex.value = targetCount - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将接口的小车状态码转换为页面展示状态
|
||||||
|
function normalizeDeviceStatus(status?: number): DeviceStatus {
|
||||||
|
const statusMap: Record<number, DeviceStatus> = {
|
||||||
|
0: "空闲",
|
||||||
|
1: "巡检",
|
||||||
|
2: "离线",
|
||||||
|
3: "充电中",
|
||||||
|
};
|
||||||
|
|
||||||
|
return statusMap[status ?? 2] ?? "离线";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据小车视频地址生成可播放的相机节点
|
||||||
|
function buildCameraItem(
|
||||||
|
car: RosCarRecord,
|
||||||
|
deviceId: string,
|
||||||
|
type: CameraType,
|
||||||
|
url?: string,
|
||||||
|
cameraNo?: string,
|
||||||
|
): CameraItem | null {
|
||||||
|
if (!url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `${deviceId}-${type}`,
|
||||||
|
name: `${type}相机`,
|
||||||
|
type,
|
||||||
|
status: normalizeDeviceStatus(car.status),
|
||||||
|
url,
|
||||||
|
cameraNo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将设备列表接口的小车记录转换为左侧机器人树节点
|
||||||
|
function normalizeDeviceNode(car: RosCarRecord, index: number): DeviceNode {
|
||||||
|
const deviceId = car.id ?? `ROBOT-${index + 1}`;
|
||||||
|
const cameras = [
|
||||||
|
buildCameraItem(car, deviceId, "可见光", car.cameraWebrtcUrl, car.cameraNo),
|
||||||
|
buildCameraItem(car, deviceId, "红外", car.cameraWebrtcUrl2, car.cameraNo2),
|
||||||
|
].filter((camera): camera is CameraItem => Boolean(camera));
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: deviceId,
|
||||||
|
deviceNo: car.deviceNo,
|
||||||
|
name: car.name || car.deviceNo || `机器人 ${index + 1}`,
|
||||||
|
project: car.projectName || defaultProject,
|
||||||
|
status: normalizeDeviceStatus(car.status),
|
||||||
|
battery: typeof car.batteryLevel === "number" ? `${car.batteryLevel}%` : "--",
|
||||||
|
cameras,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求设备列表接口,加载左侧机器人及其可见光/红外相机
|
||||||
|
async function fetchDeviceList() {
|
||||||
|
listLoading.value = true;
|
||||||
|
listError.value = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = (await getRosCarList({
|
||||||
|
page: 1,
|
||||||
|
limit: 999,
|
||||||
|
query: {},
|
||||||
|
})) as RosCarListResponse;
|
||||||
|
const rows = response.data?.data ?? [];
|
||||||
|
|
||||||
|
devices.value = rows.map((car, index) => normalizeDeviceNode(car, index));
|
||||||
|
} catch (error) {
|
||||||
|
listError.value = "设备列表加载失败";
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
listLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中右侧视频墙中的目标画面
|
||||||
|
function selectVideoSlot(index: number) {
|
||||||
|
activeSlotIndex.value = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止全部画面的 WebRTC 拉流,用于页面卸载时释放资源
|
||||||
|
function stopAllSlotStreams() {
|
||||||
|
videoSlots.value.forEach((_, index) => stopSlotStream(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止指定画面的 WebRTC 拉流并释放播放器资源
|
||||||
|
function stopSlotStream(index: number, clearState = true) {
|
||||||
|
slotRequestIds.set(index, (slotRequestIds.get(index) ?? 0) + 1);
|
||||||
|
|
||||||
|
if (clearState) {
|
||||||
|
slotStreamStates.value[index] = { loading: false, error: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const peerConnection = slotPeerConnections.get(index);
|
||||||
|
if (peerConnection) {
|
||||||
|
peerConnection.close();
|
||||||
|
slotPeerConnections.delete(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
const video = slotVideoRefs.value[index];
|
||||||
|
if (video) {
|
||||||
|
video.srcObject = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将设备返回的 webrtc 播放地址转换为 ZLMediaKit WebRTC 播放接口地址
|
||||||
|
function buildWebrtcApiUrl(streamUrl: string) {
|
||||||
|
const normalizedUrl = streamUrl.replace(/^webrtc:\/\//, "https://");
|
||||||
|
const url = new URL(normalizedUrl);
|
||||||
|
|
||||||
|
return `${url.origin}${url.pathname}${url.search}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待本地 ICE 候选收集完成,确保发给流媒体服务的 offer 信息完整
|
||||||
|
function waitForIceGatheringComplete(peerConnection: RTCPeerConnection) {
|
||||||
|
if (peerConnection.iceGatheringState === "complete") {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
const handleIceGatheringStateChange = () => {
|
||||||
|
if (peerConnection.iceGatheringState === "complete") {
|
||||||
|
peerConnection.removeEventListener("icegatheringstatechange", handleIceGatheringStateChange);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
peerConnection.addEventListener("icegatheringstatechange", handleIceGatheringStateChange);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集视频墙每个画面对应的 video 元素,供 WebRTC ontrack 写入媒体流
|
||||||
|
function setSlotVideoRef(element: Element | { $el?: Element } | null, index: number) {
|
||||||
|
const target = element instanceof Element ? element : element?.$el;
|
||||||
|
slotVideoRefs.value[index] = target instanceof HTMLVideoElement ? target : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过 WebRTC offer/answer 将指定相机拉流到选中的画面槽位
|
||||||
|
async function playSlotStream(index: number, camera: ProjectCameraItem) {
|
||||||
|
stopSlotStream(index);
|
||||||
|
|
||||||
|
if (!window.RTCPeerConnection) {
|
||||||
|
slotStreamStates.value[index] = { loading: false, error: "当前浏览器不支持 WebRTC 播放" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const video = slotVideoRefs.value[index];
|
||||||
|
if (!video) {
|
||||||
|
slotStreamStates.value[index] = { loading: false, error: "播放器未准备好" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestId = slotRequestIds.get(index) ?? 0;
|
||||||
|
const peerConnection = new RTCPeerConnection();
|
||||||
|
slotPeerConnections.set(index, peerConnection);
|
||||||
|
slotStreamStates.value[index] = { loading: true, error: "" };
|
||||||
|
|
||||||
|
peerConnection.addTransceiver("video", { direction: "recvonly" });
|
||||||
|
peerConnection.addTransceiver("audio", { direction: "recvonly" });
|
||||||
|
peerConnection.ontrack = (event) => {
|
||||||
|
if (requestId !== slotRequestIds.get(index)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
video.srcObject = event.streams[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const offer = await peerConnection.createOffer();
|
||||||
|
await peerConnection.setLocalDescription(offer);
|
||||||
|
await waitForIceGatheringComplete(peerConnection);
|
||||||
|
|
||||||
|
const response = await fetch(buildWebrtcApiUrl(camera.url), {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/sdp",
|
||||||
|
},
|
||||||
|
body: peerConnection.localDescription?.sdp ?? offer.sdp,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`拉流失败:${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseText = await response.text();
|
||||||
|
let answer = responseText;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const responseData = JSON.parse(responseText) as { code?: number; sdp?: string; msg?: string };
|
||||||
|
|
||||||
|
if (responseData.code && responseData.code !== 0) {
|
||||||
|
throw new Error(responseData.msg || "视频流拉取失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
answer = responseData.sdp || responseText;
|
||||||
|
} catch (error) {
|
||||||
|
if (responseText.trim().startsWith("{")) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestId !== slotRequestIds.get(index)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await peerConnection.setRemoteDescription({
|
||||||
|
type: "answer",
|
||||||
|
sdp: answer,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (requestId === slotRequestIds.get(index)) {
|
||||||
|
slotStreamStates.value[index] = {
|
||||||
|
loading: false,
|
||||||
|
error: error instanceof Error ? error.message : "视频流拉取失败",
|
||||||
|
};
|
||||||
|
stopSlotStream(index, false);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (requestId === slotRequestIds.get(index)) {
|
||||||
|
slotStreamStates.value[index] = {
|
||||||
|
...slotStreamStates.value[index],
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭指定画面并释放对应 WebRTC 拉流资源
|
||||||
|
function closeVideoSlot(index: number) {
|
||||||
|
stopSlotStream(index);
|
||||||
|
videoSlots.value[index] = null;
|
||||||
|
|
||||||
|
if (activeSlotIndex.value === index) {
|
||||||
|
activeSlotIndex.value = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将左侧相机播放到当前选中的右侧画面
|
||||||
|
async function playCamera(camera: ProjectCameraItem) {
|
||||||
|
const targetIndex = activeSlotIndex.value;
|
||||||
|
videoSlots.value[targetIndex] = camera;
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
await playSlotStream(targetIndex, camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换视频墙宫格布局
|
||||||
|
async function setGridMode(mode: GridMode) {
|
||||||
gridMode.value = mode;
|
gridMode.value = mode;
|
||||||
|
await nextTick();
|
||||||
|
updateVideoGridSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步录像回放播放器进度和暂停状态
|
||||||
function syncPlaybackProgress() {
|
function syncPlaybackProgress() {
|
||||||
const video = playbackPlayerRef.value;
|
const video = playbackPlayerRef.value;
|
||||||
|
|
||||||
@@ -130,6 +438,7 @@ function syncPlaybackProgress() {
|
|||||||
playbackPaused.value = video.paused;
|
playbackPaused.value = video.paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从头开始播放当前回放视频
|
||||||
function startPlayback() {
|
function startPlayback() {
|
||||||
const video = playbackPlayerRef.value;
|
const video = playbackPlayerRef.value;
|
||||||
|
|
||||||
@@ -143,7 +452,12 @@ function startPlayback() {
|
|||||||
syncPlaybackProgress();
|
syncPlaybackProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 打开当前画面的录像回放弹窗
|
||||||
async function openPlaybackDialog() {
|
async function openPlaybackDialog() {
|
||||||
|
if (!activeCamera.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
playbackDialogVisible.value = true;
|
playbackDialogVisible.value = true;
|
||||||
playbackProgress.value = 0;
|
playbackProgress.value = 0;
|
||||||
playbackPaused.value = true;
|
playbackPaused.value = true;
|
||||||
@@ -152,12 +466,14 @@ async function openPlaybackDialog() {
|
|||||||
startPlayback();
|
startPlayback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭录像回放弹窗并暂停播放器
|
||||||
function closePlaybackDialog() {
|
function closePlaybackDialog() {
|
||||||
playbackPlayerRef.value?.pause();
|
playbackPlayerRef.value?.pause();
|
||||||
playbackDialogVisible.value = false;
|
playbackDialogVisible.value = false;
|
||||||
playbackPaused.value = true;
|
playbackPaused.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换录像回放的播放和暂停状态
|
||||||
function togglePlaybackPause() {
|
function togglePlaybackPause() {
|
||||||
const video = playbackPlayerRef.value;
|
const video = playbackPlayerRef.value;
|
||||||
|
|
||||||
@@ -174,6 +490,7 @@ function togglePlaybackPause() {
|
|||||||
syncPlaybackProgress();
|
syncPlaybackProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按秒数快进或快退录像回放
|
||||||
function seekPlayback(offsetSeconds: number) {
|
function seekPlayback(offsetSeconds: number) {
|
||||||
const video = playbackPlayerRef.value;
|
const video = playbackPlayerRef.value;
|
||||||
|
|
||||||
@@ -187,6 +504,7 @@ function seekPlayback(offsetSeconds: number) {
|
|||||||
syncPlaybackProgress();
|
syncPlaybackProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据拖动条位置更新录像回放播放进度
|
||||||
function updatePlaybackProgress() {
|
function updatePlaybackProgress() {
|
||||||
const video = playbackPlayerRef.value;
|
const video = playbackPlayerRef.value;
|
||||||
|
|
||||||
@@ -201,32 +519,83 @@ function updatePlaybackProgress() {
|
|||||||
|
|
||||||
video.currentTime = (playbackProgress.value / 100) * duration;
|
video.currentTime = (playbackProgress.value / 100) * duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(gridMode, async () => {
|
||||||
|
ensureVideoSlotCount();
|
||||||
|
await nextTick();
|
||||||
|
updateVideoGridSize();
|
||||||
|
}, { immediate: true });
|
||||||
|
onMounted(() => {
|
||||||
|
void fetchDeviceList();
|
||||||
|
updateVideoGridSize();
|
||||||
|
|
||||||
|
if (videoGridBoardRef.value) {
|
||||||
|
videoGridResizeObserver = new ResizeObserver(updateVideoGridSize);
|
||||||
|
videoGridResizeObserver.observe(videoGridBoardRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
videoGridResizeObserver?.disconnect();
|
||||||
|
stopAllSlotStreams();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page-shell video-center-page video-center-shell">
|
<div class="page-shell video-center-page video-center-shell">
|
||||||
<div class="video-center-layout">
|
<div class="video-center-layout">
|
||||||
<aside class="page-card video-tree-card">
|
<aside class="page-card video-tree-card">
|
||||||
<div class="page-card-title">摄像头列表</div>
|
<div class="page-card-title">机器人列表</div>
|
||||||
<div class="tree-panel">
|
<div class="tree-panel">
|
||||||
<div class="tree-panel-project">
|
<!-- <div class="tree-panel-project">
|
||||||
<div class="tree-panel-project-name">{{ currentProject.name }}</div>
|
<div class="tree-panel-project-name">{{ currentProjectName }}</div>
|
||||||
<div class="tree-panel-project-meta">共 {{ projectCameras.length }} 路摄像头</div>
|
<div class="tree-panel-project-meta">
|
||||||
|
共 {{ devices.length }} 台机器人 / {{ cameraTotal }} 路视频
|
||||||
</div>
|
</div>
|
||||||
<div class="tree-device-list">
|
<div class="tree-panel-project-meta">当前目标:{{ activeSlotLabel }}</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<div v-if="listLoading" class="tree-loading">设备列表加载中...</div>
|
||||||
|
<div v-else-if="listError" class="tree-error">{{ listError }}</div>
|
||||||
|
<div v-else class="tree-device-list">
|
||||||
|
<div v-if="!devices.length" class="tree-list-empty">暂无机器人</div>
|
||||||
|
<div v-for="device in devices" :key="device.id" class="tree-robot-node">
|
||||||
|
<div class="tree-robot-header">
|
||||||
|
<span class="tree-device-name">{{ device.name }}</span>
|
||||||
|
<span class="tree-status-line">{{ device.status }} / 电量 {{ device.battery }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tree-device-id">
|
||||||
|
{{ device.deviceNo || device.id }} / {{ device.project }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="device.cameras.length" class="tree-camera-list">
|
||||||
<button
|
<button
|
||||||
v-for="camera in projectCameras"
|
v-for="camera in device.cameras"
|
||||||
:key="camera.id"
|
:key="camera.id"
|
||||||
:class="['tree-device-btn', { active: activeCameraId === camera.id }]"
|
:class="[
|
||||||
|
'tree-device-btn',
|
||||||
|
{ active: activeCamera?.id === camera.id },
|
||||||
|
]"
|
||||||
type="button"
|
type="button"
|
||||||
@click="selectCamera(camera.id)"
|
@click="playCamera({ ...camera, deviceId: device.id, deviceName: device.name, project: device.project })"
|
||||||
>
|
>
|
||||||
<span class="tree-device-name">{{ camera.name }}</span>
|
<span class="tree-device-name">{{ camera.name }}</span>
|
||||||
<span class="tree-device-id">{{ camera.deviceName }} / {{ camera.id }}</span>
|
<span class="tree-device-id">
|
||||||
|
{{ camera.cameraNo || camera.type }} / {{ camera.status }}
|
||||||
|
</span>
|
||||||
|
<span class="tree-device-action">播放到 {{ activeSlotLabel }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="tree-list-empty">暂无视频地址</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="tree-panel-footer">
|
<div class="tree-panel-footer">
|
||||||
<button class="tree-playback-btn" type="button" @click="openPlaybackDialog">
|
<button
|
||||||
|
class="tree-playback-btn"
|
||||||
|
type="button"
|
||||||
|
:disabled="!activeCamera"
|
||||||
|
@click="openPlaybackDialog"
|
||||||
|
>
|
||||||
录像回放
|
录像回放
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -238,8 +607,10 @@ function updatePlaybackProgress() {
|
|||||||
<div>
|
<div>
|
||||||
<div class="page-card-title">视频播放</div>
|
<div class="page-card-title">视频播放</div>
|
||||||
<div class="video-wall-meta">
|
<div class="video-wall-meta">
|
||||||
{{ currentProject.name }} / {{ activeCamera.name }}
|
{{ activeSlotLabel }} /
|
||||||
|
{{ activeCamera ? `${activeCamera.deviceName} - ${activeCamera.name}` : "未播放" }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="video-wall-meta">先选中右侧画面,再从左侧点击相机播放</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-switch">
|
<div class="grid-switch">
|
||||||
<button
|
<button
|
||||||
@@ -254,26 +625,54 @@ function updatePlaybackProgress() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="['video-grid-board', `grid-${gridMode}`]">
|
|
||||||
<div
|
<div
|
||||||
v-for="(camera, index) in visibleCameras"
|
ref="videoGridBoardRef"
|
||||||
:key="camera?.id ?? `empty-${index}`"
|
:class="['video-grid-board', `grid-${gridMode}`]"
|
||||||
class="video-grid-cell"
|
:style="videoGridBoardStyle"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(camera, index) in visibleSlots"
|
||||||
|
:key="camera?.id ? `${camera.id}-${index}` : `empty-${index}`"
|
||||||
|
:class="[
|
||||||
|
'video-grid-cell',
|
||||||
|
{ active: activeSlotIndex === index, empty: !camera },
|
||||||
|
]"
|
||||||
|
@click="selectVideoSlot(index)"
|
||||||
>
|
>
|
||||||
<template v-if="camera">
|
<template v-if="camera">
|
||||||
<video autoplay loop muted playsinline :src="monitorVideo"></video>
|
<video
|
||||||
|
:ref="(element) => setSlotVideoRef(element, index)"
|
||||||
|
autoplay
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
></video>
|
||||||
<div class="video-cell-top">
|
<div class="video-cell-top">
|
||||||
<span class="cell-badge">LIVE</span>
|
<button
|
||||||
<span class="cell-type">{{ camera.type }}</span>
|
class="video-close-btn"
|
||||||
|
type="button"
|
||||||
|
title="关闭画面"
|
||||||
|
@click.stop="closeVideoSlot(index)"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="video-cell-bottom">
|
<div class="video-cell-bottom">
|
||||||
|
<div>
|
||||||
<div class="cell-name">{{ camera.name }}</div>
|
<div class="cell-name">{{ camera.name }}</div>
|
||||||
<div class="cell-meta">{{ camera.deviceName }} / {{ camera.status }}</div>
|
<div class="cell-meta">{{ camera.deviceName }} / {{ camera.status }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="cell-meta">{{ activeSlotIndex === index ? "已选中" : `画面 ${index + 1}` }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="slotStreamStates[index]?.error" class="video-empty stream-message">
|
||||||
|
{{ slotStreamStates[index]?.error }}
|
||||||
|
</div>
|
||||||
|
<div v-else-if="slotStreamStates[index]?.loading" class="video-empty stream-message">
|
||||||
|
视频流连接中...
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="video-empty">
|
<div class="video-empty">
|
||||||
<span>未分配画面</span>
|
<span>{{ activeSlotIndex === index ? "已选中,点击左侧相机播放" : "点击选中画面" }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -314,7 +713,6 @@ function updatePlaybackProgress() {
|
|||||||
<div class="playback-preview">
|
<div class="playback-preview">
|
||||||
<video
|
<video
|
||||||
ref="playbackPlayerRef"
|
ref="playbackPlayerRef"
|
||||||
:src="monitorVideo"
|
|
||||||
playsinline
|
playsinline
|
||||||
@ended="syncPlaybackProgress"
|
@ended="syncPlaybackProgress"
|
||||||
@loadedmetadata="syncPlaybackProgress"
|
@loadedmetadata="syncPlaybackProgress"
|
||||||
@@ -322,6 +720,7 @@ function updatePlaybackProgress() {
|
|||||||
@play="syncPlaybackProgress"
|
@play="syncPlaybackProgress"
|
||||||
@timeupdate="syncPlaybackProgress"
|
@timeupdate="syncPlaybackProgress"
|
||||||
></video>
|
></video>
|
||||||
|
<div class="video-empty stream-message">录像回放需接入回放拉流地址</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="playback-dialog-controls">
|
<div class="playback-dialog-controls">
|
||||||
|
|||||||
162
src/views/dashboard1/pages/小车状态WS推送协议.md
Normal file
162
src/views/dashboard1/pages/小车状态WS推送协议.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# 小车状态 WS 推送协议
|
||||||
|
|
||||||
|
## 适用范围
|
||||||
|
|
||||||
|
本文档说明 ROS 小车状态通过 WebSocket 上报到后端,以及后端向浏览器推送 `car_status` 消息的对接方式。
|
||||||
|
|
||||||
|
- 浏览器订阅地址:`ws://{host}:{port}/ws/ros?token={token}`
|
||||||
|
- 小车设备连接地址:`ws://{host}:{port}/ws/ros?deviceKey={deviceKey}`
|
||||||
|
|
||||||
|
浏览器连接成功后无需额外发送订阅指令,后端会把收到的小车状态广播给当前所有在线浏览器会话。
|
||||||
|
|
||||||
|
消息统一使用 JSON,心跳兼容字符串消息:收到 `ping` 时回复 `pong`;主动发送 `ping` 时对端回复 `pong`。
|
||||||
|
|
||||||
|
## 小车上报消息
|
||||||
|
|
||||||
|
消息类型:`telemetry`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "telemetry",
|
||||||
|
"data": {
|
||||||
|
"batteryLevel": 86,
|
||||||
|
"voltage": 24.6,
|
||||||
|
"poseX": 12.345,
|
||||||
|
"poseY": 6.789,
|
||||||
|
"poseYaw": 1.57,
|
||||||
|
"status": 1,
|
||||||
|
"signal": "-58dBm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 必填 | 说明 |
|
||||||
|
| ------------------- | ---- | -------------------------------------------------- |
|
||||||
|
| `type` | 是 | 固定为 `telemetry` |
|
||||||
|
| `data.batteryLevel` | 否 | 电量百分比 |
|
||||||
|
| `data.voltage` | 否 | 电压 |
|
||||||
|
| `data.poseX` | 否 | 当前 X 坐标 |
|
||||||
|
| `data.poseY` | 否 | 当前 Y 坐标 |
|
||||||
|
| `data.poseYaw` | 否 | 当前朝向 |
|
||||||
|
| `data.status` | 否 | 小车状态:`0` 空闲,`1` 巡检,`2` 离线,`3` 充电中 |
|
||||||
|
| `data.signal` | 否 | 小车信号 |
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 支持增量上报,`data` 中只传有变化的字段即可。
|
||||||
|
- 后端只会更新本次上报里有值的字段,未传字段保持上一次状态不变。
|
||||||
|
- `deviceKey` 不需要放在消息体里,后端以连接参数 `deviceKey` 识别当前小车。
|
||||||
|
|
||||||
|
仅上报电量和状态示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "telemetry",
|
||||||
|
"data": {
|
||||||
|
"batteryLevel": 85,
|
||||||
|
"status": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 浏览器收到的推送
|
||||||
|
|
||||||
|
后端在处理完小车上报后,会向所有在线浏览器广播 `car_status` 消息。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "car_status",
|
||||||
|
"data": {
|
||||||
|
"deviceKey": "ROBOT-001",
|
||||||
|
"liftHeight": 1200,
|
||||||
|
"batteryLevel": 86,
|
||||||
|
"voltage": 24.6,
|
||||||
|
"poseX": 12.345,
|
||||||
|
"poseY": 6.789,
|
||||||
|
"poseYaw": 1.57,
|
||||||
|
"status": 1,
|
||||||
|
"signal": "-58dBm"
|
||||||
|
},
|
||||||
|
"timestamp": 1782702000010
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
顶层字段说明:
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
| ---------------- | ---------------------------------------- |
|
||||||
|
| `type` | 固定为 `car_status` |
|
||||||
|
| `data.deviceKey` | 小车设备标识 |
|
||||||
|
| `data.liftHeight` | 升降柱高度,单位 mm |
|
||||||
|
| `timestamp` | 后端推送时间戳,毫秒 |
|
||||||
|
|
||||||
|
|
||||||
|
## 小车速度上报消息
|
||||||
|
|
||||||
|
如果 ROS 端还需要把当前线速度、角速度实时推给浏览器,可额外发送 `velocity` 消息。该消息只做转发,不落库。
|
||||||
|
|
||||||
|
消息类型:`velocity`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "velocity",
|
||||||
|
"data": {
|
||||||
|
"linearX": 0.12,
|
||||||
|
"angularZ": -0.03
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 必填 | 说明 |
|
||||||
|
| --------------- | ---- | ---------------------------------------------------- |
|
||||||
|
| `type` | 是 | 固定为 `velocity` |
|
||||||
|
| `data.linearX` | 否 | 当前线速度,对应 ROS `geometry_msgs/Twist.linear.x` |
|
||||||
|
| `data.angularZ` | 否 | 当前角速度,对应 ROS `geometry_msgs/Twist.angular.z` |
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `velocity` 与 `telemetry` 分开处理,不写入 `ros_car`。
|
||||||
|
- 支持高频上报,后端收到后直接转发给浏览器。
|
||||||
|
- `deviceKey` 仍然从连接参数获取,不需要放在消息体里。
|
||||||
|
|
||||||
|
## 浏览器收到的速度推送
|
||||||
|
|
||||||
|
后端收到 ROS 端 `velocity` 后,会向所有在线浏览器广播 `car_velocity` 消息。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "car_velocity",
|
||||||
|
"data": {
|
||||||
|
"deviceKey": "ROBOT-001",
|
||||||
|
"velocity": {
|
||||||
|
"linearX": 0.12,
|
||||||
|
"angularZ": -0.03
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp": 1782702001010
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
| ------------------------ | --------------------- |
|
||||||
|
| `type` | 固定为 `car_velocity` |
|
||||||
|
| `data.deviceKey` | 小车设备标识 |
|
||||||
|
| `data.velocity.linearX` | 当前线速度 |
|
||||||
|
| `data.velocity.angularZ` | 当前角速度 |
|
||||||
|
| `timestamp` | 后端推送时间戳,毫秒 |
|
||||||
|
|
||||||
|
## 对接约束
|
||||||
|
|
||||||
|
- 浏览器侧只需要建立连接并监听 `car_status`,不需要发送订阅命令。
|
||||||
|
- 如果需要展示实时速度,浏览器额外监听 `car_velocity`。
|
||||||
|
- 同一条 `car_status` 中,`telemetry` 表示本次上报内容,`car` 表示合并后的完整结果;前端展示建议优先使用 `car`。
|
||||||
|
- 后端当前仅在收到小车 `telemetry` 上报后广播 `car_status`。
|
||||||
|
- 后端当前在收到小车 `velocity` 上报后广播 `car_velocity`,不做缓存和存储。
|
||||||
|
- 小车连接建立时,后端会将小车状态置为空闲;小车连接断开时,后端会将小车状态置为离线。
|
||||||
|
- 仅连接断开导致的离线变化,当前不会单独触发一条 `car_status` 广播;如前端需要强一致离线提示,需要额外结合列表查询或后续补充离线广播事件。
|
||||||
190
src/views/dashboard1/pages/浏览器WS控制协议.md
Normal file
190
src/views/dashboard1/pages/浏览器WS控制协议.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# 浏览器 WS 控制协议
|
||||||
|
|
||||||
|
## 连接
|
||||||
|
|
||||||
|
浏览器端使用登录 token 建立连接:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ws://{host}:{port}/ws/ros?token={token}
|
||||||
|
```
|
||||||
|
|
||||||
|
消息统一使用 JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "cmd_vel",
|
||||||
|
"requestId": "browser-generated-id",
|
||||||
|
"data": {
|
||||||
|
"deviceKey": "ROBOT-001",
|
||||||
|
"linearX": 0.1,
|
||||||
|
"angularZ": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
通用字段:
|
||||||
|
|
||||||
|
| 字段 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `type` | 是 | 消息类型 |
|
||||||
|
| `requestId` | 否 | 浏览器生成的请求 id,后端回执会原样带回 |
|
||||||
|
| `data.deviceKey` | 是 | 控制目标机器人设备标识 |
|
||||||
|
|
||||||
|
心跳兼容字符串消息:浏览器收到 `ping` 后回复 `pong`;浏览器主动发送 `ping` 时后端回复 `pong`。
|
||||||
|
|
||||||
|
## 后端回执
|
||||||
|
|
||||||
|
后端只确认消息是否成功转发到在线机器人,不等待机器人执行完成。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "control_result",
|
||||||
|
"requestId": "browser-generated-id",
|
||||||
|
"data": {
|
||||||
|
"sourceType": "cmd_vel",
|
||||||
|
"deviceKey": "ROBOT-001",
|
||||||
|
"accepted": true,
|
||||||
|
"message": "已下发"
|
||||||
|
},
|
||||||
|
"timestamp": 1782702000020
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`accepted=false` 常见原因:`data` 缺失、`deviceKey` 缺失、机器人不在线。
|
||||||
|
|
||||||
|
## 底盘控制
|
||||||
|
|
||||||
|
消息类型只使用 `cmd_vel`。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "cmd_vel",
|
||||||
|
"requestId": "cmd-1",
|
||||||
|
"data": {
|
||||||
|
"deviceKey": "ROBOT-001",
|
||||||
|
"linearX": 0.1,
|
||||||
|
"angularZ": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| `linearX` | 对应 ROS `geometry_msgs/Twist.linear.x` |
|
||||||
|
| `angularZ` | 对应 ROS `geometry_msgs/Twist.angular.z` |
|
||||||
|
|
||||||
|
按钮建议映射:
|
||||||
|
|
||||||
|
| 操作 | `linearX` | `angularZ` |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 前进 | `0.1` | `0` |
|
||||||
|
| 后退 | `-0.1` | `0` |
|
||||||
|
| 左转 | `0` | `0.1` |
|
||||||
|
| 右转 | `0` | `-0.1` |
|
||||||
|
| 停止 | `0` | `0` |
|
||||||
|
|
||||||
|
按钮按下时发送对应数值;按钮松开、指针移出、页面失焦时发送停止值。ROS 端 WebSocket 适配器负责把 `linearX/angularZ` 转成 `/cmd_vel` 的 `geometry_msgs/Twist`。
|
||||||
|
|
||||||
|
## 云台控制
|
||||||
|
|
||||||
|
ROS 包参考:`hikvision_camera_controller/srv/Ptz`
|
||||||
|
|
||||||
|
```text
|
||||||
|
string action
|
||||||
|
int32 speed
|
||||||
|
int32 duration_ms
|
||||||
|
```
|
||||||
|
|
||||||
|
浏览器协议只下发控制动作,`speed` 和 `duration_ms` 由 ROS 端适配器使用默认值。
|
||||||
|
|
||||||
|
消息类型:`ptz_control`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "ptz_control",
|
||||||
|
"requestId": "ptz-1",
|
||||||
|
"data": {
|
||||||
|
"deviceKey": "ROBOT-001",
|
||||||
|
"action": "up"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`action` 可选值:
|
||||||
|
|
||||||
|
| 值 | 含义 |
|
||||||
|
| --- | --- |
|
||||||
|
| `up` | 上 |
|
||||||
|
| `down` | 下 |
|
||||||
|
| `left` | 左 |
|
||||||
|
| `right` | 右 |
|
||||||
|
| `stop` | 停止 |
|
||||||
|
|
||||||
|
ROS 端调用 `/hikvision_camera/ptz` 时建议映射:
|
||||||
|
|
||||||
|
| 浏览器 `action` | ROS `action` | ROS `speed` | ROS `duration_ms` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `up` | `up` | 默认值,如 20 | 默认值,如 500 |
|
||||||
|
| `down` | `down` | 默认值,如 20 | 默认值,如 500 |
|
||||||
|
| `left` | `left` | 默认值,如 20 | 默认值,如 500 |
|
||||||
|
| `right` | `right` | 默认值,如 20 | 默认值,如 500 |
|
||||||
|
| `stop` | `stop` | 0 | 0 |
|
||||||
|
|
||||||
|
## 升降柱控制
|
||||||
|
|
||||||
|
ROS 包参考:`rs485_lift_controller/srv/ControlLift`
|
||||||
|
|
||||||
|
```text
|
||||||
|
int32 control_type
|
||||||
|
uint16 height
|
||||||
|
```
|
||||||
|
|
||||||
|
消息类型:`lift_control`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "lift_control",
|
||||||
|
"requestId": "lift-1",
|
||||||
|
"data": {
|
||||||
|
"deviceKey": "ROBOT-001",
|
||||||
|
"control_type": 4,
|
||||||
|
"height": 1200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`control_type` 可选值:
|
||||||
|
|
||||||
|
| 值 | 含义 | `height` |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `0` | 复位 | `0` |
|
||||||
|
| `1` | 上升 | `0` |
|
||||||
|
| `2` | 下降 | `0` |
|
||||||
|
| `3` | 停止 | `0` |
|
||||||
|
| `4` | 运行到指定高度 | 目标高度,单位 mm |
|
||||||
|
|
||||||
|
## 机器人端收到的消息
|
||||||
|
|
||||||
|
设备端连接:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ws://{host}:{port}/ws/ros?deviceKey={deviceKey}
|
||||||
|
```
|
||||||
|
|
||||||
|
后端按相同 `type` 透传 `data` 给设备。例如浏览器发送升降柱控制,设备端收到:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "lift_control",
|
||||||
|
"data": {
|
||||||
|
"deviceKey": "ROBOT-001",
|
||||||
|
"control_type": 4,
|
||||||
|
"height": 1200
|
||||||
|
},
|
||||||
|
"timestamp": 1782702000010
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
设备端应自行做安全保护:连接断开、长时间未收到控制消息、收到停止命令时立即停止对应动作。
|
||||||
@@ -67,6 +67,9 @@ export function createAlertPhoto(scene: AlertPhotoScene, accent: string) {
|
|||||||
|
|
||||||
export const recentAlertPhotoSources = {
|
export const recentAlertPhotoSources = {
|
||||||
"线缆破损": createAlertPhoto("cable", "#ff8f79"),
|
"线缆破损": createAlertPhoto("cable", "#ff8f79"),
|
||||||
|
"电缆表面破损": createAlertPhoto("cable", "#ff8f79"),
|
||||||
|
"桥架断裂": createAlertPhoto("crack", "#89d8ff"),
|
||||||
|
"隧道积水": createAlertPhoto("heat", "#ffc86c"),
|
||||||
"局部高温": createAlertPhoto("heat", "#ffc86c"),
|
"局部高温": createAlertPhoto("heat", "#ffc86c"),
|
||||||
"墙体裂缝": createAlertPhoto("crack", "#89d8ff"),
|
"墙体裂缝": createAlertPhoto("crack", "#89d8ff"),
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
203
src/views/login/index.vue
Normal file
203
src/views/login/index.vue
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, reactive, ref } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { useUserStore } from "@/store/modules/user.js";
|
||||||
|
import { encryptPasswordPayload } from '@/utils/passwordEncrypt.js'
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const errorMessage = ref("");
|
||||||
|
|
||||||
|
const redirectPath = computed(() => {
|
||||||
|
const redirect = route.query.redirect;
|
||||||
|
if (
|
||||||
|
typeof redirect === "string" &&
|
||||||
|
redirect.startsWith("/") &&
|
||||||
|
!redirect.startsWith("//")
|
||||||
|
) {
|
||||||
|
return redirect;
|
||||||
|
}
|
||||||
|
return "/dashboard/integrated-center";
|
||||||
|
});
|
||||||
|
|
||||||
|
// 校验登录表单,避免空账号或空密码提交到后端。
|
||||||
|
function validateLoginForm() {
|
||||||
|
if (!form.username.trim()) {
|
||||||
|
errorMessage.value = "请输入账号";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!form.password) {
|
||||||
|
errorMessage.value = "请输入密码";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交登录信息,成功后回到登录前访问的页面。
|
||||||
|
async function handleLogin() {
|
||||||
|
if (!validateLoginForm() || loading.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
errorMessage.value = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const encryptedForm = await encryptPasswordPayload(form, ['password'])
|
||||||
|
await userStore.login(encryptedForm)
|
||||||
|
router.replace(redirectPath.value);
|
||||||
|
} catch (error) {
|
||||||
|
errorMessage.value =
|
||||||
|
error instanceof Error ? error.message : "登录失败,请检查账号或密码";
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="login-page">
|
||||||
|
<form class="login-card" @submit.prevent="handleLogin">
|
||||||
|
<div class="login-card__eyebrow">Robot Manager</div>
|
||||||
|
<h1>系统登录</h1>
|
||||||
|
<p>请输入账号和密码进入巡检管理大屏</p>
|
||||||
|
|
||||||
|
<label class="login-field">
|
||||||
|
<span>账号</span>
|
||||||
|
<input v-model="form.username" autocomplete="username" placeholder="请输入账号" type="text" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="login-field">
|
||||||
|
<span>密码</span>
|
||||||
|
<input v-model="form.password" autocomplete="current-password" placeholder="请输入密码" type="password" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div v-if="errorMessage" class="login-error">{{ errorMessage }}</div>
|
||||||
|
|
||||||
|
<button class="login-button" :disabled="loading" type="submit">
|
||||||
|
{{ loading ? "登录中..." : "登录" }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-page {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: #dff7ff;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 42%,
|
||||||
|
rgba(31, 126, 202, 0.32),
|
||||||
|
transparent 34%),
|
||||||
|
radial-gradient(circle at center, #15385d 0%, #061426 60%, #02070f 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 440px;
|
||||||
|
padding: 44px 48px 48px;
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid rgba(116, 236, 255, 0.28);
|
||||||
|
border-radius: 18px;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(7, 31, 61, 0.9),
|
||||||
|
rgba(4, 15, 31, 0.86));
|
||||||
|
box-shadow:
|
||||||
|
0 0 40px rgba(69, 188, 255, 0.18),
|
||||||
|
inset 0 0 26px rgba(116, 236, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card__eyebrow {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #74ecff;
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.22em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card h1 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 34px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card p {
|
||||||
|
margin: 0 0 34px;
|
||||||
|
color: rgba(223, 247, 255, 0.72);
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field span {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: rgba(223, 247, 255, 0.78);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
height: 46px;
|
||||||
|
padding: 0 16px;
|
||||||
|
color: #e8fbff;
|
||||||
|
border: 1px solid rgba(116, 236, 255, 0.26);
|
||||||
|
border-radius: 8px;
|
||||||
|
outline: none;
|
||||||
|
background: rgba(3, 15, 32, 0.74);
|
||||||
|
transition:
|
||||||
|
border-color 0.2s,
|
||||||
|
box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field input:focus {
|
||||||
|
border-color: rgba(116, 236, 255, 0.78);
|
||||||
|
box-shadow: 0 0 18px rgba(116, 236, 255, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field input::placeholder {
|
||||||
|
color: rgba(223, 247, 255, 0.34);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-error {
|
||||||
|
min-height: 20px;
|
||||||
|
margin: -4px 0 16px;
|
||||||
|
color: #ff9c9c;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-button {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
color: #062239;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: linear-gradient(90deg, #74ecff, #46a7ff);
|
||||||
|
box-shadow: 0 0 24px rgba(70, 167, 255, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-button:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.68;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -17,5 +17,5 @@
|
|||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/api/rosCar.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,44 @@
|
|||||||
import {defineConfig} from "vite";
|
import vue from '@vitejs/plugin-vue'
|
||||||
import vue from "@vitejs/plugin-vue";
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
import { resolve } from 'path'
|
||||||
import path from "path";
|
|
||||||
|
// import { viteSingleFile } from 'vite-plugin-singlefile'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default ({ command }) => {
|
||||||
base: "./",
|
const isBuild = command === 'build'
|
||||||
plugins: [vue(), vueJsx()],
|
return ({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueJsx(),
|
||||||
|
],
|
||||||
|
define: {
|
||||||
|
__VUE_OPTIONS_API__: false // 关闭 Vue2 中的 options选项API
|
||||||
|
},
|
||||||
|
base: './', // 使用相对路径
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: [{ find: '@', replacement: resolve(__dirname, 'src') }]
|
||||||
"@": path.resolve(__dirname, "./src"),
|
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
port: 6003,
|
||||||
|
host: true,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: `http://192.168.3.110:16003/`,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
},
|
},
|
||||||
});
|
'/ws': {
|
||||||
|
target: `ws://192.168.3.110:16003/`,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
oxc: {
|
||||||
|
//清除全局的console.log和debug
|
||||||
|
drop: isBuild ? ['console', 'debugger'] : [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user