Procházet zdrojové kódy

设备变化记录

李富豪 před 1 rokem
rodič
revize
285120afc8
44 změnil soubory, kde provedl 1473 přidání a 1130 odebrání
  1. 206 109
      Web/package-lock.json
  2. 5 5
      Web/package.json
  3. 1 0
      Web/src/App.vue
  4. 0 11
      Web/src/antd.ts
  5. 28 0
      Web/src/api/custom/index.ts
  6. 3 2
      Web/src/api/http/config.ts
  7. 1 2
      Web/src/components/MediaPanel.vue
  8. 7 13
      Web/src/components/common/nav.vue
  9. 7 7
      Web/src/components/common/sidebar.vue
  10. 5 26
      Web/src/components/common/topbar.vue
  11. 36 0
      Web/src/components/devices/changeRecord/components/Drawer.vue
  12. 169 0
      Web/src/components/devices/changeRecord/index.vue
  13. 37 61
      Web/src/components/devices/device-hms/DeviceHmsDrawer.vue
  14. 43 52
      Web/src/components/devices/device-log/DeviceLogUploadRecordDrawer.vue
  15. 421 0
      Web/src/components/devices/deviceList/index.vue
  16. 11 0
      Web/src/components/devices/feedbackRecord/index.vue
  17. 93 85
      Web/src/components/task/CreatePlan.vue
  18. 1 2
      Web/src/components/task/TaskPanel.vue
  19. 1 1
      Web/src/hooks/use-connect-websocket.ts
  20. 2 2
      Web/src/hooks/use-map-tool.ts
  21. 8 8
      Web/src/hooks/use-mouse-tool.ts
  22. 5 2
      Web/src/main.ts
  23. 8 1
      Web/src/pages/page-web/home.vue
  24. 2 2
      Web/src/pages/page-web/index.vue
  25. 19 426
      Web/src/pages/page-web/projects/devices.vue
  26. 14 9
      Web/src/pages/page-web/projects/dock.vue
  27. 42 81
      Web/src/pages/page-web/projects/layer.vue
  28. 0 10
      Web/src/pages/page-web/projects/media.vue
  29. 11 0
      Web/src/pages/page-web/projects/media/index.vue
  30. 11 0
      Web/src/pages/page-web/projects/replay/index.vue
  31. 5 5
      Web/src/pages/page-web/projects/task.vue
  32. 11 0
      Web/src/pages/page-web/projects/trajectory/index.vue
  33. 159 84
      Web/src/pages/page-web/projects/tsa.vue
  34. 1 2
      Web/src/pages/page-web/projects/wayline.vue
  35. 28 15
      Web/src/pages/page-web/projects/workspace.vue
  36. 14 41
      Web/src/router/index.ts
  37. 29 29
      Web/src/store/index.ts
  38. 11 15
      Web/src/styles/common.scss
  39. 1 1
      Web/src/types/enums.ts
  40. 4 4
      Web/src/types/flight-area.ts
  41. 4 9
      Web/src/utils/bytes.ts
  42. 2 2
      Web/src/utils/error-code/index.ts
  43. 4 4
      Web/src/utils/time.ts
  44. 3 2
      Web/vite.config.ts

+ 206 - 109
Web/package-lock.json

@@ -11,17 +11,17 @@
       "dependencies": {
         "@amap/amap-jsapi-loader": "^1.0.1",
         "agora-rtc-sdk-ng": "^4.12.1",
-        "ant-design-vue": "2.2.8",
+        "ant-design-vue": "^2.2.8",
         "axios": "^0.21.1",
         "eventemitter3": "^5.0.0",
         "mitt": "^3.0.0",
+        "moment": "^2.30.0",
         "mqtt": "^4.3.7",
         "reconnecting-websocket": "^4.4.0",
         "vconsole": "^3.15.0",
-        "vue": "^3.2.0",
-        "vue-i18n": "^9.13.0",
-        "vue-router": "4",
-        "vuex": "^4.0.0"
+        "vue": "3.2.26",
+        "vue-router": "^4.3.0",
+        "vuex": "^4.1.0"
       },
       "devDependencies": {
         "@types/node": "^18.15.0",
@@ -521,51 +521,11 @@
         "node": ">=12"
       }
     },
-    "node_modules/@intlify/core-base": {
-      "version": "9.13.1",
-      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.13.1.tgz",
-      "integrity": "sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w==",
-      "dependencies": {
-        "@intlify/message-compiler": "9.13.1",
-        "@intlify/shared": "9.13.1"
-      },
-      "engines": {
-        "node": ">= 16"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/kazupon"
-      }
-    },
-    "node_modules/@intlify/message-compiler": {
-      "version": "9.13.1",
-      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.13.1.tgz",
-      "integrity": "sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w==",
-      "dependencies": {
-        "@intlify/shared": "9.13.1",
-        "source-map-js": "^1.0.2"
-      },
-      "engines": {
-        "node": ">= 16"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/kazupon"
-      }
-    },
-    "node_modules/@intlify/shared": {
-      "version": "9.13.1",
-      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.13.1.tgz",
-      "integrity": "sha512-u3b6BKGhE6j/JeRU6C/RL2FgyJfy6LakbtfeVF8fJXURpZZTzfh3e05J0bu0XPw447Q6/WUp3C4ajv4TMS4YsQ==",
-      "engines": {
-        "node": ">= 16"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/kazupon"
-      }
-    },
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
-      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+      "peer": true
     },
     "node_modules/@rollup/rollup-android-arm-eabi": {
       "version": "4.18.0",
@@ -816,6 +776,7 @@
       "version": "3.4.29",
       "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.29.tgz",
       "integrity": "sha512-TFKiRkKKsRCKvg/jTSSKK7mYLJEQdUiUfykbG49rubC9SfDyvT2JrzTReopWlz2MxqeLyxh9UZhvxEIBgAhtrg==",
+      "peer": true,
       "dependencies": {
         "@babel/parser": "^7.24.7",
         "@vue/shared": "3.4.29",
@@ -828,6 +789,7 @@
       "version": "3.4.29",
       "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.29.tgz",
       "integrity": "sha512-A6+iZ2fKIEGnfPJejdB7b1FlJzgiD+Y/sxxKwJWg1EbJu6ZPgzaPQQ51ESGNv0CP6jm6Z7/pO6Ia8Ze6IKrX7w==",
+      "peer": true,
       "dependencies": {
         "@vue/compiler-core": "3.4.29",
         "@vue/shared": "3.4.29"
@@ -837,6 +799,7 @@
       "version": "3.4.29",
       "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.29.tgz",
       "integrity": "sha512-zygDcEtn8ZimDlrEQyLUovoWgKQic6aEQqRXce2WXBvSeHbEbcAsXyCk9oG33ZkyWH4sl9D3tkYc1idoOkdqZQ==",
+      "peer": true,
       "dependencies": {
         "@babel/parser": "^7.24.7",
         "@vue/compiler-core": "3.4.29",
@@ -853,6 +816,7 @@
       "version": "3.4.29",
       "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.29.tgz",
       "integrity": "sha512-rFbwCmxJ16tDp3N8XCx5xSQzjhidYjXllvEcqX/lopkoznlNPz3jyy0WGJCyhAaVQK677WWFt3YO/WUEkMMUFQ==",
+      "peer": true,
       "dependencies": {
         "@vue/compiler-dom": "3.4.29",
         "@vue/shared": "3.4.29"
@@ -864,49 +828,134 @@
       "integrity": "sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw=="
     },
     "node_modules/@vue/reactivity": {
-      "version": "3.4.29",
-      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.29.tgz",
-      "integrity": "sha512-w8+KV+mb1a8ornnGQitnMdLfE0kXmteaxLdccm2XwdFxXst4q/Z7SEboCV5SqJNpZbKFeaRBBJBhW24aJyGINg==",
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.26.tgz",
+      "integrity": "sha512-h38bxCZLW6oFJVDlCcAiUKFnXI8xP8d+eO0pcDxx+7dQfSPje2AO6M9S9QO6MrxQB7fGP0DH0dYQ8ksf6hrXKQ==",
       "dependencies": {
-        "@vue/shared": "3.4.29"
+        "@vue/shared": "3.2.26"
+      }
+    },
+    "node_modules/@vue/reactivity-transform": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.26.tgz",
+      "integrity": "sha512-XKMyuCmzNA7nvFlYhdKwD78rcnmPb7q46uoR00zkX6yZrUmcCQ5OikiwUEVbvNhL5hBJuvbSO95jB5zkUon+eQ==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.26",
+        "@vue/shared": "3.2.26",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7"
       }
     },
+    "node_modules/@vue/reactivity-transform/node_modules/@vue/compiler-core": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.26.tgz",
+      "integrity": "sha512-N5XNBobZbaASdzY9Lga2D9Lul5vdCIOXvUMd6ThcN8zgqQhPKfCV+wfAJNNJKQkSHudnYRO2gEB+lp0iN3g2Tw==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/shared": "3.2.26",
+        "estree-walker": "^2.0.2",
+        "source-map": "^0.6.1"
+      }
+    },
+    "node_modules/@vue/reactivity-transform/node_modules/@vue/shared": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.26.tgz",
+      "integrity": "sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA=="
+    },
+    "node_modules/@vue/reactivity-transform/node_modules/magic-string": {
+      "version": "0.25.9",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+      "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+      "dependencies": {
+        "sourcemap-codec": "^1.4.8"
+      }
+    },
+    "node_modules/@vue/reactivity/node_modules/@vue/shared": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.26.tgz",
+      "integrity": "sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA=="
+    },
     "node_modules/@vue/runtime-core": {
-      "version": "3.4.29",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.29.tgz",
-      "integrity": "sha512-s8fmX3YVR/Rk5ig0ic0NuzTNjK2M7iLuVSZyMmCzN/+Mjuqqif1JasCtEtmtoJWF32pAtUjyuT2ljNKNLeOmnQ==",
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.26.tgz",
+      "integrity": "sha512-BcYi7qZ9Nn+CJDJrHQ6Zsmxei2hDW0L6AB4vPvUQGBm2fZyC0GXd/4nVbyA2ubmuhctD5RbYY8L+5GUJszv9mQ==",
       "dependencies": {
-        "@vue/reactivity": "3.4.29",
-        "@vue/shared": "3.4.29"
+        "@vue/reactivity": "3.2.26",
+        "@vue/shared": "3.2.26"
       }
     },
+    "node_modules/@vue/runtime-core/node_modules/@vue/shared": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.26.tgz",
+      "integrity": "sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA=="
+    },
     "node_modules/@vue/runtime-dom": {
-      "version": "3.4.29",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.29.tgz",
-      "integrity": "sha512-gI10atCrtOLf/2MPPMM+dpz3NGulo9ZZR9d1dWo4fYvm+xkfvRrw1ZmJ7mkWtiJVXSsdmPbcK1p5dZzOCKDN0g==",
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.26.tgz",
+      "integrity": "sha512-dY56UIiZI+gjc4e8JQBwAifljyexfVCkIAu/WX8snh8vSOt/gMSEGwPRcl2UpYpBYeyExV8WCbgvwWRNt9cHhQ==",
       "dependencies": {
-        "@vue/reactivity": "3.4.29",
-        "@vue/runtime-core": "3.4.29",
-        "@vue/shared": "3.4.29",
-        "csstype": "^3.1.3"
+        "@vue/runtime-core": "3.2.26",
+        "@vue/shared": "3.2.26",
+        "csstype": "^2.6.8"
       }
     },
+    "node_modules/@vue/runtime-dom/node_modules/@vue/shared": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.26.tgz",
+      "integrity": "sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA=="
+    },
     "node_modules/@vue/server-renderer": {
-      "version": "3.4.29",
-      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.29.tgz",
-      "integrity": "sha512-HMLCmPI2j/k8PVkSBysrA2RxcxC5DgBiCdj7n7H2QtR8bQQPqKAe8qoaxLcInzouBmzwJ+J0x20ygN/B5mYBng==",
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.26.tgz",
+      "integrity": "sha512-Jp5SggDUvvUYSBIvYEhy76t4nr1vapY/FIFloWmQzn7UxqaHrrBpbxrqPcTrSgGrcaglj0VBp22BKJNre4aA1w==",
       "dependencies": {
-        "@vue/compiler-ssr": "3.4.29",
-        "@vue/shared": "3.4.29"
+        "@vue/compiler-ssr": "3.2.26",
+        "@vue/shared": "3.2.26"
       },
       "peerDependencies": {
-        "vue": "3.4.29"
+        "vue": "3.2.26"
+      }
+    },
+    "node_modules/@vue/server-renderer/node_modules/@vue/compiler-core": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.26.tgz",
+      "integrity": "sha512-N5XNBobZbaASdzY9Lga2D9Lul5vdCIOXvUMd6ThcN8zgqQhPKfCV+wfAJNNJKQkSHudnYRO2gEB+lp0iN3g2Tw==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/shared": "3.2.26",
+        "estree-walker": "^2.0.2",
+        "source-map": "^0.6.1"
+      }
+    },
+    "node_modules/@vue/server-renderer/node_modules/@vue/compiler-dom": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.26.tgz",
+      "integrity": "sha512-smBfaOW6mQDxcT3p9TKT6mE22vjxjJL50GFVJiI0chXYGU/xzC05QRGrW3HHVuJrmLTLx5zBhsZ2dIATERbarg==",
+      "dependencies": {
+        "@vue/compiler-core": "3.2.26",
+        "@vue/shared": "3.2.26"
+      }
+    },
+    "node_modules/@vue/server-renderer/node_modules/@vue/compiler-ssr": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.26.tgz",
+      "integrity": "sha512-2mywLX0ODc4Zn8qBoA2PDCsLEZfpUGZcyoFRLSOjyGGK6wDy2/5kyDOWtf0S0UvtoyVq95OTSGIALjZ4k2q/ag==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.2.26",
+        "@vue/shared": "3.2.26"
       }
     },
+    "node_modules/@vue/server-renderer/node_modules/@vue/shared": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.26.tgz",
+      "integrity": "sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA=="
+    },
     "node_modules/@vue/shared": {
       "version": "3.4.29",
       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz",
-      "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA=="
+      "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==",
+      "peer": true
     },
     "node_modules/agora-rtc-sdk-ng": {
       "version": "4.21.0",
@@ -1190,9 +1239,9 @@
       }
     },
     "node_modules/csstype": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
-      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+      "version": "2.6.21",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
+      "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
     },
     "node_modules/debug": {
       "version": "4.3.5",
@@ -1251,6 +1300,7 @@
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
       "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "peer": true,
       "engines": {
         "node": ">=0.12"
       },
@@ -1591,6 +1641,7 @@
       "version": "0.30.10",
       "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
       "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
+      "peer": true,
       "dependencies": {
         "@jridgewell/sourcemap-codec": "^1.4.15"
       }
@@ -1984,6 +2035,14 @@
       "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
       "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
     },
+    "node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/source-map-js": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
@@ -1992,6 +2051,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/sourcemap-codec": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+      "deprecated": "Please use @jridgewell/sourcemap-codec instead"
+    },
     "node_modules/split2": {
       "version": "3.2.2",
       "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
@@ -2034,7 +2099,7 @@
       "version": "5.5.2",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
       "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
-      "devOptional": true,
+      "dev": true,
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -2149,42 +2214,15 @@
       "dev": true
     },
     "node_modules/vue": {
-      "version": "3.4.29",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.29.tgz",
-      "integrity": "sha512-8QUYfRcYzNlYuzKPfge1UWC6nF9ym0lx7mpGVPJYNhddxEf3DD0+kU07NTL0sXuiT2HuJuKr/iEO8WvXvT0RSQ==",
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.26.tgz",
+      "integrity": "sha512-KD4lULmskL5cCsEkfhERVRIOEDrfEL9CwAsLYpzptOGjaGFNWo3BQ9g8MAb7RaIO71rmVOziZ/uEN/rHwcUIhg==",
       "dependencies": {
-        "@vue/compiler-dom": "3.4.29",
-        "@vue/compiler-sfc": "3.4.29",
-        "@vue/runtime-dom": "3.4.29",
-        "@vue/server-renderer": "3.4.29",
-        "@vue/shared": "3.4.29"
-      },
-      "peerDependencies": {
-        "typescript": "*"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/vue-i18n": {
-      "version": "9.13.1",
-      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.13.1.tgz",
-      "integrity": "sha512-mh0GIxx0wPtPlcB1q4k277y0iKgo25xmDPWioVVYanjPufDBpvu5ySTjP5wOrSvlYQ2m1xI+CFhGdauv/61uQg==",
-      "dependencies": {
-        "@intlify/core-base": "9.13.1",
-        "@intlify/shared": "9.13.1",
-        "@vue/devtools-api": "^6.5.0"
-      },
-      "engines": {
-        "node": ">= 16"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/kazupon"
-      },
-      "peerDependencies": {
-        "vue": "^3.0.0"
+        "@vue/compiler-dom": "3.2.26",
+        "@vue/compiler-sfc": "3.2.26",
+        "@vue/runtime-dom": "3.2.26",
+        "@vue/server-renderer": "3.2.26",
+        "@vue/shared": "3.2.26"
       }
     },
     "node_modules/vue-router": {
@@ -2215,6 +2253,65 @@
         "vue": "^3.0.0"
       }
     },
+    "node_modules/vue/node_modules/@vue/compiler-core": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.26.tgz",
+      "integrity": "sha512-N5XNBobZbaASdzY9Lga2D9Lul5vdCIOXvUMd6ThcN8zgqQhPKfCV+wfAJNNJKQkSHudnYRO2gEB+lp0iN3g2Tw==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/shared": "3.2.26",
+        "estree-walker": "^2.0.2",
+        "source-map": "^0.6.1"
+      }
+    },
+    "node_modules/vue/node_modules/@vue/compiler-dom": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.26.tgz",
+      "integrity": "sha512-smBfaOW6mQDxcT3p9TKT6mE22vjxjJL50GFVJiI0chXYGU/xzC05QRGrW3HHVuJrmLTLx5zBhsZ2dIATERbarg==",
+      "dependencies": {
+        "@vue/compiler-core": "3.2.26",
+        "@vue/shared": "3.2.26"
+      }
+    },
+    "node_modules/vue/node_modules/@vue/compiler-sfc": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.26.tgz",
+      "integrity": "sha512-ePpnfktV90UcLdsDQUh2JdiTuhV0Skv2iYXxfNMOK/F3Q+2BO0AulcVcfoksOpTJGmhhfosWfMyEaEf0UaWpIw==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.26",
+        "@vue/compiler-dom": "3.2.26",
+        "@vue/compiler-ssr": "3.2.26",
+        "@vue/reactivity-transform": "3.2.26",
+        "@vue/shared": "3.2.26",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7",
+        "postcss": "^8.1.10",
+        "source-map": "^0.6.1"
+      }
+    },
+    "node_modules/vue/node_modules/@vue/compiler-ssr": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.26.tgz",
+      "integrity": "sha512-2mywLX0ODc4Zn8qBoA2PDCsLEZfpUGZcyoFRLSOjyGGK6wDy2/5kyDOWtf0S0UvtoyVq95OTSGIALjZ4k2q/ag==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.2.26",
+        "@vue/shared": "3.2.26"
+      }
+    },
+    "node_modules/vue/node_modules/@vue/shared": {
+      "version": "3.2.26",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.26.tgz",
+      "integrity": "sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA=="
+    },
+    "node_modules/vue/node_modules/magic-string": {
+      "version": "0.25.9",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+      "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+      "dependencies": {
+        "sourcemap-codec": "^1.4.8"
+      }
+    },
     "node_modules/vuex": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",

+ 5 - 5
Web/package.json

@@ -11,17 +11,17 @@
   "dependencies": {
     "@amap/amap-jsapi-loader": "^1.0.1",
     "agora-rtc-sdk-ng": "^4.12.1",
-    "ant-design-vue": "2.2.8",
+    "ant-design-vue": "^2.2.8",
     "axios": "^0.21.1",
     "eventemitter3": "^5.0.0",
     "mitt": "^3.0.0",
+    "moment": "^2.30.0",
     "mqtt": "^4.3.7",
     "reconnecting-websocket": "^4.4.0",
     "vconsole": "^3.15.0",
-    "vue": "^3.2.0",
-    "vue-i18n": "^9.13.0",
-    "vue-router": "4",
-    "vuex": "^4.0.0"
+    "vue": "3.2.26",
+    "vue-router": "^4.3.0",
+    "vuex": "^4.1.0"
   },
   "devDependencies": {
     "@types/node": "^18.15.0",

+ 1 - 0
Web/src/App.vue

@@ -14,6 +14,7 @@ import { defineComponent } from 'vue'
 import { useMyStore } from './store'
 import GMap from '/@/components/GMap.vue'
 import zhCN from 'ant-design-vue/es/locale/zh_CN';
+import 'moment/dist/locale/zh-cn';
 
 export default defineComponent({
   name: 'App',

+ 0 - 11
Web/src/antd.ts

@@ -1,11 +0,0 @@
-import { App } from 'vue'
-import * as antDesign from 'ant-design-vue'
-import svgIcon from '/@/components/svgIcon.vue'
-import 'ant-design-vue/dist/antd.css'
-
-export const antComponents = {
-  install(app: App): void {
-    app.use(antDesign)
-    app.component('svg-icon', svgIcon)
-  }
-}

+ 28 - 0
Web/src/api/custom/index.ts

@@ -0,0 +1,28 @@
+import request from '/@/api/http/request';
+
+// Api参数类型
+export type FetchFeedbackRecordListApiParams = {
+    page: number,
+    page_size: number,
+};
+
+// Api函数类型
+export type FetchFeedbackRecordListApi = (data: FetchFeedbackRecordListApiParams) => Promise<any>;
+export type FetchChangeRecordListApi = () => Promise<any>;
+
+// 获取反馈记录列表
+const fetchFeedbackRecordListApi: FetchFeedbackRecordListApi = async (data) => {
+    const res = await request.get('/manage/api/v1/devices', { params: data });
+    return res.data;
+};
+
+// 获取变化记录列表
+const fetchChangeRecordListApi: FetchChangeRecordListApi = async () => {
+    const res = await request.get('/manage/api/v1/workspaces/current');
+    return res.data;
+};
+
+export const apis = {
+    fetchFeedbackRecordList: fetchFeedbackRecordListApi,
+    fetchChangeRecordList: fetchChangeRecordListApi,
+};

+ 3 - 2
Web/src/api/http/config.ts

@@ -4,8 +4,9 @@ export const CURRENT_CONFIG = {
   appKey: '7c9e9108f2ddcbab32d2b508f452151',
   appLicense: 'ZK7Dzih4Qc9JCZhDiyDsWJwTW+1rhnnzT1SqDxbdSPVV24bbDC4r1KNjXo7tIPBnPne7ipnXeefP0lJ0OHvxMpkKiag5lFCIndKSvYYdQkyScT3dahCXjmYsd0YyWyHj4tvXoR2DRVq1PdBHLB1iUo2FGLCIZ8QHbGyqglyGdHY=',
   // 网络请求
-  baseURL: '/api', 
-  websocketURL: 'ws://192.168.3.42:6789/api/v1/ws', // Example: 'ws://192.168.1.1:6789/api/v1/ws'
+  baseURL: '/api',
+  apiURL: 'http://192.168.3.42:6789',
+  websocketURL: 'ws://192.168.3.42:6789/api/v1/ws',
   // livestreaming
   // RTMP  Note: This IP is the address of the streaming server. If you want to see livestream on web page, you need to convert the RTMP stream to WebRTC stream.
   rtmpURL: 'Please enter the rtmp access address.', // Example: 'rtmp://192.168.1.1/live/'

+ 1 - 2
Web/src/components/MediaPanel.vue

@@ -24,9 +24,8 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from '@vue/reactivity'
 import { TableState } from 'ant-design-vue/lib/table/interface'
-import { onMounted, reactive } from 'vue'
+import { onMounted, reactive,ref} from 'vue'
 import { IPage } from '../api/http/type'
 import { ELocalStorageKey } from '../types/enums'
 import { downloadFile } from '../utils/common'

+ 7 - 13
Web/src/components/common/nav.vue

@@ -1,12 +1,6 @@
 <template>
   <a-layout-sider collapsible>
     <a-menu theme="dark" mode="inline" v-model:selectedKeys="state.selectedKeys" @select="handleSelect">
-      <a-menu-item :key="'/' + ERouterName.WORKSPACE">
-        <MacCommandOutlined />
-        <span>
-          项目工作空间
-        </span>
-      </a-menu-item>
       <a-menu-item :key="'/' + ERouterName.DEVICES">
         <HddOutlined />
         <span>
@@ -37,12 +31,18 @@
           轨迹回放
         </span>
       </a-menu-item>
+      <a-menu-item :key="'/' + ERouterName.MEMBERS">
+        <TeamOutlined />
+        <span>
+          成员管理
+        </span>
+      </a-menu-item>
     </a-menu>
   </a-layout-sider>
 </template>
 <script lang="ts" setup>
 import { reactive, watchEffect } from 'vue';
-import { GatewayOutlined, ContainerOutlined, HddOutlined, PictureOutlined, VideoCameraOutlined, MacCommandOutlined } from '@ant-design/icons-vue';
+import { GatewayOutlined, ContainerOutlined, HddOutlined, PictureOutlined, VideoCameraOutlined, TeamOutlined } from '@ant-design/icons-vue';
 import { ERouterName } from '/@/types/enums';
 import router from '/@/router';
 
@@ -63,12 +63,6 @@ watchEffect(() => {
 // 选择菜单
 const handleSelect = (item: any) => {
   router.push({ path: item.key })
-  // state.selectedKeys = [item.key];
-
-
-  // router
-  // const menuLevel = item.keyPath.length > 1 ? 2 : 1;
-  // // props.onChangeSelectedKey(item.key, menuLevel);
 }
 </script>
 

+ 7 - 7
Web/src/components/common/sidebar.vue

@@ -22,8 +22,8 @@
 
 <script lang="ts">
 import { createVNode, defineComponent } from 'vue'
-import { getRoot } from '/@/root'
 import * as icons from '@ant-design/icons-vue'
+import { getRoot } from '/@/root'
 import { ERouterName } from '/@/types'
 
 interface IOptions {
@@ -51,12 +51,12 @@ export default defineComponent({
     const root = getRoot()
     const options = [
       { key: 0, label: 'Tsa', path: '/' + ERouterName.TSA, icon: 'TeamOutlined' },
-      // { key: 1, label: 'Livestream', path: '/' + ERouterName.LIVESTREAM, icon: 'VideoCameraOutlined' },
-      // { key: 2, label: 'Annotations', path: '/' + ERouterName.LAYER, icon: 'EnvironmentOutlined' },
-      // { key: 3, label: 'Media Files', path: '/' + ERouterName.MEDIA, icon: 'PictureOutlined' },
-      // { key: 4, label: 'Flight Route Library', path: '/' + ERouterName.WAYLINE, icon: 'NodeIndexOutlined' },
-      // { key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' },
-      // { key: 6, label: 'Flight Area', path: '/' + ERouterName.FLIGHT_AREA, icon: 'GroupOutlined' },
+      { key: 1, label: 'Livestream', path: '/' + ERouterName.LIVESTREAM, icon: 'VideoCameraOutlined' },
+      { key: 2, label: 'Annotations', path: '/' + ERouterName.LAYER, icon: 'EnvironmentOutlined' },
+      { key: 3, label: 'Media Files', path: '/' + ERouterName.MEDIA, icon: 'PictureOutlined' },
+      { key: 4, label: 'Flight Route Library', path: '/' + ERouterName.WAYLINE, icon: 'NodeIndexOutlined' },
+      { key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' },
+      { key: 6, label: 'Flight Area', path: '/' + ERouterName.FLIGHT_AREA, icon: 'GroupOutlined' },
     ]
 
     function selectedRoute(item: IOptions) {

+ 5 - 26
Web/src/components/common/topbar.vue

@@ -2,10 +2,9 @@
   <div class="width-100 flex-row flex-justify-between flex-align-center"
     style="height: 60px;border-bottom: 1px solid #F0F2F5;">
     <div>
-      <a-avatar :size="40" shape="square" :src="cloudapi" />
-      <span class="ml10">
-        上云
-      </span>
+      <a-button type="primary" @click="onClickToWorkspace">
+        项目工作空间
+      </a-button>
     </div>
     <div style="cursor: pointer;" class="height-100 fz16 flex-row flex-justify-between flex-align-center">
       <a-dropdown>
@@ -36,31 +35,11 @@ import { getRoot } from '/@/root'
 import { getPlatformInfo } from '/@/api/manage'
 import { ELocalStorageKey, ERouterName } from '/@/types'
 import { UserOutlined, PoweroffOutlined } from '@ant-design/icons-vue'
-import cloudapi from '/@/assets/icons/cloudapi.png'
 
 const root = getRoot()
 
-interface IOptions {
-  key: number
-  label: string
-  path:
-  | string
-  | {
-    path: string
-    query?: any
-  }
-  icon: string
-}
 const username = ref(localStorage.getItem(ELocalStorageKey.Username))
 const workspaceName = ref('')
-const options = [
-  { key: 0, label: ERouterName.WORKSPACE.charAt(0).toUpperCase() + ERouterName.WORKSPACE.substr(1), path: '/' + ERouterName.WORKSPACE },
-  { key: 1, label: ERouterName.MEMBERS.charAt(0).toUpperCase() + ERouterName.MEMBERS.substr(1), path: '/' + ERouterName.MEMBERS },
-  { key: 2, label: ERouterName.DEVICES.charAt(0).toUpperCase() + ERouterName.DEVICES.substr(1), path: '/' + ERouterName.DEVICES },
-  { key: 3, label: ERouterName.FIRMWARES.charAt(0).toUpperCase() + ERouterName.FIRMWARES.substr(1), path: '/' + ERouterName.FIRMWARES },
-]
-
-const selected = ref<string>(root.$route.path)
 
 onMounted(() => {
   getPlatformInfo().then(res => {
@@ -68,8 +47,8 @@ onMounted(() => {
   })
 })
 
-function selectedRoute(path: string) {
-  selected.value = path
+const onClickToWorkspace = () => {
+  root.$router.push(ERouterName.WORKSPACE)
 }
 
 const logout = () => {

+ 36 - 0
Web/src/components/devices/changeRecord/components/Drawer.vue

@@ -0,0 +1,36 @@
+<template>
+    <a-drawer :width="800" title="详情" placement="right" v-model:visible="visible" @close="onClose">
+        详情
+    </a-drawer>
+</template>
+
+<script lang="ts" setup>
+import { reactive, onMounted } from 'vue'
+import { apis } from '/@/api/custom/index'
+
+interface Props {
+    id: string,
+    visible: boolean,
+    onClose: () => void,
+};
+
+const props = withDefaults(defineProps<Props>(), {
+
+});
+
+interface State {
+    listLoading: boolean,
+    list: any[],
+};
+
+const state: State = reactive({
+    listLoading: false,
+    list: [],
+});
+
+onMounted(async () => {
+
+})
+</script>
+
+<style lang="scss" scoped></style>

+ 169 - 0
Web/src/components/devices/changeRecord/index.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="changeRecord">
+    <div class="changeRecord-table">
+      <a-table :scroll="{ x: 'max-content', y: 600 }" rowKey="id" :loading="state.listLoading" :columns="columns"
+        :dataSource="state.list" :pagination="paginationConfig">
+        <!-- 操作 -->
+        <template #operation="{ record }">
+          <div class="editable-row-operations">
+            <div class="flex-align-center flex-row" style="color: #2d8cf0">
+              <a-tooltip title="详情">
+                <FileSearchOutlined style="margin-right: 10px;" @click="onClickEdit(record.id)" />
+              </a-tooltip>
+              <a-tooltip title="删除">
+                <DeleteOutlined @click="onClickDelete(record.id)" />
+              </a-tooltip>
+            </div>
+          </div>
+        </template>
+      </a-table>
+    </div>
+    <Drawer :id="state.currentId" :visible="state.drawerVisible" :onClose="drawerOnClickClose" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive, onMounted } from 'vue';
+import { Modal } from 'ant-design-vue';
+import { DeleteOutlined, FileSearchOutlined } from '@ant-design/icons-vue';
+import Drawer from './components/Drawer.vue';
+import { apis } from '/@/api/custom/index';
+
+interface State {
+  listLoading: boolean,
+  list: any[],
+  currentId: string,
+  drawerVisible: boolean,
+};
+
+const state: State = reactive({
+  listLoading: false,
+  list: [],
+  currentId: '',
+  drawerVisible: false,
+});
+
+const paginationConfig = reactive({
+  pageSizeOptions: ['20', '50', '100'],
+  showQuickJumper: true,
+  showSizeChanger: true,
+  pageSize: 50,
+  current: 1,
+  total: 0
+})
+
+onMounted(async () => {
+  state.listLoading = true;
+  try {
+    const res = await apis.fetchChangeRecordList();
+    console.log(res, 'list');
+    const list = [
+      {
+        id: '1',
+        time: '2021-01-01',
+        name: '1',
+        deviceType: 'aa',
+        sn: '123',
+        deviceName: '1',
+        firmware_version: '1',
+        description: '1',
+        status: '1',
+      },
+      {
+        id: '2',
+        time: '2021-01-01',
+        name: '1',
+        deviceType: 'aa',
+        sn: '123',
+        deviceName: '1',
+        firmware_version: '1',
+        description: '1',
+        status: '1',
+      },
+    ]
+    state.list = list;
+  } catch (e) {
+    console.error(e);
+  } finally {
+    state.listLoading = false;
+  }
+})
+
+const columns = [
+  {
+    title: '反馈时间',
+    dataIndex: 'time',
+    sorter: (a: any, b: any) => a.time - b.time,
+  },
+  {
+    title: '反馈人',
+    dataIndex: 'name',
+  },
+  {
+    title: '设备型号',
+    dataIndex: 'deviceType',
+    sorter: (a: any, b: any) => a.deviceType - b.deviceType,
+  },
+  {
+    title: '设备SN',
+    dataIndex: 'sn',
+  },
+  {
+    title: '设备名称',
+    dataIndex: 'deviceName',
+    sorter: (a: any, b: any) => a.deviceName - b.deviceName,
+  },
+  {
+    title: '固件版本',
+    dataIndex: 'firmware_version',
+  },
+  {
+    title: '设备异常描述',
+    dataIndex: 'description',
+  },
+  {
+    title: '上传状态',
+    dataIndex: 'status',
+    sorter: (a: any, b: any) => a.time - b.time,
+  },
+  {
+    title: '操作',
+    dataIndex: 'operation',
+    fixed: 'right',
+    width: 100,
+    className: 'titleStyle',
+    slots: { customRender: 'operation' }
+  },
+]
+
+// 点击编辑
+const onClickEdit = (id: string) => {
+  state.currentId = id;
+  state.drawerVisible = true;
+}
+
+const drawerOnClickClose = () => {
+  state.drawerVisible = false;
+}
+
+// 点击删除
+const onClickDelete = (id: number) => {
+  Modal.confirm({
+    title: '提示',
+    content: '确定删除吗?',
+    onOk: async () => {
+
+    },
+  });
+}
+</script>
+
+<style lang="scss" scoped>
+.changeRecord {
+  &-table {
+    background-color: white;
+    margin: 20px;
+    padding: 0 20px;
+  }
+}
+</style>

+ 37 - 61
Web/src/components/devices/device-hms/DeviceHmsDrawer.vue

@@ -1,78 +1,54 @@
 <template>
-  <a-drawer
-    title="Hms Info"
-    placement="right"
-    v-model:visible="sVisible"
-    @update:visible="onVisibleChange"
-    :destroyOnClose="true"
-    :width="800">
+  <a-drawer title="设备告警信息" placement="right" v-model:visible="sVisible" @update:visible="onVisibleChange"
+    :destroyOnClose="true" :width="800">
     <div class="flex-row flex-align-center">
       <div style="width: 240px;">
-        <a-range-picker
-          v-model:value="time"
-          format="YYYY-MM-DD"
-          :placeholder="['Start Time', 'End Time']"
-          @change="onTimeChange"/>
+        <a-range-picker v-model:value="time" format="YYYY-MM-DD" @change="onTimeChange" />
       </div>
       <div class="ml5">
-        <a-select
-          style="width: 150px"
-          v-model:value="param.level"
-          @select="onLevelSelect">
-          <a-select-option
-            v-for="item in levels"
-            :key="item.label"
-            :value="item.value"
-          >
+        <a-select style="width: 150px" v-model:value="param.level" @select="onLevelSelect">
+          <a-select-option v-for="item in levels" :key="item.label" :value="item.value">
             {{ item.label }}
           </a-select-option>
         </a-select>
       </div>
       <div class="ml5">
-        <a-select
-          v-model:value="param.domain"
-          :disabled="!param.children_sn || !param.device_sn"
-          style="width: 150px"
+        <a-select v-model:value="param.domain" :disabled="!param.children_sn || !param.device_sn" style="width: 150px"
           @select="onDeviceTypeSelect">
-          <a-select-option
-            v-for="item in deviceTypes"
-            :key="item.label"
-            :value="item.value"
-          >
+          <a-select-option v-for="item in deviceTypes" :key="item.label" :value="item.value">
             {{ item.label }}
           </a-select-option>
         </a-select>
       </div>
       <div class="ml5">
-        <a-input-search
-          v-model:value="param.message"
-          placeholder="input search message"
-          style="width: 200px"
-          @search="getHms"/>
+        <a-input-search v-model:value="param.message" placeholder="搜索告警信息" style="width: 200px" @search="getHms" />
       </div>
     </div>
     <div>
-      <a-table :columns="hmsColumns"  :scroll="{ x: '100%', y: 600 }" :data-source="hmsData.data" :pagination="hmsPaginationProp" @change="refreshHmsData" row-key="hms_id"
-        :rowClassName="rowClassName" :loading="loading">
+      <a-table :columns="hmsColumns" :scroll="{ x: '100%', y: 600 }" :data-source="hmsData.data"
+        :pagination="hmsPaginationProp" @change="refreshHmsData" row-key="hms_id" :rowClassName="rowClassName"
+        :loading="loading">
         <template #time="{ record }">
           <div>{{ record.create_time }}</div>
           <div :style="record.update_time ? '' : record.level === EHmsLevel.CAUTION ? 'color: orange;' :
-            record.level === EHmsLevel.WARN ? 'color: red;' : 'color: #28d445;'">{{ record.update_time ?? 'It is happening...' }}</div>
+            record.level === EHmsLevel.WARN ? 'color: red;' : 'color: #28d445;'">
+            {{ record.update_time ?? 'It is happening...' }}</div>
         </template>
         <template #level="{ text }">
           <div class="flex-row flex-align-center">
-            <div :class="text === EHmsLevel.CAUTION ? 'caution' : text === EHmsLevel.WARN ? 'warn' : 'notice'" style="width: 10px; height: 10px; border-radius: 50%;"></div>
+            <div :class="text === EHmsLevel.CAUTION ? 'caution' : text === EHmsLevel.WARN ? 'warn' : 'notice'"
+              style="width: 10px; height: 10px; border-radius: 50%;"></div>
             <div style="margin-left: 3px;">{{ EHmsLevel[text] }}</div>
           </div>
         </template>
         <template v-for="col in ['code', 'message']" #[col]="{ text }" :key="col">
           <a-tooltip :title="text">
-              <div >{{ text }}</div>
+            <div>{{ text }}</div>
           </a-tooltip>
         </template>
-        <template #domain="{text}">
+        <template #domain="{ text }">
           <a-tooltip :title="EDeviceTypeName[text]">
-              <div >{{ EDeviceTypeName[text] }}</div>
+            <div>{{ EDeviceTypeName[text] }}</div>
           </a-tooltip>
         </template>
       </a-table>
@@ -84,7 +60,7 @@
 <script lang="ts" setup>
 import { watchEffect, reactive, ref, defineProps, defineEmits, watch } from 'vue'
 import { getDeviceHms, HmsQueryBody } from '/@/api/manage'
-import moment, { Moment } from 'moment'
+import moment from 'moment'
 import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
 import { Device, DeviceHms } from '/@/types/device'
 import { IPage } from '/@/api/http/type'
@@ -108,11 +84,11 @@ watch(props, () => {
   }
 })
 
-function onVisibleChange (sVisible: boolean) {
+function onVisibleChange(sVisible: boolean) {
   setVisible(sVisible)
 }
 
-function setVisible (v: boolean, e?: Event) {
+function setVisible(v: boolean, e?: Event) {
   sVisible.value = v
   emit('update:visible', v, e)
 }
@@ -120,12 +96,12 @@ function setVisible (v: boolean, e?: Event) {
 const loading = ref(false)
 
 const hmsColumns: ColumnProps[] = [
-  { title: 'Alarm Begin | End Time', dataIndex: 'create_time', width: '25%', className: 'titleStyle', slots: { customRender: 'time' } },
-  { title: 'Level', dataIndex: 'level', width: '120px', className: 'titleStyle', slots: { customRender: 'level' } },
-  { title: 'Device', dataIndex: 'domain', width: '12%', className: 'titleStyle', slots: { customRender: 'domain' } },
-  { title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', ellipsis: true, slots: { customRender: 'code' } },
-  { title: 'Hms Message', dataIndex: 'message_en', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
-  { title: 'Hms Message', dataIndex: 'message_zh', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
+  { title: '告警开始|结束时间', dataIndex: 'create_time', width: '25%', className: 'titleStyle', slots: { customRender: 'time' } },
+  { title: '告警等级', dataIndex: 'level', width: '120px', className: 'titleStyle', slots: { customRender: 'level' } },
+  { title: '设备', dataIndex: 'domain', width: '12%', className: 'titleStyle', slots: { customRender: 'domain' } },
+  { title: '错误码', dataIndex: 'key', width: '20%', className: 'titleStyle', ellipsis: true, slots: { customRender: 'code' } },
+  { title: '告警内容', dataIndex: 'message_en', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
+  { title: '解决方案', dataIndex: 'message_zh', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },
 ]
 
 interface DeviceHmsData {
@@ -148,14 +124,14 @@ const hmsPaginationProp = reactive({
 })
 
 // 获取分页信息
-function getPaginationBody () {
+function getPaginationBody() {
   return {
     page: hmsPaginationProp.current,
     page_size: hmsPaginationProp.pageSize
   } as IPage
 }
 
-function showHms () {
+function showHms() {
   const dock = props.device
   if (!dock) return
   if (dock.domain === EDeviceTypeName.Dock) {
@@ -167,7 +143,7 @@ function showHms () {
   }
 }
 
-function refreshHmsData (page: Pagination) {
+function refreshHmsData(page: Pagination) {
   hmsPaginationProp.current = page?.current!
   hmsPaginationProp.pageSize = page?.pageSize!
   getHms()
@@ -187,7 +163,7 @@ const param = reactive<HmsQueryBody>({
 
 const levels = [
   {
-    label: 'All',
+    label: '全部告警等级',
     value: ''
   }, {
     label: EHmsLevel[0],
@@ -203,7 +179,7 @@ const levels = [
 
 const deviceTypes = [
   {
-    label: 'All',
+    label: '全部设备',
     value: -1
   }, {
     label: EDeviceTypeName[EDeviceTypeName.Aircraft],
@@ -227,7 +203,7 @@ const rowClassName = (record: any, index: number) => {
 
 const time = ref([moment(param.begin_time), moment(param.end_time)])
 
-function getHms () {
+function getHms() {
   loading.value = true
   getDeviceHms(param, workspaceId, getPaginationBody())
     .then(res => {
@@ -243,20 +219,20 @@ function getHms () {
     })
 }
 
-function getDeviceHmsBySn (sn: string, childSn: string) {
+function getDeviceHmsBySn(sn: string, childSn: string) {
   param.device_sn = sn
   param.children_sn = childSn
   param.sns = [param.device_sn, param.children_sn]
   getHms()
 }
 
-function onTimeChange (newTime: [Moment, Moment]) {
+function onTimeChange(newTime: any[]) {
   param.begin_time = newTime[0].valueOf()
   param.end_time = newTime[1].valueOf()
   getHms()
 }
 
-function onDeviceTypeSelect (val: number) {
+function onDeviceTypeSelect(val: number) {
   param.sns = [param.device_sn, param.children_sn]
   if (val === EDeviceTypeName.Dock) {
     param.sns = [param.device_sn, '']
@@ -267,7 +243,7 @@ function onDeviceTypeSelect (val: number) {
   getHms()
 }
 
-function onLevelSelect (val: number) {
+function onLevelSelect(val: number) {
   param.level = val
   getHms()
 }

+ 43 - 52
Web/src/components/devices/device-log/DeviceLogUploadRecordDrawer.vue

@@ -1,9 +1,5 @@
 <template>
-  <a-drawer
-    title="设备日志上传记录"
-    placement="right"
-    v-model:visible="sVisible"
-    @update:visible="onVisibleChange"
+  <a-drawer title="设备日志上传记录" placement="right" v-model:visible="sVisible" @update:visible="onVisibleChange"
     :width="800">
     <!-- 设备日志上传记录 -->
     <div class="device-log-upload-record-wrap">
@@ -11,32 +7,32 @@
         <a-button type="primary" @click="onUploadDeviceLog">上传日志</a-button>
       </div>
       <div class="device-log-upload-list">
-        <a-table :columns="deviceLogUploadListColumns"
-                  :scroll="{ x: '100%', y: 600 }"
-                  :data-source="deviceUploadLogState.uploadLogList"
-                  :loading="deviceUploadLogState.loading"
-                  :pagination="deviceUploadLogState.paginationProp"
-                  @change="onDeviceUploadLogTableChange"
-                  rowKey="logs_id">
-         <!-- 设备类型 -->
+        <a-table :columns="deviceLogUploadListColumns" :scroll="{ x: '100%', y: 600 }"
+          :data-source="deviceUploadLogState.uploadLogList" :loading="deviceUploadLogState.loading"
+          :pagination="deviceUploadLogState.paginationProp" @change="onDeviceUploadLogTableChange" rowKey="logs_id">
+          <!-- 设备类型 -->
           <template #device_type="{ record }">
             <div>
-              <div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).parents[0].device_model.device_model_key]}}</div>
-              <div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).hosts[0].device_model.device_model_key]}}</div>
+              <div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{
+                DEVICE_NAME[getDeviceInfo(record).parents[0].device_model.device_model_key] }}</div>
+              <div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{
+                DEVICE_NAME[getDeviceInfo(record).hosts[0].device_model.device_model_key] }}</div>
             </div>
           </template>
           <!-- 设备sn -->
           <template #device_sn="{ record }">
             <div>
-              <div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{ getDeviceInfo(record).parents[0].sn }}</div>
-              <div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ getDeviceInfo(record).hosts[0].sn }}</div>
+              <div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{
+                getDeviceInfo(record).parents[0].sn }}</div>
+              <div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{
+                getDeviceInfo(record).hosts[0].sn }}</div>
             </div>
           </template>
           <!-- 上传状态 -->
           <template #status="{ record }">
             <div>
               <div>
-                <span class="circle-icon" :style="{backgroundColor: getDeviceLogUploadStatus(record).color}"></span>
+                <span class="circle-icon" :style="{ backgroundColor: getDeviceLogUploadStatus(record).color }"></span>
                 {{ getDeviceLogUploadStatus(record).text }}
               </div>
               <div v-if="record.status === DeviceLogUploadStatusEnum.Uploading">
@@ -48,16 +44,16 @@
           <template #action="{ record }">
             <div class="row-action">
               <a-tooltip title="查看详情">
-                  <FileTextOutlined  @click="showDeviceLogDetail(record)"/>
+                <FileTextOutlined @click="showDeviceLogDetail(record)" />
               </a-tooltip>
               <span v-if="record.status === DeviceLogUploadStatusEnum.Uploading">
                 <a-tooltip title="取消">
-                  <StopOutlined @click="onCancelUploadDeviceLog(record)"/>
+                  <StopOutlined @click="onCancelUploadDeviceLog(record)" />
                 </a-tooltip>
               </span>
               <span v-else>
                 <a-tooltip title="删除">
-                  <DeleteOutlined @click="onDeleteUploadDeviceLog(record)"/>
+                  <DeleteOutlined @click="onDeleteUploadDeviceLog(record)" />
                 </a-tooltip>
               </span>
             </div>
@@ -67,17 +63,12 @@
     </div>
   </a-drawer>
   <!-- 设备日志上传弹框 -->
-  <DeviceLogUploadModal
-     v-model:visible="deviceLogUploadModalVisible"
-     :device="props.device"
-     @upload-log-ok="onUploadLogOk"
-  ></DeviceLogUploadModal>
+  <DeviceLogUploadModal v-model:visible="deviceLogUploadModalVisible" :device="props.device"
+    @upload-log-ok="onUploadLogOk"></DeviceLogUploadModal>
 
   <!-- 设备日志上传详情弹框 -->
-  <DeviceLogDetailModal
-     v-model:visible="deviceLogDetailModalVisible"
-     :deviceLog="currentDeviceLog"
-  ></DeviceLogDetailModal>
+  <DeviceLogDetailModal v-model:visible="deviceLogDetailModalVisible" :deviceLog="currentDeviceLog">
+  </DeviceLogDetailModal>
 </template>
 
 <script lang="ts" setup>
@@ -109,11 +100,11 @@ watchEffect(() => {
   }
 })
 
-function onVisibleChange (sVisible: boolean) {
+function onVisibleChange(sVisible: boolean) {
   setVisible(sVisible)
 }
 
-function setVisible (v: boolean, e?: Event) {
+function setVisible(v: boolean, e?: Event) {
   sVisible.value = v
   emit('update:visible', v, e)
 }
@@ -141,7 +132,7 @@ const deviceUploadLogState = reactive({
 })
 
 // 获取上传的设备日志
-async function getDeviceUploadLogInfo () {
+async function getDeviceUploadLogInfo() {
   deviceUploadLogState.loading = true
   try {
     const { code, data } = await getDeviceUploadLogList({
@@ -163,13 +154,13 @@ async function getDeviceUploadLogInfo () {
 type Pagination = TableState['pagination']
 
 // 获取设备信息
-function getDeviceInfo (deviceLogItem: GetDeviceUploadLogListRsp) {
+function getDeviceInfo(deviceLogItem: GetDeviceUploadLogListRsp) {
   const { device_topo: deviceTopo } = deviceLogItem
   return deviceTopo
 }
 
 // 获取上传状态
-function getDeviceLogUploadStatus (deviceLogItem: GetDeviceUploadLogListRsp) {
+function getDeviceLogUploadStatus(deviceLogItem: GetDeviceUploadLogListRsp) {
   const statusObj = {
     color: '',
     text: ''
@@ -181,7 +172,7 @@ function getDeviceLogUploadStatus (deviceLogItem: GetDeviceUploadLogListRsp) {
 }
 
 // 获取上传进度
-function getLogProgress (deviceLogItem: GetDeviceUploadLogListRsp) {
+function getLogProgress(deviceLogItem: GetDeviceUploadLogListRsp) {
   let percent = 0
   const { logs_progress } = deviceLogItem
   if (logs_progress && logs_progress.length > 0) {
@@ -194,7 +185,7 @@ function getLogProgress (deviceLogItem: GetDeviceUploadLogListRsp) {
 }
 
 // 设备日志上传进度更新
-function onDeviceLogUploadWs (data: DeviceLogUploadInfo) {
+function onDeviceLogUploadWs(data: DeviceLogUploadInfo) {
   const { sn, output } = data
   if (output) {
     const { files, status, logs_id: logId } = output || {}
@@ -219,7 +210,7 @@ function onDeviceLogUploadWs (data: DeviceLogUploadInfo) {
 useDeviceLogUploadProgressEvent(onDeviceLogUploadWs)
 
 // 搜索
-async function onDeviceUploadLogTableChange (page: Pagination) {
+async function onDeviceUploadLogTableChange(page: Pagination) {
   deviceUploadLogState.paginationProp.current = page?.current || 1
   deviceUploadLogState.paginationProp.pageSize = page?.pageSize || 20
   await getDeviceUploadLogInfo()
@@ -229,25 +220,25 @@ async function onDeviceUploadLogTableChange (page: Pagination) {
 const deviceLogDetailModalVisible = ref(false)
 const currentDeviceLog = ref({} as GetDeviceUploadLogListRsp)
 
-function showDeviceLogDetail (deviceLogItem: GetDeviceUploadLogListRsp) {
+function showDeviceLogDetail(deviceLogItem: GetDeviceUploadLogListRsp) {
   if (!deviceLogItem) return
   currentDeviceLog.value = deviceLogItem
   deviceLogDetailModalVisible.value = true
 }
 
 // 取消上传设备日志
-async function onCancelUploadDeviceLog (deviceLogItem: GetDeviceUploadLogListRsp) {
+async function onCancelUploadDeviceLog(deviceLogItem: GetDeviceUploadLogListRsp) {
   Modal.confirm({
     title: '取消日志上传',
     content: '您确认取消设备日志上传吗?',
     okType: 'danger',
-    onOk () {
+    onOk() {
       cancelDeviceLogUploadOk()
     },
   })
 }
 
-async function cancelDeviceLogUploadOk () {
+async function cancelDeviceLogUploadOk() {
   const { code } = await cancelDeviceLogUpload({
     device_sn: props.device?.device_sn || '',
     module_list: [DOMAIN.DOCK, DOMAIN.DRONE],
@@ -259,18 +250,18 @@ async function cancelDeviceLogUploadOk () {
 }
 
 // 删除上传的设备日志
-function onDeleteUploadDeviceLog (deviceLogItem: GetDeviceUploadLogListRsp) {
+function onDeleteUploadDeviceLog(deviceLogItem: GetDeviceUploadLogListRsp) {
   Modal.confirm({
     title: '删除上传日志',
     content: '您确认删除该条已上传设备日志吗?',
     okType: 'danger',
-    onOk () {
+    onOk() {
       deleteUploadDeviceLogOk(deviceLogItem)
     },
   })
 }
 
-async function deleteUploadDeviceLogOk (deviceLogItem: GetDeviceUploadLogListRsp) {
+async function deleteUploadDeviceLogOk(deviceLogItem: GetDeviceUploadLogListRsp) {
   const { code } = await deleteDeviceLogUpload({
     device_sn: props.device?.device_sn || '',
     logs_id: deviceLogItem.logs_id
@@ -283,25 +274,25 @@ async function deleteUploadDeviceLogOk (deviceLogItem: GetDeviceUploadLogListRsp
 // 上传日志
 const deviceLogUploadModalVisible = ref(false)
 
-function onUploadDeviceLog () {
+function onUploadDeviceLog() {
   deviceLogUploadModalVisible.value = true
 }
 
-function onUploadLogOk () {
+function onUploadLogOk() {
   // 刷新列表
   getDeviceUploadLogInfo()
 }
 </script>
 
 <style lang="scss" scoped>
-.device-log-upload-record-wrap{
-  .page-action-row{
+.device-log-upload-record-wrap {
+  .page-action-row {
     display: flex;
     justify-content: space-between;
     width: 100%;
   }
 
-  .device-log-upload-list{
+  .device-log-upload-list {
     padding: 20px 0 10px;
   }
 
@@ -315,10 +306,10 @@ function onUploadLogOk () {
     flex-shrink: 0;
   }
 
-  .row-action{
+  .row-action {
     color: #2d8cf0;
 
-    & > span{
+    &>span {
       margin-right: 10px;
     }
   }

+ 421 - 0
Web/src/components/devices/deviceList/index.vue

@@ -0,0 +1,421 @@
+<template>
+  <div class="device-table-wrap table flex-display flex-column">
+    <a-table :columns="columns" :data-source="data.device" :pagination="paginationProp" @change="refreshData"
+      row-key="device_sn" :expandedRowKeys="expandRows" :row-selection="rowSelection" :rowClassName="rowClassName"
+      :scroll="{ x: 'max-content', y: 600 }" :expandIcon="expandIcon" :loading="loading">
+      <template v-for="col in ['nickname']" #[col]="{ text, record }" :key="col">
+        <div>
+          <a-input v-if="editableData[record.device_sn]" v-model:value="editableData[record.device_sn][col]"
+            style="margin: -5px 0" />
+          <template v-else>
+            <a-tooltip :title="text">
+              {{ text }}
+            </a-tooltip>
+          </template>
+        </div>
+      </template>
+      <template v-for="col in ['sn', 'workspace']" #[col]="{ text }" :key="col">
+        <a-tooltip :title="text">
+          <span>{{ text }}</span>
+        </a-tooltip>
+      </template>
+      <!-- 固件版本 -->
+      <template #firmware_version="{ record }">
+        <span v-if="judgeCurrentType(EDeviceTypeName.Dock)">
+          <DeviceFirmwareUpgrade :device="record" class="table-flex-col" @device-upgrade="onDeviceUpgrade" />
+        </span>
+        <span v-else>
+          {{ record.firmware_version }}
+        </span>
+      </template>
+      <!-- 固件升级 -->
+      <template #firmware_status="{ text }">
+        <div v-if="text === -1">
+          不支持
+        </div>
+        <div v-else>
+          {{ DeviceFirmwareStatus[text] }}
+        </div>
+      </template>
+      <!-- 状态 -->
+      <template #status="{ text }">
+        <span v-if="text" class="flex-row flex-align-center">
+          <span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: green;" />
+          <span>在线</span>
+        </span>
+        <span class="flex-row flex-align-center" v-else>
+          <span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: red;" />
+          <span>离线</span>
+        </span>
+      </template>
+      <!-- 操作 -->
+      <template #action="{ record }">
+        <div class="editable-row-operations">
+          <!-- 编辑态操作 -->
+          <div v-if="editableData[record.device_sn]">
+            <a-tooltip title="确定">
+              <span @click="save(record)" style="color: #28d445;">
+                <CheckOutlined />
+              </span>
+            </a-tooltip>
+            <a-tooltip title="取消">
+              <span @click="() => delete editableData[record.device_sn]" style="color: #e70102;">
+                <CloseOutlined />
+              </span>
+            </a-tooltip>
+          </div>
+          <!-- 非编辑态操作 -->
+          <div v-else class="flex-align-center flex-row" style="color: #2d8cf0">
+            <a-tooltip v-if="current.indexOf(EDeviceTypeName.Dock) !== -1" title="设备日志">
+              <CloudServerOutlined @click="showDeviceLogUploadRecord(record)" />
+            </a-tooltip>
+            <a-tooltip v-if="current.indexOf(EDeviceTypeName.Dock) !== -1" title="告警信息">
+              <FileSearchOutlined @click="showHms(record)" />
+            </a-tooltip>
+            <a-tooltip title="编辑">
+              <EditOutlined @click="edit(record)" />
+            </a-tooltip>
+            <a-tooltip title="删除">
+              <DeleteOutlined @click="() => { deleteTip = true, deleteSn = record.device_sn }" />
+            </a-tooltip>
+          </div>
+        </div>
+      </template>
+    </a-table>
+    <a-modal v-model:visible="deleteTip" width="450px" :closable="false" centered :okButtonProps="{ danger: true }"
+      @ok="unbind">
+      <p class="pt10 pl20" style="height: 50px;">从工作区中删除设备吗?</p>
+      <template #title>
+        <div class="flex-row flex-justify-center">
+          <span>删除设备</span>
+        </div>
+      </template>
+    </a-modal>
+    <!-- 设备升级 -->
+    <DeviceFirmwareUpgradeModal title="设备升级" v-model:visible="deviceFirmwareUpgradeModalVisible"
+      :device="selectedDevice" @ok="onUpgradeDeviceOk"></DeviceFirmwareUpgradeModal>
+    <!-- 设备日志上传记录 -->
+    <DeviceLogUploadRecordDrawer v-model:visible="deviceLogUploadRecordVisible" :device="currentDevice">
+    </DeviceLogUploadRecordDrawer>
+    <!-- hms 信息 -->
+    <DeviceHmsDrawer v-model:visible="hmsVisible" :device="currentDevice">
+    </DeviceHmsDrawer>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { h, onMounted, reactive, ref, UnwrapRef } from 'vue'
+import { notification } from 'ant-design-vue'
+import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
+import { getBindingDevices, unbindDevice, updateDevice } from '/@/api/manage'
+import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, FileSearchOutlined, CloudServerOutlined } from '@ant-design/icons-vue'
+import { Device, DeviceFirmwareStatus, DeviceFirmwareStatusEnum } from '/@/types/device'
+import DeviceFirmwareUpgrade from '/@/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue'
+import DeviceFirmwareUpgradeModal from '/@/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue'
+import { useDeviceFirmwareUpgrade } from '/@/components/devices/device-upgrade/use-device-upgrade'
+import { useDeviceUpgradeEvent } from '/@/components/devices/device-upgrade/use-device-upgrade-event'
+import { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd'
+import DeviceLogUploadRecordDrawer from '/@/components/devices/device-log/DeviceLogUploadRecordDrawer.vue'
+import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue'
+import { IPage } from '/@/api/http/type'
+import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
+
+interface DeviceData {
+  device: Device[]
+}
+
+const loading = ref(true)
+const deleteTip = ref<boolean>(false)
+const deleteSn = ref<string>()
+
+const columns: ColumnProps[] = [
+  {
+    title: '设备型号', dataIndex: 'device_name', width: 150,
+    sorter: (a: Device, b: Device) => a.nickname.localeCompare(b.nickname),
+    className: 'titleStyle'
+  },
+  { title: '设备SN', dataIndex: 'device_sn', width: 100, className: 'titleStyle', ellipsis: true, slots: { customRender: 'sn' } },
+  {
+    title: '设备名称',
+    dataIndex: 'nickname',
+    width: 150,
+    sorter: (a: Device, b: Device) => a.nickname.localeCompare(b.nickname),
+    className: 'titleStyle',
+    ellipsis: true,
+    slots: { customRender: 'nickname' }
+  },
+  { title: '固件版本', dataIndex: 'firmware_version', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_version' } },
+  { title: '固件升级', dataIndex: 'firmware_status', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_status' } },
+  { title: '当前状态', dataIndex: 'status', width: 100, className: 'titleStyle', slots: { customRender: 'status' } },
+  { title: '加入项目时间', dataIndex: 'bound_time', width: 150, sorter: (a: Device, b: Device) => a.bound_time.localeCompare(b.bound_time), className: 'titleStyle' },
+  { title: '最后在线时间', dataIndex: 'login_time', width: 150, sorter: (a: Device, b: Device) => a.login_time.localeCompare(b.login_time), className: 'titleStyle' },
+  {
+    title: '操作',
+    dataIndex: 'actions',
+    fixed: 'right',
+    width: 150,
+    className: 'titleStyle',
+    slots: { customRender: 'action' }
+  },
+]
+
+const expandIcon = (props: any) => {
+  if (judgeCurrentType(EDeviceTypeName.Dock) && !props.expanded) {
+    return h('div',
+      {
+        style: 'border-left: 2px solid rgb(200,200,200); border-bottom: 2px solid rgb(200,200,200); height: 16px; width: 16px; float: left;',
+        class: 'mt-5 ml0',
+      })
+  }
+}
+
+const rowClassName = (record: any, index: number) => {
+  const className = []
+  if ((index & 1) === 0) {
+    className.push('table-striped')
+  }
+  if (record.domain !== EDeviceTypeName.Dock) {
+    className.push('child-row')
+  }
+  return className.toString().replaceAll(',', ' ')
+}
+
+const expandRows = ref<string[]>([])
+const data = reactive<DeviceData>({
+  device: []
+})
+
+const paginationProp = reactive({
+  pageSizeOptions: ['20', '50', '100'],
+  showQuickJumper: true,
+  showSizeChanger: true,
+  pageSize: 50,
+  current: 1,
+  total: 0
+})
+
+// 获取分页信息
+function getPaginationBody() {
+  return {
+    page: paginationProp.current,
+    page_size: paginationProp.pageSize
+  } as IPage
+}
+
+const rowSelection = {
+  onChange: (selectedRowKeys: (string | number)[], selectedRows: []) => {
+    console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)
+  },
+  onSelect: (record: any, selected: boolean, selectedRows: []) => {
+    console.log(record, selected, selectedRows)
+  },
+  onSelectAll: (selected: boolean, selectedRows: [], changeRows: []) => {
+    console.log(selected, selectedRows, changeRows)
+  },
+  getCheckboxProps: (record: any) => ({
+    disabled: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock,
+    style: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock ? 'display: none' : ''
+  }),
+}
+
+type Pagination = TableState['pagination']
+
+const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
+const editableData: UnwrapRef<Record<string, Device>> = reactive({})
+
+const current = ref([EDeviceTypeName.Dock])
+
+function judgeCurrentType(type: EDeviceTypeName): boolean {
+  return current.value.indexOf(type) !== -1
+}
+
+// 设备升级
+const {
+  deviceFirmwareUpgradeModalVisible,
+  selectedDevice,
+  onDeviceUpgrade,
+  onUpgradeDeviceOk
+} = useDeviceFirmwareUpgrade(workspaceId)
+
+function onDeviceUpgradeWs(payload: DeviceCmdExecuteInfo) {
+  updateDevicesByWs(data.device, payload)
+}
+
+function updateDevicesByWs(devices: Device[], payload: DeviceCmdExecuteInfo) {
+  if (!devices || devices.length <= 0) {
+    return
+  }
+  for (let i = 0; i < devices.length; i++) {
+    if (devices[i].device_sn === payload.sn) {
+      if (!payload.output) return
+      const { status, progress, ext } = payload.output
+      if (status === DeviceCmdExecuteStatus.Sent || status === DeviceCmdExecuteStatus.InProgress) { // 升级中
+        const rate = ext?.rate ? (ext.rate / 1024).toFixed(2) + 'kb/s' : ''
+        devices[i].firmware_status = DeviceFirmwareStatusEnum.DuringUpgrade
+        devices[i].firmware_progress = (progress?.percent || 0) + '% ' + rate
+      } else { // 终态:成功,失败,超时
+        if (status === DeviceCmdExecuteStatus.Failed || status === DeviceCmdExecuteStatus.Timeout) {
+          notification.error({
+            message: `(${payload.sn}) Upgrade failed`,
+            description: `Error Code: ${payload.result}`,
+            duration: null
+          })
+        }
+        // 拉取列表
+        getDevices(current.value[0], true)
+      }
+      return
+    }
+    if (devices[i].children) {
+      updateDevicesByWs(devices[i].children || [], payload)
+    }
+  }
+}
+
+useDeviceUpgradeEvent(onDeviceUpgradeWs)
+
+// 获取设备列表信息
+function getDevices(domain: number, closeLoading?: boolean) {
+  if (!closeLoading) {
+    loading.value = true
+  }
+  getBindingDevices(workspaceId, getPaginationBody(), domain).then(res => {
+    if (res.code !== 0) {
+      return
+    }
+    const resData: Device[] = res.data.list
+    expandRows.value = []
+    resData.forEach((val: any) => {
+      if (val.children) {
+        val.children = [val.children]
+      }
+      if (judgeCurrentType(EDeviceTypeName.Dock)) {
+        expandRows.value.push(val.device_sn)
+      }
+    })
+    data.device = resData
+    paginationProp.total = res.data.pagination.total
+    paginationProp.current = res.data.pagination.page
+    paginationProp.pageSize = res.data.pagination.page_size
+    loading.value = false
+  })
+}
+
+function refreshData(page: Pagination) {
+  paginationProp.current = page?.current!
+  paginationProp.pageSize = page?.pageSize!
+  getDevices(current.value[0])
+}
+
+// 编辑
+function edit(record: Device) {
+  editableData[record.device_sn] = record
+}
+
+// 保存
+function save(record: Device) {
+  delete editableData[record.device_sn]
+  updateDevice({ nickname: record.nickname }, workspaceId, record.device_sn)
+}
+
+// 解绑
+function unbind() {
+  deleteTip.value = false
+  unbindDevice(deleteSn.value?.toString()!).then(res => {
+    if (res.code !== 0) {
+      return
+    }
+    getDevices(current.value[0])
+  })
+}
+
+const currentDevice = ref({} as Device)
+// 设备日志
+const deviceLogUploadRecordVisible = ref(false)
+function showDeviceLogUploadRecord(dock: Device) {
+  deviceLogUploadRecordVisible.value = true
+  currentDevice.value = dock
+}
+
+// 健康状态
+const hmsVisible = ref<boolean>(false)
+
+function showHms(dock: Device) {
+  hmsVisible.value = true
+  currentDevice.value = dock
+}
+
+onMounted(() => {
+  getDevices(current.value[0])
+})
+</script>
+
+<style lang="scss" scoped>
+.device-table-wrap {
+  .editable-row-operations {
+    div>span {
+      margin-right: 10px;
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+.table {
+  background-color: white;
+  margin: 20px;
+  padding: 0 20px;
+}
+
+.table-striped {
+  background-color: #f7f9fa;
+}
+
+.ant-table {
+  border-top: 1px solid rgb(0, 0, 0, 0.06);
+  border-bottom: 1px solid rgb(0, 0, 0, 0.06);
+}
+
+.ant-table-tbody tr td {
+  border: 0;
+}
+
+.ant-table td {
+  white-space: nowrap;
+}
+
+.ant-table-thead tr th {
+  background: white !important;
+  border: 0;
+}
+
+th.ant-table-selection-column {
+  background-color: white !important;
+}
+
+.ant-table-header {
+  background-color: white !important;
+}
+
+.child-row {
+  height: 70px;
+}
+
+.notice {
+  background: $success;
+  overflow: hidden;
+  cursor: pointer;
+}
+
+.caution {
+  background: orange;
+  cursor: pointer;
+  overflow: hidden;
+}
+
+.warn {
+  background: red;
+  cursor: pointer;
+  overflow: hidden;
+}
+</style>

+ 11 - 0
Web/src/components/devices/feedbackRecord/index.vue

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    参考设备变化记录代码
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

+ 93 - 85
Web/src/components/task/CreatePlan.vue

@@ -4,34 +4,41 @@
       Create Plan
     </div>
     <div class="content">
-      <a-form ref="valueRef" layout="horizontal" :hideRequiredMark="true" :rules="rules" :model="planBody" labelAlign="left">
-        <a-form-item label="Plan Name" name="name" :labelCol="{span: 23}">
-          <a-input style="background: black;"  placeholder="Please enter plan name" v-model:value="planBody.name"/>
+      <a-form ref="valueRef" layout="horizontal" :hideRequiredMark="true" :rules="rules" :model="planBody"
+        labelAlign="left">
+        <a-form-item label="Plan Name" name="name" :labelCol="{ span: 23 }">
+          <a-input style="background: black;" placeholder="Please enter plan name" v-model:value="planBody.name" />
         </a-form-item>
         <!-- 航线 -->
-        <a-form-item label="Flight Route" :wrapperCol="{offset: 7}" name="file_id">
-          <router-link
-            :to="{name: 'select-plan'}"
-            @click="selectRoute"
-          >
-          Select Route
+        <a-form-item label="Flight Route" :wrapperCol="{ offset: 7 }" name="file_id">
+          <router-link :to="{ name: 'select-plan' }" @click="selectRoute">
+            Select Route
           </router-link>
         </a-form-item>
         <a-form-item v-if="planBody.file_id" style="margin-top: -15px;">
           <div class="wayline-panel" style="padding-top: 5px;">
             <div class="title">
               <a-tooltip :title="wayline.name">
-                <div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ wayline.name }}</div>
+                <div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">
+                  {{ wayline.name }}</div>
               </a-tooltip>
-              <div class="ml10"><UserOutlined /></div>
+              <div class="ml10">
+                <UserOutlined />
+              </div>
               <a-tooltip :title="wayline.user_name">
-                <div class="ml5 pr10" style="width: 80px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ wayline.user_name }}</div>
+                <div class="ml5 pr10"
+                  style="width: 80px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{
+                    wayline.user_name }}</div>
               </a-tooltip>
             </div>
             <div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
-              <span><RocketOutlined /></span>
+              <span>
+                <RocketOutlined />
+              </span>
               <span class="ml5">{{ DEVICE_NAME[wayline.drone_model_key] }}</span>
-              <span class="ml10"><CameraFilled style="border-top: 1px solid; padding-top: -3px;" /></span>
+              <span class="ml10">
+                <CameraFilled style="border-top: 1px solid; padding-top: -3px;" />
+              </span>
               <span class="ml5" v-for="payload in wayline.payload_model_keys" :key="payload.id">
                 {{ DEVICE_NAME[payload] }}
               </span>
@@ -42,21 +49,21 @@
           </div>
         </a-form-item>
         <!-- 设备 -->
-        <a-form-item label="Device" :wrapperCol="{offset: 10}" v-model:value="planBody.dock_sn" name="dock_sn">
-          <router-link
-            :to="{name: 'select-plan'}"
-            @click="selectDevice"
-          >Select Device</router-link>
+        <a-form-item label="Device" :wrapperCol="{ offset: 10 }" v-model:value="planBody.dock_sn" name="dock_sn">
+          <router-link :to="{ name: 'select-plan' }" @click="selectDevice">Select Device</router-link>
         </a-form-item>
         <a-form-item v-if="planBody.dock_sn" style="margin-top: -15px;">
           <div class="panel" style="padding-top: 5px;">
             <div class="title">
               <a-tooltip :title="dock.nickname">
-                <div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div>
+                <div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">
+                  {{ dock.nickname }}</div>
               </a-tooltip>
             </div>
             <div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
-              <span><RocketOutlined /></span>
+              <span>
+                <RocketOutlined />
+              </span>
               <span class="ml5">{{ dock.children?.nickname ?? 'No drone' }}</span>
             </div>
           </div>
@@ -65,68 +72,59 @@
         <a-form-item label="Plan Timer" class="plan-timer-form-item">
           <div style="white-space: nowrap;">
             <a-radio-group v-model:value="planBody.task_type" button-style="solid">
-              <a-radio-button v-for="type in TaskTypeOptions" :value="type.value" :key="type.value">{{ type.label }}</a-radio-button>
+              <a-radio-button v-for="type in TaskTypeOptions" :value="type.value" :key="type.value">
+                {{ type.label }}
+              </a-radio-button>
             </a-radio-group>
           </div>
         </a-form-item>
-        <!-- execute date -->
-        <a-form-item label="Date" v-if="planBody.task_type === TaskType.Timed || planBody.task_type === TaskType.Condition" name="select_execute_date" :labelCol="{span: 23}">
-          <a-range-picker
-            v-model:value="planBody.select_execute_date"
-            :disabledDate="(current: Moment) => current < moment().subtract(1, 'days')"
-            format="YYYY-MM-DD"
-            :placeholder="['Start Time', 'End Time']"
-            style="width: 100%;"
-          />
+        <a-form-item label="Date"
+          v-if="planBody.task_type === TaskType.Timed || planBody.task_type === TaskType.Condition"
+          name="select_execute_date" :labelCol="{ span: 23 }">
+          <a-range-picker v-model:value="planBody.select_execute_date"
+            :disabledDate="(current: any) => current < moment().subtract(1, 'days')" format="YYYY-MM-DD"
+            :placeholder="['Start Time', 'End Time']" style="width: 100%;" />
         </a-form-item>
-        <!-- execute time -->
-        <a-form-item label="Time" v-if="planBody.task_type === TaskType.Timed || planBody.task_type === TaskType.Condition"
-          name="select_execute_time" ref="select_execute_time" :labelCol="{span: 23}" :autoLink="false">
-          <div class="mb10 flex-row flex-align-center flex-justify-around" v-for="n in planBody.select_time_number" :key="n">
-            <a-time-picker
-              v-model:value="planBody.select_time[n - 1][0]"
-              format="HH:mm:ss"
-              show-time
-              placeholder="Start Time"
-              :style="planBody.task_type === TaskType.Condition ? 'width: 40%' : 'width: 82%'"
-              @change="() => $refs.select_execute_time.onFieldChange()"
-            />
+        <a-form-item label="Time"
+          v-if="planBody.task_type === TaskType.Timed || planBody.task_type === TaskType.Condition"
+          name="select_execute_time" ref="select_execute_time" :labelCol="{ span: 23 }" :autoLink="false">
+          <div class="mb10 flex-row flex-align-center flex-justify-around" v-for="n in planBody.select_time_number"
+            :key="n">
+            <a-time-picker v-model:value="planBody.select_time[n - 1][0]" format="HH:mm:ss" show-time
+              placeholder="Start Time" :style="planBody.task_type === TaskType.Condition ? 'width: 40%' : 'width: 82%'"
+              @change="() => $refs.select_execute_time.onFieldChange()" />
             <template v-if="planBody.task_type === TaskType.Condition">
               <div><span style="color: white;">-</span></div>
-              <a-time-picker
-                v-model:value="planBody.select_time[n - 1][1]"
-                format="HH:mm:ss"
-                show-time
-                placeholder="End Time"
-                style="width: 40%;"
-              />
+              <a-time-picker v-model:value="planBody.select_time[n - 1][1]" format="HH:mm:ss" show-time
+                placeholder="End Time" style="width: 40%;" />
             </template>
             <div class="ml5" style="font-size:18px">
-              <PlusCircleOutlined class="mr5" style="color: #1890ff" @click="addTime"/>
-              <MinusCircleOutlined :style="planBody.select_time_number === 1 ? 'color: gray' : 'color: red;'" @click="removeTime"/>
+              <PlusCircleOutlined class="mr5" style="color: #1890ff" @click="addTime" />
+              <MinusCircleOutlined :style="planBody.select_time_number === 1 ? 'color: gray' : 'color: red;'"
+                @click="removeTime" />
             </div>
           </div>
         </a-form-item>
         <template v-if="planBody.task_type === TaskType.Condition">
           <!-- battery capacity -->
-          <a-form-item label="Start task when battery level reaches" :labelCol="{span: 23}" name="min_battery_capacity">
+          <a-form-item label="Start task when battery level reaches" :labelCol="{ span: 23 }"
+            name="min_battery_capacity">
             <a-input-number class="width-100" v-model:value="planBody.min_battery_capacity" :min="50" :max="100"
-            :formatter="(value: number) => `${value}%`" :parser="(value: string) => value.replace('%', '')">
+              :formatter="(value: number) => `${value}%`" :parser="(value: string) => value.replace('%', '')">
             </a-input-number>
           </a-form-item>
           <!-- storage capacity -->
-          <a-form-item label="Start task when storage level reaches (MB)" :labelCol="{span: 23}" name="storage_capacity">
+          <a-form-item label="Start task when storage level reaches (MB)" :labelCol="{ span: 23 }"
+            name="storage_capacity">
             <a-input-number v-model:value="planBody.min_storage_capacity" class="width-100">
             </a-input-number>
           </a-form-item>
         </template>
-        <!-- RTH Altitude Relative to Dock -->
-        <a-form-item label="RTH Altitude Relative to Dock (m)" :labelCol="{span: 23}" name="rth_altitude">
+        <a-form-item label="RTH Altitude Relative to Dock (m)" :labelCol="{ span: 23 }" name="rth_altitude">
           <a-input-number v-model:value="planBody.rth_altitude" :min="20" :max="1500" class="width-100" required>
           </a-input-number>
         </a-form-item>
-        <!-- Lost Action -->
-        <a-form-item label="Lost Action" :labelCol="{span: 23}" name="out_of_control_action">
+        <a-form-item label="Lost Action" :labelCol="{ span: 23 }" name="out_of_control_action">
           <div style="white-space: nowrap;">
             <a-radio-group v-model:value="planBody.out_of_control_action" button-style="solid">
               <a-radio-button v-for="action in OutOfControlActionOptions" :value="action.value" :key="action.value">
@@ -146,19 +144,21 @@
       </a-form>
     </div>
   </div>
-  <div v-if="drawerVisible" style="position: absolute; left: 335px; width: 280px; height: 100vh; float: right; top: 0; z-index: 1000; color: white; background: #282828;">
+  <div v-if="drawerVisible"
+    style="position: absolute; left: 335px; width: 280px; height: 100vh; float: right; top: 0; z-index: 1000; color: white; background: #282828;">
     <div>
-      <router-view :name="routeName"/>
+      <router-view :name="routeName" />
     </div>
     <div style="position: absolute; top: 15px; right: 10px;">
-      <a style="color: white;" @click="closePanel"><CloseOutlined /></a>
+      <a style="color: white;" @click="closePanel">
+        <CloseOutlined />
+      </a>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, onUnmounted, reactive, ref, toRaw, UnwrapRef } from 'vue'
-import { CloseOutlined, RocketOutlined, CameraFilled, UserOutlined, PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons-vue'
+import { computed, reactive, ref } from 'vue'
 import { ELocalStorageKey, ERouterName } from '/@/types'
 import { useMyStore } from '/@/store'
 import { WaylineType, WaylineFile } from '/@/types/wayline'
@@ -166,7 +166,7 @@ import { Device, DEVICE_NAME } from '/@/types/device'
 import { createPlan, CreatePlan } from '/@/api/wayline'
 import { getRoot } from '/@/root'
 import { TaskType, OutOfControlActionOptions, OutOfControlAction, TaskTypeOptions } from '/@/types/task'
-import moment, { Moment } from 'moment'
+import moment from 'moment';
 import { RuleObject } from 'ant-design-vue/es/form/interface'
 
 const root = getRoot()
@@ -190,9 +190,9 @@ const planBody = reactive({
   file_id: computed(() => store.state?.waylineInfo.id),
   dock_sn: computed(() => store.state?.dockInfo.device_sn),
   task_type: TaskType.Immediate,
-  select_execute_date: [moment(), moment()] as Moment[],
+  select_execute_date: [moment(), moment()] as any,
   select_time_number: 1,
-  select_time: [[]] as Moment[][],
+  select_time: [[]] as any[],
   rth_altitude: '',
   out_of_control_action: OutOfControlAction.ReturnToHome,
   min_battery_capacity: 90 as number,
@@ -209,7 +209,7 @@ const rules = {
   file_id: [{ required: true, message: 'Select Route' }],
   dock_sn: [{ required: true, message: 'Select Device' }],
   select_execute_time: [{
-    validator: async (rule: RuleObject, value: Moment[]) => {
+    validator: async (rule: RuleObject, value: any[]) => {
       validEndTime()
       validStartTime()
       if (planBody.select_time.length < planBody.select_time_number) {
@@ -240,14 +240,14 @@ const rules = {
   out_of_control_action: [{ required: true, message: 'Select Lost Action' }],
 }
 
-function validStartTime (): Error | void {
+function validStartTime(): Error | void {
   for (let i = 0; i < planBody.select_time.length; i++) {
     if (!planBody.select_time[i][0]) {
       throw new Error('Select start time')
     }
   }
 }
-function validEndTime (): Error | void {
+function validEndTime(): Error | void {
   if (TaskType.Condition !== planBody.task_type) return
   for (let i = 0; i < planBody.select_time.length; i++) {
     if (!planBody.select_time[i][1]) {
@@ -258,7 +258,7 @@ function validEndTime (): Error | void {
     }
   }
 }
-function validOverlapped (): Error | void {
+function validOverlapped(): Error | void {
   if (TaskType.Condition !== planBody.task_type) return
   const arr = planBody.select_time.slice()
   arr.sort((a, b) => a[0].unix() - b[0].unix())
@@ -269,7 +269,7 @@ function validOverlapped (): Error | void {
   })
 }
 
-function onSubmit () {
+function onSubmit() {
   console.info(dock, '12131231')
   valueRef.value.validate().then(() => {
     disabled.value = true
@@ -306,32 +306,32 @@ function onSubmit () {
   })
 }
 
-function closePlan () {
+function closePlan() {
   root.$router.push('/' + ERouterName.TASK)
 }
 
-function closePanel () {
+function closePanel() {
   drawerVisible.value = false
   routeName.value = ''
 }
 
-function selectRoute () {
+function selectRoute() {
   drawerVisible.value = true
   routeName.value = 'WaylinePanel'
 }
 
-function selectDevice () {
+function selectDevice() {
   drawerVisible.value = true
   routeName.value = 'DockPanel'
 }
 
-function addTime () {
+function addTime() {
   valueRef.value.validateFields(['select_execute_time']).then(() => {
     planBody.select_time_number++
     planBody.select_time.push([])
   })
 }
-function removeTime () {
+function removeTime() {
   if (planBody.select_time_number === 1) return
   planBody.select_time_number--
   planBody.select_time.splice(planBody.select_time_number)
@@ -370,8 +370,13 @@ function removeTime () {
       margin: 10px;
     }
 
-    form label, input, .ant-input, .ant-calendar-range-picker-separator,
-    .ant-input:hover, .ant-time-picker .anticon, .ant-calendar-picker .anticon {
+    form label,
+    input,
+    .ant-input,
+    .ant-calendar-range-picker-separator,
+    .ant-input:hover,
+    .ant-time-picker .anticon,
+    .ant-calendar-picker .anticon {
       background-color: #232323;
       color: #fff;
     }
@@ -382,12 +387,13 @@ function removeTime () {
 
     .plan-timer-form-item {
 
-      .ant-radio-button-wrapper{
+      .ant-radio-button-wrapper {
         background-color: #232323;
         color: #fff;
         width: 33%;
         text-align: center;
-        &.ant-radio-button-wrapper-checked{
+
+        &.ant-radio-button-wrapper-checked {
           background-color: #1890ff;
         }
       }
@@ -396,11 +402,11 @@ function removeTime () {
 
   .footer {
     display: flex;
-    padding:10px 0;
+    padding: 10px 0;
 
     button {
       width: 45%;
-      color: #fff ;
+      color: #fff;
       border: 0;
     }
   }
@@ -416,6 +422,7 @@ function removeTime () {
   font-size: 13px;
   border-radius: 2px;
   cursor: pointer;
+
   .title {
     display: flex;
     color: white;
@@ -437,6 +444,7 @@ function removeTime () {
   font-size: 13px;
   border-radius: 2px;
   cursor: pointer;
+
   .title {
     display: flex;
     color: white;
@@ -447,4 +455,4 @@ function removeTime () {
     margin: 0px 10px 0 10px;
   }
 }
-</style>
+</style>

+ 1 - 2
Web/src/components/task/TaskPanel.vue

@@ -84,10 +84,9 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from '@vue/reactivity'
 import { message } from 'ant-design-vue'
 import { TableState } from 'ant-design-vue/lib/table/interface'
-import { onMounted } from 'vue'
+import { onMounted, reactive} from 'vue'
 import { IPage } from '/@/api/http/type'
 import { deleteTask, updateTaskStatus, UpdateTaskStatus, getWaylineJobs, Task, uploadMediaFileNow } from '/@/api/wayline'
 import { useMyStore } from '/@/store'

+ 1 - 1
Web/src/hooks/use-connect-websocket.ts

@@ -7,7 +7,7 @@ import { getWebsocketUrl } from '/@/websocket/util/config'
  * 接收一个message函数
  * @param messageHandler
  */
-export function useConnectWebSocket (messageHandler: MessageHandler) {
+export function useConnectWebSocket(messageHandler: MessageHandler) {
   const webSocket = new ConnectWebSocket(getWebsocketUrl())
 
   onMounted(() => {

+ 2 - 2
Web/src/hooks/use-map-tool.ts

@@ -1,12 +1,12 @@
 import { GeojsonCoordinate } from '../utils/genjson'
 import { getRoot } from '/@/root'
 
-export function useMapTool () {
+export function useMapTool() {
   const root = getRoot()
   const map = root.$map
   const AMap = root.$aMap
 
-  function panTo (coordinate: GeojsonCoordinate) {
+  function panTo(coordinate: GeojsonCoordinate) {
     map.panTo(coordinate, 100)
     map.setZoom(18, false, 100)
   }

+ 8 - 8
Web/src/hooks/use-mouse-tool.ts

@@ -6,7 +6,7 @@ import { MapDoodleEnum } from '/@/types/map-enum'
 import { EFlightAreaType } from '../types/flight-area'
 import { message } from 'ant-design-vue'
 
-export function useMouseTool () {
+export function useMouseTool() {
   const root = getRoot()
 
   const state = reactive({
@@ -19,7 +19,7 @@ export function useMouseTool () {
     [EFlightAreaType.DFENCE]: '#19be6b',
     [EFlightAreaType.NFZ]: '#ff0000',
   }
-  function drawPin (type:MapDoodleType, getDrawCallback:Function) {
+  function drawPin(type: MapDoodleType, getDrawCallback: Function) {
     root?.$mouseTool.marker({
       title: type + state.pinNum,
       icon: pin2d8cf0,
@@ -28,7 +28,7 @@ export function useMouseTool () {
     root?.$mouseTool.on('draw', getDrawCallback)
   }
 
-  function drawPolyline (type:MapDoodleType, getDrawCallback:Function) {
+  function drawPolyline(type: MapDoodleType, getDrawCallback: Function) {
     root?.$mouseTool.polyline({
       strokeColor: '#2d8cf0',
       strokeOpacity: 1,
@@ -39,7 +39,7 @@ export function useMouseTool () {
     root?.$mouseTool.on('draw', getDrawCallback)
   }
 
-  function drawPolygon (type:MapDoodleType, getDrawCallback:Function) {
+  function drawPolygon(type: MapDoodleType, getDrawCallback: Function) {
     root?.$mouseTool.polygon({
       strokeColor: '#2d8cf0',
       strokeOpacity: 1,
@@ -51,12 +51,12 @@ export function useMouseTool () {
     root?.$mouseTool.on('draw', getDrawCallback)
   }
 
-  function drawOff (type:MapDoodleType) {
+  function drawOff(type: MapDoodleType) {
     root?.$mouseTool.close()
     root?.$mouseTool.off('draw')
   }
 
-  function drawFlightAreaPolygon (type: EFlightAreaType, getDrawFlightAreaCallback: Function) {
+  function drawFlightAreaPolygon(type: EFlightAreaType, getDrawFlightAreaCallback: Function) {
     root?.$mouseTool.polygon({
       strokeColor: flightAreaColorMap[type],
       strokeOpacity: 1,
@@ -73,7 +73,7 @@ export function useMouseTool () {
     root?.$mouseTool.on('draw', getDrawFlightAreaCallback)
   }
 
-  function drawFlightAreaCircle (type: EFlightAreaType, getDrawFlightAreaCallback: Function) {
+  function drawFlightAreaCircle(type: EFlightAreaType, getDrawFlightAreaCallback: Function) {
     root?.$mouseTool.circle({
       strokeColor: flightAreaColorMap[type],
       strokeOpacity: 1,
@@ -90,7 +90,7 @@ export function useMouseTool () {
     root?.$mouseTool.on('draw', getDrawFlightAreaCallback)
   }
 
-  function mouseTool (type: MapDoodleType, getDrawCallback: Function, flightAreaType?: EFlightAreaType) {
+  function mouseTool(type: MapDoodleType, getDrawCallback: Function, flightAreaType?: EFlightAreaType) {
     state.currentType = type
     if (flightAreaType) {
       switch (type) {

+ 5 - 2
Web/src/main.ts

@@ -1,15 +1,18 @@
 import App from './App.vue'
 import router from './router'
-import { antComponents } from './antd'
 import store, { storeKey } from './store'
 import { createInstance } from '/@/root'
 import { useDirectives } from './directives'
+import Antd from 'ant-design-vue';
+import svgIcon from '/@/components/svgIcon.vue'
+import 'ant-design-vue/dist/antd.css'
 import '/@/styles/index.scss'
 
 const app = createInstance(App)
 
 app.use(store, storeKey)
 app.use(router)
-app.use(antComponents)
+app.use(Antd)
+app.component('svg-icon', svgIcon)
 app.use(useDirectives)
 app.mount('#root')

+ 8 - 1
Web/src/pages/page-web/home.vue

@@ -5,7 +5,7 @@
       <a-layout-header class="header">
         <Topbar />
       </a-layout-header>
-      <a-layout-content>
+      <a-layout-content class="content">
         <router-view />
       </a-layout-content>
     </a-layout>
@@ -72,4 +72,11 @@ onMounted(() => {
   font-size: 15px;
   padding: 0 20px;
 }
+
+.content {
+  width: 100%;
+  height: 100%;
+  background: #F7F9FA;
+  overflow: auto;
+}
 </style>

+ 2 - 2
Web/src/pages/page-web/index.vue

@@ -38,8 +38,8 @@ import { ELocalStorageKey, ERouterName, EUserType } from '/@/types'
 const root = getRoot()
 
 const formState: UnwrapRef<LoginBody> = reactive({
-  username: 'adminPC',
-  password: 'adminPC',
+  username: '',
+  password: '',
   flag: EUserType.Web,
 })
 

+ 19 - 426
Web/src/pages/page-web/projects/devices.vue

@@ -1,442 +1,35 @@
 <template>
-  <a-menu v-model:selectedKeys="current" mode="horizontal" @select="select">
-    <a-menu-item :key="3">
+  <a-menu v-model:selectedKeys="state.selectedKeys" mode="horizontal">
+    <a-menu-item :key="1">
       设备列表
     </a-menu-item>
-    <a-menu-item :key="0">
+    <a-menu-item :key="2">
       机场异常反馈记录
     </a-menu-item>
-    <a-menu-item :key="2">
+    <a-menu-item :key="3">
       所有设备变化记录
     </a-menu-item>
   </a-menu>
-  <div class="device-table-wrap table flex-display flex-column">
-    <a-table :columns="columns" :data-source="data.device" :pagination="paginationProp" @change="refreshData"
-      row-key="device_sn" :expandedRowKeys="expandRows" :row-selection="rowSelection" :rowClassName="rowClassName"
-      :scroll="{ x: '100%', y: 600 }" :expandIcon="expandIcon" :loading="loading">
-      <template v-for="col in ['nickname']" #[col]="{ text, record }" :key="col">
-        <div>
-          <a-input v-if="editableData[record.device_sn]" v-model:value="editableData[record.device_sn][col]"
-            style="margin: -5px 0" />
-          <template v-else>
-            <a-tooltip :title="text">
-              {{ text }}
-            </a-tooltip>
-          </template>
-        </div>
-      </template>
-      <template v-for="col in ['sn', 'workspace']" #[col]="{ text }" :key="col">
-        <a-tooltip :title="text">
-          <span>{{ text }}</span>
-        </a-tooltip>
-      </template>
-      <!-- 固件版本 -->
-      <template #firmware_version="{ record }">
-        <span v-if="judgeCurrentType(EDeviceTypeName.Dock)">
-          <DeviceFirmwareUpgrade :device="record" class="table-flex-col" @device-upgrade="onDeviceUpgrade" />
-        </span>
-        <span v-else>
-          {{ record.firmware_version }}
-        </span>
-      </template>
-      <!-- 固件升级 -->
-      <template #firmware_status="{ text }">
-        <div v-if="text === -1">
-          不支持
-        </div>
-        <div v-else>
-          {{ DeviceFirmwareStatus[text] }}
-        </div>
-      </template>
-      <!-- 状态 -->
-      <template #status="{ text }">
-        <span v-if="text" class="flex-row flex-align-center">
-          <span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: green;" />
-          <span>在线</span>
-        </span>
-        <span class="flex-row flex-align-center" v-else>
-          <span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: red;" />
-          <span>离线</span>
-        </span>
-      </template>
-      <!-- 操作 -->
-      <template #action="{ record }">
-        <div class="editable-row-operations">
-          <!-- 编辑态操作 -->
-          <div v-if="editableData[record.device_sn]">
-            <a-tooltip title="Confirm changes">
-              <span @click="save(record)" style="color: #28d445;">
-                <CheckOutlined />
-              </span>
-            </a-tooltip>
-            <a-tooltip title="Modification canceled">
-              <span @click="() => delete editableData[record.device_sn]" style="color: #e70102;">
-                <CloseOutlined />
-              </span>
-            </a-tooltip>
-          </div>
-          <!-- 非编辑态操作 -->
-          <div v-else class="flex-align-center flex-row" style="color: #2d8cf0">
-            <a-tooltip v-if="current.indexOf(EDeviceTypeName.Dock) !== -1" title="设备日志">
-              <CloudServerOutlined @click="showDeviceLogUploadRecord(record)" />
-            </a-tooltip>
-            <a-tooltip v-if="current.indexOf(EDeviceTypeName.Dock) !== -1" title="Hms Info">
-              <FileSearchOutlined @click="showHms(record)" />
-            </a-tooltip>
-            <a-tooltip title="Edit">
-              <EditOutlined @click="edit(record)" />
-            </a-tooltip>
-            <a-tooltip title="Delete">
-              <DeleteOutlined @click="() => { deleteTip = true, deleteSn = record.device_sn }" />
-            </a-tooltip>
-          </div>
-        </div>
-      </template>
-
-    </a-table>
-    <a-modal v-model:visible="deleteTip" width="450px" :closable="false" centered :okButtonProps="{ danger: true }"
-      @ok="unbind">
-      <p class="pt10 pl20" style="height: 50px;">Delete device from workspace?</p>
-      <template #title>
-        <div class="flex-row flex-justify-center">
-          <span>Delete devices</span>
-        </div>
-      </template>
-    </a-modal>
-    <!-- 设备升级 -->
-    <DeviceFirmwareUpgradeModal title="设备升级" v-model:visible="deviceFirmwareUpgradeModalVisible"
-      :device="selectedDevice" @ok="onUpgradeDeviceOk"></DeviceFirmwareUpgradeModal>
-    <!-- 设备日志上传记录 -->
-    <DeviceLogUploadRecordDrawer v-model:visible="deviceLogUploadRecordVisible" :device="currentDevice">
-    </DeviceLogUploadRecordDrawer>
-    <!-- hms 信息 -->
-    <DeviceHmsDrawer v-model:visible="hmsVisible" :device="currentDevice">
-    </DeviceHmsDrawer>
+  <div v-if="state.selectedKeys[0] === 1">
+    <DeviceList />
+  </div>
+  <div v-else-if="state.selectedKeys[0] === 2">
+    <FeedbackRecord />
+  </div>
+  <div v-else-if="state.selectedKeys[0] === 3">
+    <ChangeRecord />
   </div>
 </template>
 
 <script lang="ts" setup>
-import { h, onMounted, reactive, ref, UnwrapRef } from 'vue'
-import { notification } from 'ant-design-vue'
-import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
-import { getBindingDevices, unbindDevice, updateDevice } from '/@/api/manage'
-import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
-import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, FileSearchOutlined, CloudServerOutlined } from '@ant-design/icons-vue'
-import { Device, DeviceFirmwareStatus, DeviceFirmwareStatusEnum } from '/@/types/device'
-import DeviceFirmwareUpgrade from '/@/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue'
-import DeviceFirmwareUpgradeModal from '/@/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue'
-import { useDeviceFirmwareUpgrade } from '/@/components/devices/device-upgrade/use-device-upgrade'
-import { useDeviceUpgradeEvent } from '/@/components/devices/device-upgrade/use-device-upgrade-event'
-import { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd'
-import DeviceLogUploadRecordDrawer from '/@/components/devices/device-log/DeviceLogUploadRecordDrawer.vue'
-import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue'
-import { IPage } from '/@/api/http/type'
-
-interface DeviceData {
-  device: Device[]
-}
-
-const loading = ref(true)
-const deleteTip = ref<boolean>(false)
-const deleteSn = ref<string>()
-
-const columns: ColumnProps[] = [
-  {
-    title: '设备型号', dataIndex: 'device_name', width: 150,
-    sorter: (a: Device, b: Device) => a.nickname.localeCompare(b.nickname),
-    className: 'titleStyle'
-  },
-  { title: '设备SN', dataIndex: 'device_sn', width: 100, className: 'titleStyle', ellipsis: true, slots: { customRender: 'sn' } },
-  {
-    title: '设备名称',
-    dataIndex: 'nickname',
-    width: 150,
-    sorter: (a: Device, b: Device) => a.nickname.localeCompare(b.nickname),
-    className: 'titleStyle',
-    ellipsis: true,
-    slots: { customRender: 'nickname' }
-  },
-  { title: '固件版本', dataIndex: 'firmware_version', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_version' } },
-  { title: '固件升级', dataIndex: 'firmware_status', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_status' } },
-  { title: '当前状态', dataIndex: 'status', width: 100, className: 'titleStyle', slots: { customRender: 'status' } },
-  { title: '加入项目时间', dataIndex: 'bound_time', width: 150, sorter: (a: Device, b: Device) => a.bound_time.localeCompare(b.bound_time), className: 'titleStyle' },
-  { title: '最后在线时间', dataIndex: 'login_time', width: 150, sorter: (a: Device, b: Device) => a.login_time.localeCompare(b.login_time), className: 'titleStyle' },
-  {
-    title: '操作',
-    dataIndex: 'actions',
-    fixed: 'right',
-    width: 100,
-    className: 'titleStyle',
-    slots: { customRender: 'action' }
-  },
-]
-
-const expandIcon = (props: any) => {
-  if (judgeCurrentType(EDeviceTypeName.Dock) && !props.expanded) {
-    return h('div',
-      {
-        style: 'border-left: 2px solid rgb(200,200,200); border-bottom: 2px solid rgb(200,200,200); height: 16px; width: 16px; float: left;',
-        class: 'mt-5 ml0',
-      })
-  }
-}
-
-const rowClassName = (record: any, index: number) => {
-  const className = []
-  if ((index & 1) === 0) {
-    className.push('table-striped')
-  }
-  if (record.domain !== EDeviceTypeName.Dock) {
-    className.push('child-row')
-  }
-  return className.toString().replaceAll(',', ' ')
-}
-
-const expandRows = ref<string[]>([])
-const data = reactive<DeviceData>({
-  device: []
-})
-
-const paginationProp = reactive({
-  pageSizeOptions: ['20', '50', '100'],
-  showQuickJumper: true,
-  showSizeChanger: true,
-  pageSize: 50,
-  current: 1,
-  total: 0
-})
-
-// 获取分页信息
-function getPaginationBody() {
-  return {
-    page: paginationProp.current,
-    page_size: paginationProp.pageSize
-  } as IPage
-}
-
-const rowSelection = {
-  onChange: (selectedRowKeys: (string | number)[], selectedRows: []) => {
-    console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)
-  },
-  onSelect: (record: any, selected: boolean, selectedRows: []) => {
-    console.log(record, selected, selectedRows)
-  },
-  onSelectAll: (selected: boolean, selectedRows: [], changeRows: []) => {
-    console.log(selected, selectedRows, changeRows)
-  },
-  getCheckboxProps: (record: any) => ({
-    disabled: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock,
-    style: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock ? 'display: none' : ''
-  }),
-}
-type Pagination = TableState['pagination']
-
-const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
-const editableData: UnwrapRef<Record<string, Device>> = reactive({})
-const current = ref([EDeviceTypeName.Dock])
-
-function judgeCurrentType(type: EDeviceTypeName): boolean {
-  return current.value.indexOf(type) !== -1
-}
-
-// 设备升级
-const {
-  deviceFirmwareUpgradeModalVisible,
-  selectedDevice,
-  onDeviceUpgrade,
-  onUpgradeDeviceOk
-} = useDeviceFirmwareUpgrade(workspaceId)
-
-function onDeviceUpgradeWs(payload: DeviceCmdExecuteInfo) {
-  updateDevicesByWs(data.device, payload)
-}
-
-function updateDevicesByWs(devices: Device[], payload: DeviceCmdExecuteInfo) {
-  if (!devices || devices.length <= 0) {
-    return
-  }
-  for (let i = 0; i < devices.length; i++) {
-    if (devices[i].device_sn === payload.sn) {
-      if (!payload.output) return
-      const { status, progress, ext } = payload.output
-      if (status === DeviceCmdExecuteStatus.Sent || status === DeviceCmdExecuteStatus.InProgress) { // 升级中
-        const rate = ext?.rate ? (ext.rate / 1024).toFixed(2) + 'kb/s' : ''
-        devices[i].firmware_status = DeviceFirmwareStatusEnum.DuringUpgrade
-        devices[i].firmware_progress = (progress?.percent || 0) + '% ' + rate
-      } else { // 终态:成功,失败,超时
-        if (status === DeviceCmdExecuteStatus.Failed || status === DeviceCmdExecuteStatus.Timeout) {
-          notification.error({
-            message: `(${payload.sn}) Upgrade failed`,
-            description: `Error Code: ${payload.result}`,
-            duration: null
-          })
-        }
-        // 拉取列表
-        getDevices(current.value[0], true)
-      }
-      return
-    }
-    if (devices[i].children) {
-      updateDevicesByWs(devices[i].children || [], payload)
-    }
-  }
-}
+import { reactive } from 'vue';
+import DeviceList from '/@/components/devices/deviceList/index.vue'
+import FeedbackRecord from '/@/components/devices/feedbackRecord/index.vue'
+import ChangeRecord from '/@/components/devices/changeRecord/index.vue'
 
-useDeviceUpgradeEvent(onDeviceUpgradeWs)
-
-// 获取设备列表信息
-function getDevices(domain: number, closeLoading?: boolean) {
-  if (!closeLoading) {
-    loading.value = true
-  }
-  getBindingDevices(workspaceId, getPaginationBody(), domain).then(res => {
-    if (res.code !== 0) {
-      return
-    }
-    const resData: Device[] = res.data.list
-    expandRows.value = []
-    resData.forEach((val: any) => {
-      if (val.children) {
-        val.children = [val.children]
-      }
-      if (judgeCurrentType(EDeviceTypeName.Dock)) {
-        expandRows.value.push(val.device_sn)
-      }
-    })
-    data.device = resData
-    paginationProp.total = res.data.pagination.total
-    paginationProp.current = res.data.pagination.page
-    paginationProp.pageSize = res.data.pagination.page_size
-    loading.value = false
-  })
-}
-
-function refreshData(page: Pagination) {
-  paginationProp.current = page?.current!
-  paginationProp.pageSize = page?.pageSize!
-  getDevices(current.value[0])
-}
-
-// 编辑
-function edit(record: Device) {
-  editableData[record.device_sn] = record
-}
-
-// 保存
-function save(record: Device) {
-  delete editableData[record.device_sn]
-  updateDevice({ nickname: record.nickname }, workspaceId, record.device_sn)
-}
-
-// 删除
-function showDeleteTip(sn: any) {
-  deleteTip.value = true
-}
-
-// 解绑
-function unbind() {
-  deleteTip.value = false
-  unbindDevice(deleteSn.value?.toString()!).then(res => {
-    if (res.code !== 0) {
-      return
-    }
-    getDevices(current.value[0])
-  })
-}
-
-// 选择设备
-function select(item: any) {
-  getDevices(item.key)
-}
-
-const currentDevice = ref({} as Device)
-// 设备日志
-const deviceLogUploadRecordVisible = ref(false)
-function showDeviceLogUploadRecord(dock: Device) {
-  deviceLogUploadRecordVisible.value = true
-  currentDevice.value = dock
-}
-
-// 健康状态
-const hmsVisible = ref<boolean>(false)
-
-function showHms(dock: Device) {
-  hmsVisible.value = true
-  currentDevice.value = dock
-}
-
-onMounted(() => {
-  getDevices(current.value[0])
+const state = reactive({
+  selectedKeys: [1],
 })
 </script>
 
-<style lang="scss" scoped>
-.device-table-wrap {
-  .editable-row-operations {
-    div>span {
-      margin-right: 10px;
-    }
-  }
-}
-</style>
-
-<style lang="scss">
-.table {
-  background-color: white;
-  margin: 20px;
-  padding: 20px;
-  height: 88vh;
-}
-
-.table-striped {
-  background-color: #f7f9fa;
-}
-
-.ant-table {
-  border-top: 1px solid rgb(0, 0, 0, 0.06);
-  border-bottom: 1px solid rgb(0, 0, 0, 0.06);
-}
-
-.ant-table-tbody tr td {
-  border: 0;
-}
-
-.ant-table td {
-  white-space: nowrap;
-}
-
-.ant-table-thead tr th {
-  background: white !important;
-  border: 0;
-}
-
-th.ant-table-selection-column {
-  background-color: white !important;
-}
-
-.ant-table-header {
-  background-color: white !important;
-}
-
-.child-row {
-  height: 70px;
-}
-
-.notice {
-  background: $success;
-  overflow: hidden;
-  cursor: pointer;
-}
-
-.caution {
-  background: orange;
-  cursor: pointer;
-  overflow: hidden;
-}
-
-.warn {
-  background: red;
-  cursor: pointer;
-  overflow: hidden;
-}
-</style>
+<style lang="scss" scoped></style>

+ 14 - 9
Web/src/pages/page-web/projects/dock.vue

@@ -7,17 +7,20 @@
         <a-col :span="1"></a-col>
       </a-row>
     </div>
-    <div class="scrollbar height-100" :style="{ height: scorllHeight + 'px'}">
+    <div class="scrollbar height-100" :style="{ height: scorllHeight + 'px' }">
       <div id="data" class=" uranus-scrollbar" v-if="docksData.data.length !== 0" @scroll="onScroll">
         <div v-for="dock in docksData.data" :key="dock.device_sn">
           <div class="panel" style="padding-top: 5px;" @click="selectDock(dock)">
             <div class="title">
               <a-tooltip :title="dock.nickname">
-                <div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div>
+                <div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">
+                  {{ dock.nickname }}</div>
               </a-tooltip>
             </div>
             <div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
-              <span><RocketOutlined /></span>
+              <span>
+                <RocketOutlined />
+              </span>
               <span class="ml5">{{ dock.children?.nickname ?? 'No drone' }}</span>
             </div>
           </div>
@@ -31,9 +34,8 @@
 </template>
 
 <script lang="ts" setup>
-import { reactive } from '@vue/reactivity'
 import { message } from 'ant-design-vue'
-import { onMounted, ref } from 'vue'
+import { onMounted, reactive, ref } from 'vue'
 import { deleteWaylineFile, downloadWaylineFile, getWaylineFiles } from '/@/api/wayline'
 import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
 import { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'
@@ -66,13 +68,14 @@ onMounted(() => {
     getDocks()
   }, 1000)
 })
+
 const body: IPage = {
   page: 1,
   total: -1,
   page_size: 10,
 }
 
-async function getDocks () {
+async function getDocks() {
   if (!canRefresh.value) {
     return
   }
@@ -91,7 +94,7 @@ async function getDocks () {
   })
 }
 
-function onScroll (e: any) {
+function onScroll(e: any) {
   const element = e.srcElement
   if (element.scrollTop + element.clientHeight >= element.scrollHeight - 5 && Math.ceil(body.total / body.page_size) > body.page && canRefresh.value) {
     body.page++
@@ -99,7 +102,7 @@ function onScroll (e: any) {
   }
 }
 
-function selectDock (dock: Device) {
+function selectDock(dock: Device) {
   store.commit('SET_SELECT_DOCK_INFO', dock)
 }
 
@@ -116,6 +119,7 @@ function selectDock (dock: Device) {
   font-size: 13px;
   border-radius: 2px;
   cursor: pointer;
+
   .title {
     display: flex;
     flex-direction: row;
@@ -125,10 +129,11 @@ function selectDock (dock: Device) {
     margin: 0px 10px 0 10px;
   }
 }
+
 .uranus-scrollbar {
   overflow: auto;
   scrollbar-width: thin;
   scrollbar-color: #c5c8cc transparent;
   height: 100%;
 }
-</style>
+</style>

+ 42 - 81
Web/src/pages/page-web/projects/layer.vue

@@ -1,80 +1,32 @@
 <template>
   <div class="project-layer-wrapper height-100">
-    <div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;">
-      <a-row>
-        <a-col :span="1"></a-col>
-        <a-col :span="22">Annotations</a-col>
-        <a-col :span="1"></a-col>
-      </a-row>
+    <div class="scrollbar">
+      <LayersTree :layer-data="mapLayers" class="project-layer-content" @check="checkLayer" @select="selectLayer"
+        v-model:selectedKeys="selectedKeys" v-model:checkedKeys="checkedKeys" />
     </div>
-    <div class="scrollbar" :style="{ height: scorllHeight + 'px'}">
-    <LayersTree
-      :layer-data="mapLayers"
-      class="project-layer-content"
-      @check="checkLayer"
-      @select="selectLayer"
-      v-model:selectedKeys="selectedKeys"
-      v-model:checkedKeys="checkedKeys"
-    />
-    </div>
-    <a-drawer
-      title="Map Element"
-      placement="right"
-      :closable="true"
-      v-model:visible="visible"
-      :mask="false"
-      wrapClassName="drawer-element-wrapper"
-      @close="closeDrawer"
-      width="300"
-    >
+    <a-drawer title="Map Element" placement="right" :closable="true" v-model:visible="visible" :mask="false"
+      wrapClassName="drawer-element-wrapper" @close="closeDrawer" width="300">
       <div class="drawer-element-content">
         <div class="name element-item">
           <span class="title">Name:</span>
-          <a-input
-            v-model:value="layerState.layerName"
-            style="width:120px"
-            placeholder="element name"
-            @change="changeLayer"
-          />
+          <a-input v-model:value="layerState.layerName" style="width:120px" placeholder="element name"
+            @change="changeLayer" />
         </div>
-        <div
-          class="longitude element-item"
-          v-if="layerState.currentType === geoType.Point"
-        >
+        <div class="longitude element-item" v-if="layerState.currentType === geoType.Point">
           <span class="title">Longitude:</span>
-          <a-input
-            v-model:value="layerState.longitude"
-            style="width:120px"
-            placeholder="longitude"
-            @change="changeLayer"
-          />
+          <a-input v-model:value="layerState.longitude" style="width:120px" placeholder="longitude"
+            @change="changeLayer" />
         </div>
-        <div
-          class="latitude element-item"
-          v-if="layerState.currentType === geoType.Point"
-        >
+        <div class="latitude element-item" v-if="layerState.currentType === geoType.Point">
           <span class="title">Latitude:</span>
-          <a-input
-            v-model:value="layerState.latitude"
-            style="width:120px"
-            placeholder="latitude"
-            @change="changeLayer"
-          />
+          <a-input v-model:value="layerState.latitude" style="width:120px" placeholder="latitude"
+            @change="changeLayer" />
         </div>
         <div class="color-content">
           <span class="mr30">Color: </span>
-          <div
-            v-for="item in colors"
-            :key="item.id"
-            class="color-item"
-            :style="'background:' + item.color"
-            @click="changeColor(item)"
-          >
-            <svg-icon
-              v-if="item.color === layerState.color"
-              :size="18"
-              name="check"
-            ></svg-icon>
+          <div v-for="item in colors" :key="item.id" class="color-item" :style="'background:' + item.color"
+            @click="changeColor(item)">
+            <svg-icon v-if="item.color === layerState.color" :size="18" name="check"></svg-icon>
           </div>
         </div>
       </div>
@@ -132,14 +84,14 @@ const colors = ref<Color[]>([
 ])
 const scorllHeight = ref()
 
-async function getAllElement () {
+async function getAllElement() {
   getElementGroups('init')
   setTimeout(() => {
     useGMapCoverHook = useGMapCover()
     initMapCover()
   }, 1000)
 }
-function initMapCover () {
+function initMapCover() {
   mapLayers.value.forEach(item => {
     if (item.elements) {
       setMapCoverByElement(item.elements)
@@ -155,7 +107,7 @@ watch(
     deep: true
   }
 )
-function setMapCoverByElement (elements: LayerResource[]) {
+function setMapCoverByElement(elements: LayerResource[]) {
   elements.forEach(element => {
     const name = element.name
     const color = element.resource?.content.properties.color
@@ -163,7 +115,7 @@ function setMapCoverByElement (elements: LayerResource[]) {
     updateMapElement(element, name, color)
   })
 }
-function updateMapElement (
+function updateMapElement(
   element: LayerResource,
   name: string,
   color: string | undefined
@@ -185,10 +137,10 @@ function updateMapElement (
     useGMapCoverHook.updatePolygonElement(id, name, coordinates, color)
   }
 }
-function checkLayer (keys: string[]) {
+function checkLayer(keys: string[]) {
   console.log('checkLayer', keys, selectedKeys.value, checkedKeys.value)
 }
-function selectLayer (keys: string[], e) {
+function selectLayer(keys: string[], e) {
   // console.log('selectLayer', e.node.eventKey, e.selected)
   if (e.selected) {
     selectedKey.value = e.node.eventKey
@@ -199,7 +151,7 @@ function selectLayer (keys: string[], e) {
   store.commit('SET_DRAW_VISIBLE_INFO', visible.value)
   // store.dispatch('updateElement', { type: 'is_select', id: e.node.eventKey, bool: e.selected })
 }
-function getCurrentLayer (id: string) {
+function getCurrentLayer(id: string) {
   const Layers = store.state.Layers
   const key = id.replaceAll('resource__', '')
   // console.log('selectedKey.value', selectedKey.value)
@@ -219,7 +171,7 @@ function getCurrentLayer (id: string) {
   console.log('layer', layer)
   return layer
 }
-function setBaseInfo () {
+function setBaseInfo() {
   const layer = selectedLayer.value
   if (layer) {
     const geoType = layer.resource?.content.geometry.type
@@ -248,19 +200,19 @@ onMounted(() => {
   scorllHeight.value = parent?.clientHeight - parent.firstElementChild!.clientHeight
   getAllElement()
 })
-function closeDrawer () {
+function closeDrawer() {
   store.commit('SET_DRAW_VISIBLE_INFO', false)
   selectedKeys.value = []
 }
-function changeColor (color: Color) {
+function changeColor(color: Color) {
   layerState.color = color.color
 
   updateElements()
 }
-function changeLayer (val: string) {
+function changeLayer(val: string) {
   updateElements()
 }
-async function deleteElement () {
+async function deleteElement() {
   const elementid = selectedLayer.value.id
 
   await deleteElementReq(elementid, {}).then(async (res: any) => {
@@ -275,7 +227,7 @@ async function deleteElement () {
     getElementGroups()
   })
 }
-async function getElementGroups (type?: string) {
+async function getElementGroups(type?: string) {
   const result = await getElementGroupsReq({
     groupId: '',
     isDistributed: true
@@ -287,7 +239,7 @@ async function getElementGroups (type?: string) {
   }
   store.commit('SET_LAYER_INFO', mapLayers.value)
 }
-async function updateElements () {
+async function updateElements() {
   let content = null
   if (layerState.currentType === GeoType.Point) {
     const position = {
@@ -320,7 +272,7 @@ async function updateElements () {
   getElementGroups()
 }
 
-function updateWgs84togcj02 () {
+function updateWgs84togcj02() {
   const layers = mapLayers.value
   layers.forEach(item => {
     if (item.elements) {
@@ -331,7 +283,7 @@ function updateWgs84togcj02 () {
   })
   return layers
 }
-function updateCoordinates (transformType: string, element: LayerResource) {
+function updateCoordinates(transformType: string, element: LayerResource) {
   const geoType = element.resource?.content.geometry.type
   const type = element.resource?.type as number
   if (element.resource) {
@@ -403,25 +355,31 @@ function updateCoordinates (transformType: string, element: LayerResource) {
   .ant-drawer-content {
     background-color: $dark-highlight;
     color: $text-white-basic;
+
     .ant-drawer-header {
       background-color: $dark-highlight;
+
       .ant-drawer-title {
         color: $text-white-basic;
       }
+
       .ant-drawer-close {
         color: $text-white-basic;
       }
     }
+
     .ant-input {
       background-color: #101010;
       border-color: $dark-border;
       color: $text-white-basic;
     }
   }
+
   .color-content {
     display: flex;
     align-items: center;
     margin-top: 8px;
+
     .color-item {
       cursor: pointer;
       width: 18px;
@@ -432,15 +390,18 @@ function updateCoordinates (transformType: string, element: LayerResource) {
       margin-left: 5px;
     }
   }
+
   .title {
     display: inline-flex;
     width: 80px;
   }
+
   .element-item {
     margin-bottom: 10px;
   }
 }
+
 .scrollbar {
   overflow: auto;
 }
-</style>
+</style>

+ 0 - 10
Web/src/pages/page-web/projects/media.vue

@@ -1,10 +0,0 @@
-<template>
-  <div class="project-media-wrapper">
-  </div>
-</template>
-
-<script lang="ts" setup>
-</script>
-
-<style lang="scss" scoped>
-</style>

+ 11 - 0
Web/src/pages/page-web/projects/media/index.vue

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    照片管理
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

+ 11 - 0
Web/src/pages/page-web/projects/replay/index.vue

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    视频回放
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

+ 5 - 5
Web/src/pages/page-web/projects/task.vue

@@ -6,13 +6,13 @@
         <a-col :span="20">Task Plan Library</a-col>
         <a-col :span="2">
           <span v-if="taskRoute">
-            <router-link :to="{ name: ERouterName.CREATE_PLAN}">
-              <PlusOutlined class="route-icon"/>
+            <router-link :to="{ name: ERouterName.CREATE_PLAN }">
+              <PlusOutlined class="route-icon" />
             </router-link>
           </span>
           <span v-else>
-            <router-link :to="{ name: ERouterName.TASK}">
-              <MinusOutlined class="route-icon"/>
+            <router-link :to="{ name: ERouterName.TASK }">
+              <MinusOutlined class="route-icon" />
             </router-link>
           </span>
         </a-col>
@@ -20,7 +20,7 @@
       </a-row>
     </div>
     <div v-if="!taskRoute">
-      <router-view/>
+      <router-view />
     </div>
   </div>
 </template>

+ 11 - 0
Web/src/pages/page-web/projects/trajectory/index.vue

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    轨迹回放
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

+ 159 - 84
Web/src/pages/page-web/projects/tsa.vue

@@ -1,68 +1,71 @@
 <template>
   <div class="project-tsa-wrapper ">
-    <div>
-      <a-row>
-        <a-col :span="1"></a-col>
-        <a-col :span="11">My Username</a-col>
-        <a-col :span="11" align="right" style="font-weight: 700">{{ username }}</a-col>
-        <a-col :span="1"></a-col>
-      </a-row>
-    </div>
-    <div class="scrollbar" :style="{ height: scorllHeight + 'px'}">
+    <div class="scrollbar" :style="{ height: scorllHeight + 'px' }">
       <a-collapse :bordered="false" expandIconPosition="right" accordion style="background: #232323;">
-        <a-collapse-panel :key="EDeviceTypeName.Dock" header="Dock" style="border-bottom: 1px solid #4f4f4f;">
+        <a-collapse-panel :key="EDeviceTypeName.Dock" header="机场" style="border-bottom: 1px solid #4f4f4f;">
           <div v-if="onlineDocks.data.length === 0" style="height: 150px; color: white;">
             <a-empty :image="noData" :image-style="{ height: '60px' }" />
           </div>
           <div v-else class="fz12" style="color: white;">
-            <div v-for="dock in onlineDocks.data" :key="dock.sn" style="background: #3c3c3c; height: 90px; width: 250px; margin-bottom: 10px;">
-              <div style="border-radius: 2px; height: 100%; width: 100%;" class="flex-row flex-justify-between flex-align-center">
+            <div v-for="dock in onlineDocks.data" :key="dock.sn"
+              style="background: #3c3c3c; height: 90px; width: 250px; margin-bottom: 10px;">
+              <div style="border-radius: 2px; height: 100%; width: 100%;"
+                class="flex-row flex-justify-between flex-align-center">
                 <div style="float: left; padding: 0px 5px 8px 8px; width: 88%">
                   <div style="width: 80%; height: 30px; line-height: 30px; font-size: 16px;">
                     <a-tooltip :title="`${dock.gateway.callsign} - ${dock.callsign ?? 'No Drone'}`">
-                      <div class="text-hidden" style="max-width: 200px;">{{ dock.gateway.callsign }} - {{ dock.callsign ?? 'No Drone' }}</div>
+                      <div class="text-hidden" style="max-width: 200px;">{{ dock.gateway.callsign }} - {{ dock.callsign
+                        ?? 'No Drone' }}</div>
                     </a-tooltip>
                   </div>
                   <div class="mt5 flex-align-center flex-row flex-justify-between" style="background: #595959;">
                     <div class="flex-align-center flex-row">
-                      <span class="ml5 mr5"><RobotOutlined /></span>
-                      <div class="font-bold text-hidden" style="max-width: 80px;" :style="dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.mode_code !== EDockModeCode.Disconnected ? 'color: #00ee8b' :  'color: red;'">
-                        {{ dockInfo[dock.gateway.sn] ? EDockModeCode[dockInfo[dock.gateway.sn].basic_osd?.mode_code] : EDockModeCode[EDockModeCode.Disconnected] }}
+                      <span class="ml5 mr5">
+                        <RobotOutlined />
+                      </span>
+                      <div class="font-bold text-hidden" style="max-width: 80px;"
+                        :style="dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.mode_code !== EDockModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'">
+                        {{ dockInfo[dock.gateway.sn] ? EDockModeCode[dockInfo[dock.gateway.sn].basic_osd?.mode_code] :
+                          EDockModeCode[EDockModeCode.Disconnected] }}
                       </div>
                     </div>
                     <div class="mr5 flex-align-center flex-row" style="width: 85px; margin-right: 0; height: 18px;">
                       <div v-if="hmsInfo[dock.gateway.sn]" class="flex-align-center flex-row">
-                          <div :class="hmsInfo[dock.gateway.sn][0].level === EHmsLevel.CAUTION ? 'caution-blink' :
-                            hmsInfo[dock.gateway.sn][0].level === EHmsLevel.WARN ? 'warn-blink' : 'notice-blink'" style="width: 18px; height: 16px; text-align: center;">
-                            <span :style="hmsInfo[dock.gateway.sn].length > 99 ? 'font-size: 11px' : 'font-size: 12px'">{{ hmsInfo[dock.gateway.sn].length }}</span>
-                            <span class="fz10">{{ hmsInfo[dock.gateway.sn].length > 99 ? '+' : ''}}</span>
-                          </div>
-                        <a-popover trigger="click" placement="bottom" color="black" v-model:visible="hmsVisible[dock.gateway.sn]"
+                        <div :class="hmsInfo[dock.gateway.sn][0].level === EHmsLevel.CAUTION ? 'caution-blink' :
+                          hmsInfo[dock.gateway.sn][0].level === EHmsLevel.WARN ? 'warn-blink' : 'notice-blink'"
+                          style="width: 18px; height: 16px; text-align: center;">
+                          <span :style="hmsInfo[dock.gateway.sn].length > 99 ? 'font-size: 11px' : 'font-size: 12px'">{{
+                            hmsInfo[dock.gateway.sn].length }}</span>
+                          <span class="fz10">{{ hmsInfo[dock.gateway.sn].length > 99 ? '+' : '' }}</span>
+                        </div>
+                        <a-popover trigger="click" placement="bottom" color="black"
+                          v-model:visible="hmsVisible[dock.gateway.sn]"
                           @visibleChange="readHms(hmsVisible[dock.gateway.sn], dock.gateway.sn)"
-                          :overlayStyle="{width: '200px', height: '300px'}">
+                          :overlayStyle="{ width: '200px', height: '300px' }">
                           <div :class="hmsInfo[dock.gateway.sn][0].level === EHmsLevel.CAUTION ? 'caution' :
-                            hmsInfo[dock.gateway.sn][0].level === EHmsLevel.WARN ? 'warn' : 'notice'" style="margin-left: 3px; width: 62px; height: 16px;">
+                            hmsInfo[dock.gateway.sn][0].level === EHmsLevel.WARN ? 'warn' : 'notice'"
+                            style="margin-left: 3px; width: 62px; height: 16px;">
                             <span class="word-loop">{{ hmsInfo[dock.gateway.sn][0].message_en }}</span>
                           </div>
                           <template #content>
-                            <a-collapse style="background: black; height: 300px; overflow-y: auto;" :bordered="false" expand-icon-position="right" :accordion="true">
-                              <a-collapse-panel v-for="hms in hmsInfo[dock.gateway.sn]" :key="hms.hms_id" :showArrow="false"
+                            <a-collapse style="background: black; height: 300px; overflow-y: auto;" :bordered="false"
+                              expand-icon-position="right" :accordion="true">
+                              <a-collapse-panel v-for="hms in hmsInfo[dock.gateway.sn]" :key="hms.hms_id"
+                                :showArrow="false"
                                 style=" margin: 0 auto 3px auto; border: 0; width: 140px; border-radius: 3px"
-                                :class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'"
-                                >
+                                :class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'">
                                 <template #header="{ isActive }">
                                   <div class="flex-row flex-align-center" style="width: 130px;">
                                     <div style="width: 110px;">
                                       <span class="word-loop">{{ hms.message_en }}</span>
                                     </div>
-                                    <div style="width: 20px; height: 15px; font-size: 10px; z-index: 2 " class="flex-row flex-align-center flex-justify-center"
-                                      :class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'"
-                                    >
+                                    <div style="width: 20px; height: 15px; font-size: 10px; z-index: 2 "
+                                      class="flex-row flex-align-center flex-justify-center"
+                                      :class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'">
                                       <DoubleRightOutlined :rotate="isActive ? 90 : 0" />
                                     </div>
                                   </div>
                                 </template>
-
                                 <a-tooltip :title="hms.create_time">
                                   <div style="color: white;" class="text-hidden">{{ hms.create_time }}</div>
                                 </a-tooltip>
@@ -76,43 +79,51 @@
                   </div>
                   <div class="mt5 flex-align-center flex-row flex-justify-between" style="background: #595959;">
                     <div class="flex-row">
-                      <span class="ml5 mr5"><RocketOutlined /></span>
-                      <div class="font-bold text-hidden" style="max-width: 80px" :style="deviceInfo[dock.sn] && deviceInfo[dock.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' :  'color: red;'">
-                        {{ deviceInfo[dock.sn] ? EModeCode[deviceInfo[dock.sn].mode_code] : EModeCode[EModeCode.Disconnected] }}
+                      <span class="ml5 mr5">
+                        <RocketOutlined />
+                      </span>
+                      <div class="font-bold text-hidden" style="max-width: 80px"
+                        :style="deviceInfo[dock.sn] && deviceInfo[dock.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'">
+                        {{ deviceInfo[dock.sn] ? EModeCode[deviceInfo[dock.sn].mode_code] :
+                          EModeCode[EModeCode.Disconnected] }}
                       </div>
                     </div>
                     <div class="mr5 flex-align-center flex-row" style="width: 85px; margin-right: 0; height: 18px;">
                       <div v-if="hmsInfo[dock.sn]" class="flex-align-center flex-row">
                         <div :class="hmsInfo[dock.sn][0].level === EHmsLevel.CAUTION ? 'caution-blink' :
-                          hmsInfo[dock.sn][0].level === EHmsLevel.WARN ? 'warn-blink' : 'notice-blink'" style="width: 18px; height: 16px; text-align: center;">
-                          <span :style="hmsInfo[dock.sn].length > 99 ? 'font-size: 11px' : 'font-size: 12px'">{{ hmsInfo[dock.sn].length }}</span>
-                          <span class="fz10">{{ hmsInfo[dock.sn].length > 99 ? '+' : ''}}</span>
+                          hmsInfo[dock.sn][0].level === EHmsLevel.WARN ? 'warn-blink' : 'notice-blink'"
+                          style="width: 18px; height: 16px; text-align: center;">
+                          <span :style="hmsInfo[dock.sn].length > 99 ? 'font-size: 11px' : 'font-size: 12px'">{{
+                            hmsInfo[dock.sn].length
+                            }}</span>
+                          <span class="fz10">{{ hmsInfo[dock.sn].length > 99 ? '+' : '' }}</span>
                         </div>
-                        <a-popover trigger="click" placement="bottom" color="black" v-model:visible="hmsVisible[dock.sn]" @visibleChange="readHms(hmsVisible[dock.sn], dock.sn)"
-                          :overlayStyle="{width: '200px', height: '300px'}">
+                        <a-popover trigger="click" placement="bottom" color="black"
+                          v-model:visible="hmsVisible[dock.sn]" @visibleChange="readHms(hmsVisible[dock.sn], dock.sn)"
+                          :overlayStyle="{ width: '200px', height: '300px' }">
                           <div :class="hmsInfo[dock.sn][0].level === EHmsLevel.CAUTION ? 'caution' :
-                            hmsInfo[dock.sn][0].level === EHmsLevel.WARN ? 'warn' : 'notice'" style="margin-left: 3px; width: 62px; height: 16px;">
+                            hmsInfo[dock.sn][0].level === EHmsLevel.WARN ? 'warn' : 'notice'"
+                            style="margin-left: 3px; width: 62px; height: 16px;">
                             <span class="word-loop">{{ hmsInfo[dock.sn][0].message_en }}</span>
                           </div>
                           <template #content>
-                            <a-collapse style="background: black; height: 300px; overflow-y: auto;" :bordered="false" expand-icon-position="right" :accordion="true">
+                            <a-collapse style="background: black; height: 300px; overflow-y: auto;" :bordered="false"
+                              expand-icon-position="right" :accordion="true">
                               <a-collapse-panel v-for="hms in hmsInfo[dock.sn]" :key="hms.hms_id" :showArrow="false"
                                 style=" margin: 0 auto 3px auto; border: 0; width: 140px; border-radius: 3px"
-                                :class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'"
-                                >
+                                :class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'">
                                 <template #header="{ isActive }">
                                   <div class="flex-row flex-align-center" style="width: 130px;">
                                     <div style="width: 110px;">
                                       <span class="word-loop">{{ hms.message_en }}</span>
                                     </div>
-                                    <div style="width: 20px; height: 15px; font-size: 10px; z-index: 2 " class="flex-row flex-align-center flex-justify-center"
-                                      :class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'"
-                                    >
+                                    <div style="width: 20px; height: 15px; font-size: 10px; z-index: 2 "
+                                      class="flex-row flex-align-center flex-justify-center"
+                                      :class="hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'">
                                       <DoubleRightOutlined :rotate="isActive ? 90 : 0" />
                                     </div>
                                   </div>
                                 </template>
-
                                 <a-tooltip :title="hms.create_time">
                                   <div style="color: white;" class="text-hidden">{{ hms.create_time }}</div>
                                 </a-tooltip>
@@ -125,10 +136,16 @@
                     </div>
                   </div>
                 </div>
-                <div style="float: right; background: #595959; height: 100%; width: 40px;" class="flex-row flex-justify-center flex-align-center">
-                  <div class="fz16" @click="switchVisible($event, dock, true, dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.mode_code !== EDockModeCode.Disconnected)">
-                    <a v-if="osdVisible.gateway_sn === dock.gateway.sn && osdVisible.visible"><EyeOutlined /></a>
-                    <a v-else><EyeInvisibleOutlined /></a>
+                <div style="float: right; background: #595959; height: 100%; width: 40px;"
+                  class="flex-row flex-justify-center flex-align-center">
+                  <div class="fz16"
+                    @click="switchVisible($event, dock, true, dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.mode_code !== EDockModeCode.Disconnected)">
+                    <a v-if="osdVisible.gateway_sn === dock.gateway.sn && osdVisible.visible">
+                      <EyeOutlined />
+                    </a>
+                    <a v-else>
+                      <EyeInvisibleOutlined />
+                    </a>
                   </div>
                 </div>
               </div>
@@ -137,47 +154,66 @@
         </a-collapse-panel>
       </a-collapse>
       <a-collapse :bordered="false" expandIconPosition="right" accordion style="background: #232323;">
-        <a-collapse-panel :key="EDeviceTypeName.Aircraft" header="Online Devices" style="border-bottom: 1px solid #4f4f4f;">
+        <a-collapse-panel :key="EDeviceTypeName.Aircraft" header="在线设备" style="border-bottom: 1px solid #4f4f4f;">
           <div v-if="onlineDevices.data.length === 0" style="height: 150px; color: white;">
             <a-empty :image="noData" :image-style="{ height: '60px' }" />
           </div>
           <div v-else class="fz12" style="color: white;">
-            <div v-for="device in onlineDevices.data" :key="device.sn" style="background: #3c3c3c; height: 90px; width: 250px; margin-bottom: 10px;">
+            <div v-for="device in onlineDevices.data" :key="device.sn"
+              style="background: #3c3c3c; height: 90px; width: 250px; margin-bottom: 10px;">
               <div class="battery-slide" v-if="deviceInfo[device.sn]">
                 <div style="background: #535759; width: 100%;"></div>
-                <div class="capacity-percent" :style="{ width: deviceInfo[device.sn].battery.capacity_percent + '%'}"></div>
-                <div class="return-home" :style="{ width: deviceInfo[device.sn].battery.return_home_power + '%'}"></div>
-                <div class="landing" :style="{ width: deviceInfo[device.sn].battery.landing_power + '%'}"></div>
+                <div class="capacity-percent" :style="{ width: deviceInfo[device.sn].battery.capacity_percent + '%' }">
+                </div>
+                <div class="return-home" :style="{ width: deviceInfo[device.sn].battery.return_home_power + '%' }">
+                </div>
+                <div class="landing" :style="{ width: deviceInfo[device.sn].battery.landing_power + '%' }"></div>
                 <div class="battery" :style="{ left: deviceInfo[device.sn].battery.capacity_percent + '%' }"></div>
               </div>
-              <div style="border-bottom: 1px solid #515151; border-radius: 2px; height: 50px; width: 100%;" class="flex-row flex-justify-between flex-align-center">
+              <div style="border-bottom: 1px solid #515151; border-radius: 2px; height: 50px; width: 100%;"
+                class="flex-row flex-justify-between flex-align-center">
                 <div style="float: left; padding: 5px 5px 8px 8px; width: 88%">
                   <div style="width: 100%; height: 100%;">
                     <a-tooltip>
-                      <template #title>{{ device.model ? `${device.model} - ${device.callsign}` : 'No Drone'}}</template>
-                      <span class="text-hidden" style="max-width: 200px; display: block; height: 20px;">{{ device.model ? `${device.model} - ${device.callsign}` : 'No Drone'}}</span>
+                      <template #title>
+                        {{ device.model ? `${device.model} - ${device.callsign}` : 'NoDrone' }}</template>
+                      <span class="text-hidden" style="max-width: 200px; display: block; height: 20px;">
+                        {{ device.model ? `${device.model} - ${device.callsign}` : 'No Drone' }}
+                      </span>
                     </a-tooltip>
                   </div>
                   <div class="mt5" style="background: #595959;">
-                    <span class="ml5 mr5"><RocketOutlined /></span>
-                    <span class="font-bold" :style="deviceInfo[device.sn] && deviceInfo[device.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' :  'color: red;'">
-                      {{ deviceInfo[device.sn] ? EModeCode[deviceInfo[device.sn].mode_code] : EModeCode[EModeCode.Disconnected] }}
+                    <span class="ml5 mr5">
+                      <RocketOutlined />
+                    </span>
+                    <span class="font-bold"
+                      :style="deviceInfo[device.sn] && deviceInfo[device.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'">
+                      {{ deviceInfo[device.sn] ? EModeCode[deviceInfo[device.sn].mode_code] :
+                        EModeCode[EModeCode.Disconnected] }}
                     </span>
                   </div>
                 </div>
-                <div style="float: right; background: #595959; height: 50px; width: 40px;" class="flex-row flex-justify-center flex-align-center">
-                  <div class="fz16" @click="switchVisible($event, device, false, deviceInfo[device.sn] && deviceInfo[device.sn].mode_code !== EModeCode.Disconnected)">
-                    <a v-if="osdVisible.sn === device.sn && osdVisible.visible"><EyeOutlined /></a>
-                    <a v-else><EyeInvisibleOutlined /></a>
+                <div style="float: right; background: #595959; height: 50px; width: 40px;"
+                  class="flex-row flex-justify-center flex-align-center">
+                  <div class="fz16"
+                    @click="switchVisible($event, device, false, deviceInfo[device.sn] && deviceInfo[device.sn].mode_code !== EModeCode.Disconnected)">
+                    <a v-if="osdVisible.sn === device.sn && osdVisible.visible">
+                      <EyeOutlined />
+                    </a>
+                    <a v-else>
+                      <EyeInvisibleOutlined />
+                    </a>
                   </div>
                 </div>
               </div>
               <div class="flex-row flex-justify-center flex-align-center" style="height: 40px;">
-                <div class="flex-row" style="height: 20px; background: #595959; width: 94%;" >
-                  <span class="mr5"><a-image style="margin-left: 2px; margin-top: -2px; height: 20px; width: 20px;" :src="rc" /></span>
+                <div class="flex-row" style="height: 20px; background: #595959; width: 94%;">
+                  <span class="mr5"><a-image style="margin-left: 2px; margin-top: -2px; height: 20px; width: 20px;"
+                      :src="rc" /></span>
                   <a-tooltip>
                     <template #title>{{ device.gateway.model }} - {{ device.gateway.callsign }} </template>
-                    <div class="text-hidden" style="max-width: 200px;">{{ device.gateway.model }} - {{ device.gateway.callsign }}</div>
+                    <div class="text-hidden" style="max-width: 200px;">
+                      {{ device.gateway.model }} - {{ device.gateway.callsign }}</div>
                   </a-tooltip>
                 </div>
               </div>
@@ -185,20 +221,34 @@
           </div>
         </a-collapse-panel>
       </a-collapse>
+      <a-collapse :bordered="false" expandIconPosition="right" accordion style="background: #232323;">
+        <a-collapse-panel :key="EDeviceTypeName.Aircraft" header="地图标注管理" style="border-bottom: 1px solid #4f4f4f;">
+          <Layer />
+        </a-collapse-panel>
+      </a-collapse>
+    </div>
+    <div style="width: 100%;display: flex;justify-content: center; align-items: center;">
+      <a-tooltip title="返回" placement="right">
+        <a-button type="primary" @click="onClickGoHome">
+          <ImportOutlined style="font-size: 22px; color: white" />
+        </a-button>
+      </a-tooltip>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
 import { computed, onMounted, reactive, ref, watch, WritableComputedRef } from 'vue'
+import Layer from '/@/pages/page-web/projects/layer.vue'
 import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
 import noData from '/@/assets/icons/no-data.png'
 import rc from '/@/assets/icons/rc.png'
 import { OnlineDevice, EModeCode, OSDVisible, EDockModeCode, DeviceOsd } from '/@/types/device'
 import { useMyStore } from '/@/store'
 import { getDeviceTopo, getUnreadDeviceHms, updateDeviceHms } from '/@/api/manage'
-import { RocketOutlined, EyeInvisibleOutlined, EyeOutlined, RobotOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'
-import { EHmsLevel } from '/@/types/enums'
+import { RocketOutlined, EyeInvisibleOutlined, EyeOutlined, RobotOutlined, DoubleRightOutlined, ImportOutlined } from '@ant-design/icons-vue'
+import { EHmsLevel, ERouterName } from '/@/types/enums'
+import { getRoot } from '/@/root'
 
 const store = useMyStore()
 const username = ref(localStorage.getItem(ELocalStorageKey.Username))
@@ -245,7 +295,7 @@ onMounted(() => {
   scorllHeight.value = parent?.clientHeight - parent?.firstElementChild?.clientHeight
 })
 
-function getOnlineTopo () {
+function getOnlineTopo() {
   getDeviceTopo(workspaceId.value).then((res) => {
     if (res.code !== 0) {
       return
@@ -289,7 +339,7 @@ function getOnlineTopo () {
   })
 }
 
-function switchVisible (e: any, device: OnlineDevice, isDock: boolean, isClick: boolean) {
+function switchVisible(e: any, device: OnlineDevice, isDock: boolean, isClick: boolean) {
   if (!isClick) {
     e.target.style.cursor = 'not-allowed'
     return
@@ -309,7 +359,7 @@ function switchVisible (e: any, device: OnlineDevice, isDock: boolean, isClick:
   store.commit('SET_OSD_VISIBLE_INFO', osdVisible)
 }
 
-function getUnreadHms (sn: string) {
+function getUnreadHms(sn: string) {
   getUnreadDeviceHms(workspaceId.value, sn).then(res => {
     if (res.data.length !== 0) {
       hmsInfo.value[sn] = res.data
@@ -318,7 +368,7 @@ function getUnreadHms (sn: string) {
   console.info(hmsInfo.value)
 }
 
-function getOnlineDeviceHms () {
+function getOnlineDeviceHms() {
   const snList = Object.keys(dockInfo.value)
   if (snList.length === 0) {
     return
@@ -335,7 +385,7 @@ function getOnlineDeviceHms () {
   })
 }
 
-function readHms (visiable: boolean, sn: string) {
+function readHms(visiable: boolean, sn: string) {
   if (!visiable) {
     updateDeviceHms(workspaceId.value, sn).then(res => {
       if (res.code === 0) {
@@ -345,33 +395,41 @@ function readHms (visiable: boolean, sn: string) {
   }
 }
 
-function openLivestreamOthers () {
+const onClickGoHome = () => {
+  const root = getRoot()
+  root.$router.push('/' + ERouterName.DEVICES)
+}
+
+function openLivestreamOthers() {
   store.commit('SET_LIVESTREAM_OTHERS_VISIBLE', true)
 }
 
-function openLivestreamAgora () {
+function openLivestreamAgora() {
   store.commit('SET_LIVESTREAM_AGORA_VISIBLE', true)
 }
-
 </script>
 
 <style lang="scss">
-.project-tsa-wrapper > :first-child {
+.project-tsa-wrapper> :first-child {
   height: 50px;
   line-height: 50px;
   align-items: center;
   border-bottom: 1px solid #4f4f4f;
 }
+
 .project-tsa-wrapper {
   height: 100%;
+
   .scrollbar {
     overflow: auto;
   }
+
   ::-webkit-scrollbar {
     display: none;
   }
 }
-.ant-collapse > .ant-collapse-item > .ant-collapse-header {
+
+.ant-collapse>.ant-collapse-item>.ant-collapse-header {
   color: white;
   border: 0;
   padding-left: 14px;
@@ -383,21 +441,26 @@ function openLivestreamAgora () {
   white-space: nowrap;
   -o-text-overflow: ellipsis;
 }
+
 .font-bold {
   font-weight: 700;
 }
 
 .battery-slide {
   width: 100%;
+
   .capacity-percent {
     background: #00ee8b;
   }
+
   .return-home {
     background: #ff9f0a;
   }
+
   .landing {
     background: #f5222d;
   }
+
   .battery {
     background: white;
     border-radius: 1px;
@@ -406,13 +469,15 @@ function openLivestreamAgora () {
     margin-top: -3px;
   }
 }
-.battery-slide > div {
+
+.battery-slide>div {
   position: relative;
   margin-top: -2px;
   min-height: 2px;
   border-radius: 2px;
   white-space: nowrap;
 }
+
 .disable {
   cursor: not-allowed;
 }
@@ -421,54 +486,64 @@ function openLivestreamAgora () {
   background: $success;
   animation: blink 500ms infinite;
 }
+
 .caution-blink {
   background: orange;
   animation: blink 500ms infinite;
 }
+
 .warn-blink {
   background: red;
   animation: blink 500ms infinite;
 }
+
 .notice {
   background: $success;
   overflow: hidden;
   cursor: pointer;
 }
+
 .caution {
   background: orange;
   cursor: pointer;
   overflow: hidden;
 }
+
 .warn {
   background: red;
   cursor: pointer;
   overflow: hidden;
 }
+
 .word-loop {
   white-space: nowrap;
   display: inline-block;
   animation: 10s loop linear infinite normal;
 }
+
 @keyframes blink {
   from {
     opacity: 1;
   }
+
   50% {
     opacity: 0.35;
   }
+
   to {
     opacity: 1;
   }
 }
+
 @keyframes loop {
   0% {
     transform: translateX(20px);
     -webkit-transform: translateX(20px);
   }
+
   100% {
     transform: translateX(-100%);
     -webkit-transform: translateX(-100%);
   }
 }
-
 </style>

+ 1 - 2
Web/src/pages/page-web/projects/wayline.vue

@@ -81,9 +81,8 @@
 </template>
 
 <script lang="ts" setup>
-import { reactive } from '@vue/reactivity'
 import { message } from 'ant-design-vue'
-import { onMounted, onUpdated, ref } from 'vue'
+import { onMounted, reactive, ref } from 'vue'
 import { deleteWaylineFile, downloadWaylineFile, getWaylineFiles, importKmzFile } from '/@/api/wayline'
 import { ELocalStorageKey, ERouterName } from '/@/types'
 import { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined, SelectOutlined } from '@ant-design/icons-vue'

+ 28 - 15
Web/src/pages/page-web/projects/workspace.vue

@@ -1,9 +1,9 @@
 <template>
   <div class="project-app-wrapper">
     <div class="left">
-      <Sidebar />
+      <!-- <Sidebar /> -->
       <div class="main-content uranus-scrollbar dark">
-        <router-view />
+        <Tsa />
       </div>
     </div>
     <div class="right">
@@ -21,6 +21,7 @@
 </template>
 <script lang="ts" setup>
 import Sidebar from '/@/components/common/sidebar.vue'
+import Tsa from '/@/pages/page-web/projects/tsa.vue'
 import MediaPanel from '/@/components/MediaPanel.vue'
 import TaskPanel from '/@/components/task/TaskPanel.vue'
 import GMap from '/@/components/GMap.vue'
@@ -92,23 +93,23 @@ const messageHandler = async (payload: any) => {
     case EBizCode.ChargeClose:
     case EBizCode.DeviceFormat:
     case EBizCode.DroneFormat:
-    {
-      store.commit('SET_DEVICES_CMD_EXECUTE_INFO', {
-        biz_code: payload.biz_code,
-        timestamp: payload.timestamp,
-        ...payload.data,
-      })
-      break
-    }
+      {
+        store.commit('SET_DEVICES_CMD_EXECUTE_INFO', {
+          biz_code: payload.biz_code,
+          timestamp: payload.timestamp,
+          ...payload.data,
+        })
+        break
+      }
     case EBizCode.ControlSourceChange:
     case EBizCode.FlyToPointProgress:
     case EBizCode.TakeoffToPointProgress:
     case EBizCode.JoystickInvalidNotify:
     case EBizCode.DrcStatusNotify:
-    {
-      EventBus.emit('droneControlWs', payload)
-      break
-    }
+      {
+        EventBus.emit('droneControlWs', payload)
+        break
+      }
     case EBizCode.FlightAreasSyncProgress: {
       EventBus.emit('flightAreasSyncProgressWs', payload.data)
       break
@@ -133,6 +134,18 @@ useConnectWebSocket(messageHandler)
 <style lang="scss" scoped>
 @import '/@/styles/index.scss';
 
+::-webkit-scrollbar {
+  width: 8px;
+  height: 8px;
+  background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+  border-radius: 4px;
+  border: none;
+  background: rgb(89, 89, 89);
+}
+
 .project-app-wrapper {
   display: flex;
   transition: width 0.2s ease;
@@ -156,7 +169,7 @@ useConnectWebSocket(messageHandler)
     flex-grow: 1;
     position: relative;
 
-    .map-wrapper{
+    .map-wrapper {
       width: 100%;
       height: 100%;
     }

+ 14 - 41
Web/src/router/index.ts

@@ -36,48 +36,21 @@ const routes: Array<RouteRecordRaw> = [
         path: '/' + ERouterName.FIRMWARES,
         name: ERouterName.FIRMWARES,
         component: () => import('../pages/page-web/projects/Firmwares.vue')
-      }
-    ]
-  },
-  {
-    path: '/' + ERouterName.WORKSPACE,
-    name: ERouterName.WORKSPACE,
-    component: () => import('/@/pages/page-web/projects/workspace.vue'),
-    redirect: '/' + ERouterName.TSA,
-    children: [
-      {
-        path: '/' + ERouterName.TSA,
-        component: () => import('/@/pages/page-web/projects/tsa.vue')
-      },
-      {
-        path: '/' + ERouterName.LIVESTREAM,
-        name: ERouterName.LIVESTREAM,
-        component: () => import('/@/pages/page-web/projects/livestream.vue'),
-        children: [
-          {
-            path: ERouterName.LIVING,
-            name: ERouterName.LIVING,
-            components: {
-              LiveAgora,
-              LiveOthers
-            }
-          }
-        ]
-      },
-      {
-        path: '/' + ERouterName.LAYER,
-        name: ERouterName.LAYER,
-        component: () => import('/@/pages/page-web/projects/layer.vue')
       },
       {
         path: '/' + ERouterName.MEDIA,
         name: ERouterName.MEDIA,
-        component: () => import('/@/pages/page-web/projects/media.vue')
+        component: () => import('/@/pages/page-web/projects/media/index.vue')
       },
       {
-        path: '/' + ERouterName.WAYLINE,
-        name: ERouterName.WAYLINE,
-        component: () => import('/@/pages/page-web/projects/wayline.vue')
+        path: '/' + ERouterName.REPLAY,
+        name: ERouterName.REPLAY,
+        component: () => import('/@/pages/page-web/projects/replay/index.vue')
+      },
+      {
+        path: '/' + ERouterName.TRAJECTORY,
+        name: ERouterName.TRAJECTORY,
+        component: () => import('/@/pages/page-web/projects/trajectory/index.vue')
       },
       {
         path: '/' + ERouterName.TASK,
@@ -102,13 +75,13 @@ const routes: Array<RouteRecordRaw> = [
 
         ]
       },
-      {
-        path: '/' + ERouterName.FLIGHT_AREA,
-        name: ERouterName.FLIGHT_AREA,
-        component: () => import('/@/pages/page-web/projects/flight-area.vue')
-      },
     ]
   },
+  {
+    path: '/' + ERouterName.WORKSPACE,
+    name: ERouterName.WORKSPACE,
+    component: () => import('/@/pages/page-web/projects/workspace.vue'),
+  },
   {
     path: '/' + ERouterName.PILOT,
     name: ERouterName.PILOT,

+ 29 - 29
Web/src/store/index.ts

@@ -30,7 +30,7 @@ const initStateFunc = () => ({
     }
   ],
   layerBaseInfo: {} as {
-    [key:string]:string
+    [key: string]: string
   },
   drawVisible: false,
   livestreamOthersVisible: false,
@@ -100,25 +100,25 @@ export type RootStateType = ReturnType<typeof initStateFunc>
 const getters: GetterTree<RootStateType, RootStateType> = {
 }
 const mutations: MutationTree<RootStateType> = {
-  SET_LAYER_INFO (state, info) {
+  SET_LAYER_INFO(state, info) {
     state.Layers = info
   },
-  SET_DEVICE_INFO (state, info) {
+  SET_DEVICE_INFO(state, info) {
     state.deviceState.deviceInfo[info.sn] = info.host
     state.deviceState.currentSn = info.sn
     state.deviceState.currentType = EDeviceTypeName.Aircraft
   },
-  SET_GATEWAY_INFO (state, info) {
+  SET_GATEWAY_INFO(state, info) {
     state.deviceState.gatewayInfo[info.sn] = info.host
     state.deviceState.currentSn = info.sn
     state.deviceState.currentType = EDeviceTypeName.Gateway
   },
-  SET_DOCK_INFO (state, info) {
+  SET_DOCK_INFO(state, info) {
     if (Object.keys(info.host).length === 0) {
       return
     }
     if (!state.deviceState.dockInfo[info.sn]) {
-      state.deviceState.dockInfo[info.sn] = { } as DockOsd
+      state.deviceState.dockInfo[info.sn] = {} as DockOsd
     }
     state.deviceState.currentSn = info.sn
     state.deviceState.currentType = EDeviceTypeName.Dock
@@ -135,28 +135,28 @@ const mutations: MutationTree<RootStateType> = {
       dock.work_osd = info.host
     }
   },
-  SET_DRAW_VISIBLE_INFO (state, bool) {
+  SET_DRAW_VISIBLE_INFO(state, bool) {
     state.drawVisible = bool
   },
-  SET_LIVESTREAM_OTHERS_VISIBLE (state, bool) {
+  SET_LIVESTREAM_OTHERS_VISIBLE(state, bool) {
     state.livestreamOthersVisible = bool
   },
-  SET_LIVESTREAM_AGORA_VISIBLE (state, bool) {
+  SET_LIVESTREAM_AGORA_VISIBLE(state, bool) {
     state.livestreamAgoraVisible = bool
   },
-  SET_MAP_ELEMENT_CREATE (state, info) {
+  SET_MAP_ELEMENT_CREATE(state, info) {
     state.wsEvent.mapElementCreat = info
   },
-  SET_MAP_ELEMENT_UPDATE (state, info) {
+  SET_MAP_ELEMENT_UPDATE(state, info) {
     state.wsEvent.mapElementUpdate = info
   },
-  SET_MAP_ELEMENT_DELETE (state, info) {
+  SET_MAP_ELEMENT_DELETE(state, info) {
     state.wsEvent.mapElementDelete = info
   },
-  SET_DEVICE_ONLINE (state, info) {
+  SET_DEVICE_ONLINE(state, info) {
     state.deviceStatusEvent.deviceOnline = info
   },
-  SET_DEVICE_OFFLINE (state, info) {
+  SET_DEVICE_OFFLINE(state, info) {
     state.deviceStatusEvent.deviceOffline = info
     delete state.deviceState.gatewayInfo[info.sn]
     delete state.deviceState.deviceInfo[info.sn]
@@ -165,20 +165,20 @@ const mutations: MutationTree<RootStateType> = {
     // delete state.markerInfo.coverMap[info.sn]
     // delete state.markerInfo.pathMap[info.sn]
   },
-  SET_OSD_VISIBLE_INFO (state, info) {
+  SET_OSD_VISIBLE_INFO(state, info) {
     state.osdVisible = info
   },
-  SET_SELECT_WAYLINE_INFO (state, info) {
+  SET_SELECT_WAYLINE_INFO(state, info) {
     state.waylineInfo = info
   },
-  SET_SELECT_DOCK_INFO (state, info) {
+  SET_SELECT_DOCK_INFO(state, info) {
     state.dockInfo = info
   },
-  SET_DEVICE_HMS_INFO (state, info) {
+  SET_DEVICE_HMS_INFO(state, info) {
     const hmsList: Array<DeviceHms> = state.hmsInfo[info.sn]
     state.hmsInfo[info.sn] = info.host.concat(hmsList ?? [])
   },
-  SET_DEVICES_CMD_EXECUTE_INFO (state, info) { // 保存设备指令ws消息推送
+  SET_DEVICES_CMD_EXECUTE_INFO(state, info) { // 保存设备指令ws消息推送
     if (!info.sn) {
       return
     }
@@ -197,16 +197,16 @@ const mutations: MutationTree<RootStateType> = {
       state.devicesCmdExecuteInfo[info.sn] = [info]
     }
   },
-  SET_MQTT_STATE (state, mqttState) {
+  SET_MQTT_STATE(state, mqttState) {
     state.mqttState = mqttState
   },
-  SET_CLIENT_ID (state, clientId) {
+  SET_CLIENT_ID(state, clientId) {
     state.clientId = clientId
   },
 }
 
 const actions: ActionTree<RootStateType, RootStateType> = {
-  async getAllElement ({ commit }) {
+  async getAllElement({ commit }) {
     const result = await getLayers({
       groupId: '',
       isDistributed: true
@@ -214,7 +214,7 @@ const actions: ActionTree<RootStateType, RootStateType> = {
     commit('SET_LAYER_INFO', result.data?.list)
     console.log(result)
   },
-  updateElement ({ state }, content: {type: 'is_check' | 'is_select', id: string, bool:boolean}) {
+  updateElement({ state }, content: { type: 'is_check' | 'is_select', id: string, bool: boolean }) {
     const key = content.id.replaceAll('resource__', '')
     const type = content.type
     const layers = state.Layers
@@ -223,10 +223,10 @@ const actions: ActionTree<RootStateType, RootStateType> = {
       layer[type] = content.bool
     }
   },
-  setLayerInfo ({ state }, layers) {
+  setLayerInfo({ state }, layers) {
     // const layers = state.Layers
-    const obj:{
-      [key:string]:string
+    const obj: {
+      [key: string]: string
     } = {}
     layers.forEach(layer => {
       if (layer.type === LayerType.Default) {
@@ -240,7 +240,7 @@ const actions: ActionTree<RootStateType, RootStateType> = {
     state.layerBaseInfo = obj
     console.log('state.layerBaseInfo', state.layerBaseInfo)
   },
-  getLayerInfo ({ state }, id:string) {
+  getLayerInfo({ state }, id: string) {
     return state.layerBaseInfo[id]
   }
 }
@@ -262,6 +262,6 @@ type AllStateStoreTypes = RootStateType & {
   // moduleName: moduleType
 }
 
-export function useMyStore<T = AllStateStoreTypes> () {
+export function useMyStore<T = AllStateStoreTypes>() {
   return useStore<T>(storeKey)
-}
+}

+ 11 - 15
Web/src/styles/common.scss

@@ -1,13 +1,20 @@
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+}
 
-html, body, #app, #my-app {
+html,
+body {
+  height: 100%;
   height: 100%;
-  overflow: hidden;
+  padding: 0;
+  margin: 0;
 }
 
 body {
-  background-color: #f7f9fa;
+  background-color: #F7F9FA;
   -webkit-font-smoothing: antialiased;
-  // Prevent font enlargement in horizontal screen
   text-size-adjust: 100%;
 
   font-family: sans-serif, Roboto, sans-serif-medium, Arial;
@@ -17,15 +24,4 @@ body {
 
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
-  ::-webkit-scrollbar {
-    width: 8px;
-    height: 8px;
-    background: transparent;
-  }
-  
-  ::-webkit-scrollbar-thumb {
-    border-radius: 4px;
-    border: none;
-    background: rgb(89, 89, 89);
-  }
 }

+ 1 - 1
Web/src/types/enums.ts

@@ -5,8 +5,8 @@ export enum ERouterName {
     MEDIA = 'media',
     REPLAY = 'replay',
     TRAJECTORY = 'trajectory',
-
     MEMBERS = 'members',
+
     ELEMENT = 'element',
     PROJECT = 'project',
     HOME = 'home',

+ 4 - 4
Web/src/types/flight-area.ts

@@ -70,11 +70,11 @@ export interface FlightAreaSyncProgress {
 
 export const FlightAreaTypeTitleMap = {
   [EFlightAreaType.NFZ]: {
-    [EGeometryType.CIRCLE]: 'Circular GEO Zone',
-    [EGeometryType.POLYGON]: 'Polygonal GEO Zone',
+    [EGeometryType.CIRCLE]: '圆形地带',
+    [EGeometryType.POLYGON]: '多边形地带',
   },
   [EFlightAreaType.DFENCE]: {
-    [EGeometryType.CIRCLE]: 'Circular Task Area',
-    [EGeometryType.POLYGON]: 'Polygonal Task Area',
+    [EGeometryType.CIRCLE]: '圆形任务区',
+    [EGeometryType.POLYGON]: '多边形任务区',
   },
 }

+ 4 - 9
Web/src/utils/bytes.ts

@@ -7,7 +7,7 @@ import { DEFAULT_PLACEHOLDER, SIZES as byteSizes, BYTE_SIZES } from './constants
  * @param holder 0字节占位符,默认 --
  * @returns
  */
-export function bytesToSize (bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1, unit = false): string {
+export function bytesToSize(bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1, unit = false): string {
   if (isNaN(bytes) || bytes === 0) {
     return holder
   }
@@ -24,7 +24,7 @@ export function bytesToSize (bytes: number, holder = DEFAULT_PLACEHOLDER, fix =
 }
 
 //  获取转化后数据及单位
-export function getBytesObject (bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1): {
+export function getBytesObject(bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1): {
   value: string,
   size: string
   index: number
@@ -60,7 +60,7 @@ export function getBytesObject (bytes: number, holder = DEFAULT_PLACEHOLDER, fix
  * @param fix
  * @returns
  */
-export function bytesToSizeWithMinUnit (bytes: number, minUnit = 'B', fix = 1): string {
+export function bytesToSizeWithMinUnit(bytes: number, minUnit = 'B', fix = 1): string {
   const holder = `0${minUnit}`
   const sizes = byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
   const k = 1024
@@ -78,9 +78,4 @@ export function bytesToSizeWithMinUnit (bytes: number, minUnit = 'B', fix = 1):
   }
   // 其他
   return value + size
-}
-// console.log('size', bytesToSizeWithMinUnit(0))
-// console.log('size', bytesToSizeWithMinUnit(1023))
-// console.log('size', bytesToSizeWithMinUnit(1024))
-// console.log('size', bytesToSizeWithMinUnit(1000 * 1024, 'MB', 2))
-// console.log('size', bytesToSizeWithMinUnit(1024 * 1024, 'MB', 2))
+}

+ 2 - 2
Web/src/utils/error-code/index.ts

@@ -9,7 +9,7 @@ export interface ErrorCode {
  * @param errorMsg
  * @returns
  */
-export function getErrorMessage (code: number, errorMsg?: string): string {
+export function getErrorMessage(code: number, errorMsg?: string): string {
   const errorInfo = ERROR_CODE.find((item: ErrorCode) => item.code === code)
   return errorInfo ? errorInfo.msg : errorMsg || 'Server error'
 }
@@ -307,4 +307,4 @@ export const ERROR_CODE = [
     code: 514150,
     msg: 'DJI Dock is automatically restarting',
   },
-]
+]

+ 4 - 4
Web/src/utils/time.ts

@@ -2,14 +2,14 @@ import {
   DATE_FORMAT,
   DEFAULT_PLACEHOLDER
 } from '/@/utils/constants'
-import moment, { Moment } from 'moment'
+import moment from 'moment'
 
 // 时间字符串 或者 Unix 时间戳(毫秒数)
-export function formatDateTime (time: string | number, format = DATE_FORMAT) {
+export function formatDateTime(time: string | number, format = DATE_FORMAT) {
   return time ? moment(time).format(format) : DEFAULT_PLACEHOLDER
 }
 
 // Unix 时间戳 (秒)
-export function formatUnixTime (time: number, format = DATE_FORMAT): string {
+export function formatUnixTime(time: number, format = DATE_FORMAT): string {
   return time ? moment.unix(time).format(format) : DEFAULT_PLACEHOLDER
-}
+}

+ 3 - 2
Web/vite.config.ts

@@ -1,5 +1,6 @@
 import vue from '@vitejs/plugin-vue'
 import path from 'path'
+import { CURRENT_CONFIG } from './src/api/http/config';
 import { ConfigEnv, defineConfig, UserConfigExport } from 'vite'
 import { viteVConsole } from 'vite-plugin-vconsole'
 
@@ -37,14 +38,14 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => defineConfig(
         // 开启跨域
         changeOrigin: true,
         // 转发地址
-        target: 'http://192.168.3.42:6789',
+        target: CURRENT_CONFIG.apiURL,
         // 路径重写
         rewrite: (path) => path.replace(/^\/api/, ''),
       }
     }
   },
   build: {
-    target: ['es2015'], // 最低支持 es2015
+    // target: ['es2015'], // 最低支持 es2015
     sourcemap: false,// 构建后不生成源代码
     write: true,// 构建的文件写入磁盘
     chunkSizeWarningLimit: 10240,// 触发警告的chunk大小10M