From 5667f2b4fc7555ba678d5aef13b096cd38da64db Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Mon, 12 Jan 2026 15:45:34 +0800
Subject: [PATCH] feat:数据驾驶舱相关处理
---
applications/drone-command/src/styles/common.scss | 2
applications/drone-command/src/page/index/top/index.vue | 35
applications/drone-command/src/styles/element-ui.scss | 261 -------
applications/drone-command/src/styles/common/cockpit.scss | 660 +++++++++++++++++++
applications/drone-command/src/styles/top.scss | 7
applications/drone-command/src/assets/images/dataCockpit/map/equipment.png | 0
applications/drone-command/src/views/dataCockpit/index.vue | 10
applications/drone-command/src/assets/images/defaultava.png | 0
applications/drone-command/src/assets/images/dataCockpit/layerControl.png | 0
applications/drone-command/src/views/dataCockpit/components/MapContainer.vue | 400 +++++++++++
applications/drone-command/src/views/dataCockpit/components/RightContainer.vue | 35
applications/drone-command/src/views/api.txt | 142 ++-
applications/drone-command/src/utils/mapUtils.js | 7
applications/drone-command/src/assets/images/mh.png | 0
applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue | 395 ++++-------
applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue | 20
applications/drone-command/src/api/dataCockpit/index.js | 8
17 files changed, 1,372 insertions(+), 610 deletions(-)
diff --git a/applications/drone-command/src/api/dataCockpit/index.js b/applications/drone-command/src/api/dataCockpit/index.js
index f8b68b6..553d692 100644
--- a/applications/drone-command/src/api/dataCockpit/index.js
+++ b/applications/drone-command/src/api/dataCockpit/index.js
@@ -44,3 +44,11 @@
data
})
}
+
+// 设备统计
+export const deviceStatisticsApi = () => {
+ return request({
+ url: '/drone-fw/cockpit/dataCockpit/deviceStatistics',
+ method: 'get'
+ })
+}
diff --git a/applications/drone-command/src/assets/images/dataCockpit/layerControl.png b/applications/drone-command/src/assets/images/dataCockpit/layerControl.png
new file mode 100644
index 0000000..b6ea5b9
--- /dev/null
+++ b/applications/drone-command/src/assets/images/dataCockpit/layerControl.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/dataCockpit/map/equipment.png b/applications/drone-command/src/assets/images/dataCockpit/map/equipment.png
new file mode 100644
index 0000000..f07291b
--- /dev/null
+++ b/applications/drone-command/src/assets/images/dataCockpit/map/equipment.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/defaultava.png b/applications/drone-command/src/assets/images/defaultava.png
index cc3462f..0747af3 100644
--- a/applications/drone-command/src/assets/images/defaultava.png
+++ b/applications/drone-command/src/assets/images/defaultava.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/mh.png b/applications/drone-command/src/assets/images/mh.png
index cc0d23b..fe0b5d9 100644
--- a/applications/drone-command/src/assets/images/mh.png
+++ b/applications/drone-command/src/assets/images/mh.png
Binary files differ
diff --git a/applications/drone-command/src/page/index/top/index.vue b/applications/drone-command/src/page/index/top/index.vue
index 8554741..84906ca 100644
--- a/applications/drone-command/src/page/index/top/index.vue
+++ b/applications/drone-command/src/page/index/top/index.vue
@@ -6,23 +6,16 @@
</div>
<div class="top-bar__right">
- <div class="icon-box">
- <top-lock v-if="setting.lock" />
- <top-qna />
- <top-full v-if="setting.fullscreen" title="全屏" />
- <img class="gateway" @click="jumpMH" src="@/assets/images/mh.svg" alt="进入门户" title="进入门户" width="20"
- height="20">
- </div>
<div class="top-user">
- <img class="top-bar__img" :src="userInfo.avatar" alt="" />
+ <div class="icon-box">
+ <img class="gateway" @click="jumpMH" src="@/assets/images/mh.png" alt="进入门户" title="进入门户">
+ </div>
<el-dropdown>
<span class="el-dropdown-link">
- {{ userInfo.real_name }}
- <el-icon class="el-icon--right">
- <arrow-down />
- </el-icon>
+ <img class="top-bar__img" :src="userInfo.avatar" alt="" />
</span>
+
<template #dropdown>
<el-dropdown-menu>
<!-- <el-dropdown-item>
@@ -154,20 +147,25 @@
}
.top-bar__right {
+ margin-right: 28px;
+ padding-top: 16px;
flex: 0 0 auto;
display: flex !important;
- align-items: center;
+ align-items: flex-start;
height: 100%;
pointer-events: auto;
+ box-sizing: border-box;
.icon-box {
+ margin-top: 6px;
+ margin-right: 30px;
display: flex;
align-items: center;
gap: 0 20px;
.gateway {
- width: 25px;
- height: 25px;
+ width: 21px;
+ height: 18px;
}
>* {
@@ -187,10 +185,9 @@
}
.top-bar__img {
- width: 32px;
- height: 32px;
- border-radius: 50%;
- margin-right: 10px;
+ margin-top: 5px;
+ width: 16px;
+ height: 21px;
}
.el-dropdown-link {
diff --git a/applications/drone-command/src/styles/common.scss b/applications/drone-command/src/styles/common.scss
index 7172f8e..d8a1616 100644
--- a/applications/drone-command/src/styles/common.scss
+++ b/applications/drone-command/src/styles/common.scss
@@ -8,6 +8,8 @@
// 地图编辑样式
@import './mapEdit.scss';
+@import './common/cockpit.scss';
+
a {
text-decoration: none;
color: #333;
diff --git a/applications/drone-command/src/styles/common/cockpit.scss b/applications/drone-command/src/styles/common/cockpit.scss
new file mode 100644
index 0000000..9bf2d0c
--- /dev/null
+++ b/applications/drone-command/src/styles/common/cockpit.scss
@@ -0,0 +1,660 @@
+// 数据驾驶舱专属
+.ztzf-data-cockpit-search-input {
+ .el-input__wrapper {
+ padding: 0 8px !important;
+ background: #161B2C;
+ box-shadow: none !important;
+ border: none;
+
+ .el-input__inner {
+ color: #ffffff;
+
+ &::placeholder {
+ color: #86909C;
+ }
+ }
+ }
+
+ &.is-disabled {
+ .el-input__wrapper {
+ background-color: #161B2C;
+ }
+ }
+}
+
+.ztzf-data-cockpit-date-picker {
+ box-shadow: none !important;
+ background: #161B2C;
+ border: none;
+ border-radius: 5px;
+ box-sizing: border-box !important;
+
+ .el-input__wrapper {
+ box-shadow: none !important;
+ background-color: transparent !important;
+
+ &.is-focus {
+ box-shadow: none !important;
+ }
+ }
+
+ .el-range-input {
+ color: #fff;
+
+ &::placeholder {
+ color: #86909C;
+ }
+ }
+
+ .el-range-separator {
+ color: #ffffff;
+ }
+}
+
+.ztzf-data-cockpit-date-picker-popper {
+ background: #161B2C !important;
+ border: 1px solid !important;
+ border-radius: 0px 0px 8px 8px;
+ border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
+
+ .el-date-picker__prev-btn,
+ .el-date-picker__next-btn {
+ .el-icon {
+ color: #fff !important;
+ }
+ }
+
+ .el-date-picker__header-label {
+ color: #fff !important;
+ }
+
+ .el-picker-panel {
+ .el-picker-panel__body-wrapper {
+ .el-picker-panel__body {
+ background: #161B2C !important;
+ color: #fff !important;
+
+ .el-date-picker__time-header {
+ border: none;
+
+ .el-date-picker__editor-wrap {
+ .el-time-panel {
+ background: #161B2C !important;
+ border-radius: 0px 0px 8px 8px;
+ border: 1px solid !important;
+ border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
+
+ // 将 time-picker-popper 的样式移到这里
+ .el-time-panel__header {
+ background: #161B2C !important;
+ color: #ffffff !important;
+ border-bottom: 1px solid rgba(71, 157, 255, 0.3) !important;
+ }
+
+ .el-time-panel__content {
+ .el-time-spinner__item {
+ color: #e6e6e6 !important;
+
+ &:hover {
+ background: rgba(71, 157, 255, 0.3) !important;
+ }
+
+ &.active {
+ color: #479dff !important;
+ font-weight: bold;
+ }
+ }
+ }
+
+ .el-time-panel__footer {
+ background: #012350 !important;
+ border-top: 1px solid rgba(71, 157, 255, 0.3) !important;
+
+ .el-button {
+ color: #fff !important;
+
+ &:hover {
+ color: #479dff !important;
+ }
+ }
+
+ .el-time-panel__btn.cancel {
+ color: #fff !important;
+
+ &:hover {
+ color: #fff !important;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .el-picker-panel__content {
+ .el-date-range-picker__header {
+ .el-picker-panel__icon-btn {
+ color: #fff !important;
+
+ &:hover {
+ color: var(--el-color-primary) !important;
+ }
+
+ .el-icon {
+ &::before {
+ color: inherit !important;
+ }
+ }
+ }
+
+ &>div {
+ span {
+ color: #fff !important;
+ }
+ }
+ }
+
+ .el-date-table {
+ th {
+ color: #fff !important;
+ }
+
+ td.disabled div {
+ background-color: #00193b !important;
+ color: #505050 !important;
+ cursor: not-allowed;
+ }
+
+ // td:hover,
+ // td.current:not(.disabled) {
+ // background: rgba(60, 121, 202) !important;
+ // color: white !important;
+ // }
+
+ td.start-date span,
+ td.start-date span,
+ td.end-date span {
+ background-color: #012350 !important;
+ }
+
+ td.in-range div,
+ td.in-range div:hover,
+ &.is-week-mode .el-date-table__row.current div,
+ &.is-week-mode .el-date-table__row:hover div {
+ background-color: rgba(60, 121, 202) !important;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .el-popper__arrow {
+ &::before {
+ background: #161B2C !important;
+ border: 1px solid #161B2C !important;
+ box-sizing: border-box;
+ }
+ }
+
+ .el-picker-panel__sidebar {
+ background: #012350;
+ color: #fff;
+
+ .el-picker-panel__shortcut {
+ color: #ffffff;
+ }
+ }
+
+ .el-picker-panel__body {
+ .el-input.is-disabled .el-input__wrapper {
+ background: #012350;
+ box-shadow: 0 0 0 0.1rem#409eff;
+ color: #fff;
+
+ .el-input__inner {
+ color: #fff !important;
+ }
+ }
+
+ .el-button.is-disabled,
+ .el-button.is-disabled:hover {
+ background: #012350;
+ }
+
+ .el-input__wrapper {
+ background: #012350;
+ box-shadow: 0 0 0 0.1rem#409eff;
+ color: #fff;
+
+ .el-input__inner {
+ color: #fff !important;
+ }
+ }
+ }
+
+ .el-picker-panel__footer {
+ background: #012350;
+ color: #fff;
+ border-top: none;
+
+ .el-button--small {
+ box-shadow: 0 0 0 0.1rem#409eff;
+ background-color: #012350;
+ color: #fff;
+ border: none;
+
+ &:hover {
+ background-color: #012350;
+ color: #fff;
+ border-color: #409eff;
+ }
+ }
+ }
+}
+
+.ztzf-data-cockpit-select {
+ .el-select__input {
+ color: #8ac3fd !important;
+ }
+
+ .el-select__wrapper {
+ background: #2E2E48;
+ box-shadow: none !important;
+ border: none;
+ width: 100%;
+ }
+
+ .el-select__placeholder {
+ &.is-transparent {
+ color: #86909C !important;
+ }
+
+ &.el-select__selected-item {
+ color: #ffffff;
+ }
+ }
+
+ .el-select {
+ --el-select-border-color-hover: rgb(0, 162, 255) !important;
+ }
+
+ .el-input {
+ --el-border-color: rgb(0, 162, 255);
+ }
+
+ .el-select .el-input__wrapper {
+ background-color: #012350 !important;
+ }
+
+ .el-input__wrapper {
+ --el-input-text-color: #012350 !important;
+ }
+}
+
+.ztzf-data-cockpit-select-popper.el-select__popper {
+ border: none;
+ background: #2E2E48 !important;
+
+ .el-select-dropdown {
+ overflow-x: hidden !important;
+ border: none;
+
+ .el-scrollbar {
+ .el-select-dropdown__wrap {
+ overflow-x: hidden !important;
+ border: none;
+
+ .el-select-dropdown__list {
+ padding: 0 !important;
+
+ .el-select-dropdown__item {
+ background: transparent !important;
+ color: white;
+
+ &.hover {
+ background-color: rgba(0, 120, 233, 0.63) !important;
+ color: white;
+ }
+
+ &.selected {
+ color: #8ac3fd !important;
+ font-weight: 700;
+ }
+ }
+ }
+ }
+ }
+
+ .el-select-dropdown__empty {
+ background: #2E2E48;
+ border-radius: 0px 0px 8px 8px;
+ }
+ }
+
+ .el-popper__arrow::before {
+ background: #2E2E48 !important;
+ border: 1px solid #2E2E48 !important;
+ }
+}
+
+// 树形选择
+.ztzf-data-cockpit-tree-select-popper {
+ background: #2E2E48 !important;
+ border: none !important;
+
+ .el-popper__arrow::before {
+ background: #2E2E48 !important;
+ border: 1px solid #2E2E48 !important;
+ }
+
+ .el-tree {
+ background: transparent !important;
+ color: #fff !important;
+
+ .el-tree-node__content:hover {
+ background: none !important;
+ }
+
+ .el-tree-node__content:hover {
+ background: none !important;
+ }
+
+ .el-tree-node.is-current>.el-tree-node__content {
+ color: #479dff !important;
+ }
+ }
+
+ .el-select-dropdown__item {
+ background: none !important;
+ color: #fff !important;
+
+ input {
+ color: #fff !important;
+
+ &::placeholder {
+ color: #fff !important;
+ }
+ }
+ }
+
+ .el-tree {
+ --el-tree-node-hover-bg-color: none !important;
+ color: #fff !important;
+
+ .el-checkbox__inner {
+ background: none !important;
+ border: 1px solid #3194ef !important;
+ }
+ }
+
+ .el-select-dropdown__item.is-selected {
+ color: #fff !important;
+ }
+
+ .el-popper.is-light {
+ background: #012a50 !important;
+ box-shadow: none !important;
+ border: none !important;
+ border-radius: 4px !important;
+ margin-top: 2px;
+
+ .el-select__selection {
+ max-width: 240px !important;
+
+ .el-select__selected-item {
+ .el-tag {
+ background: #0b1d38 !important;
+ color: white;
+ }
+ }
+ }
+
+ .el-popper__arrow::before {
+ background: #012a50 !important;
+ border: 1px solid #012a50 !important;
+ }
+ }
+}
+
+.ztzf-data-cockpit-dialog {
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ width: 1222px;
+ height: 835px;
+ background: #1A1A2A;
+ border-radius: 6px 6px 6px 6px;
+ border: 1px solid #2E2E46;
+ box-sizing: border-box;
+
+ .el-dialog__header {
+ padding-bottom: 30px;
+ height: auto;
+
+ .el-dialog__title {
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: bold;
+ font-size: 18px;
+ color: #FFFFFF;
+ line-height: 22px;
+ text-align: left;
+ font-style: normal;
+ text-transform: none;
+ }
+
+ .el-dialog__headerbtn {
+ .el-icon.el-dialog__close {
+ color: #fff;
+ }
+ }
+ }
+
+ .history-search {
+ .el-form-item__label {
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 500;
+ font-size: 14px;
+ color: #C5C9D6;
+ font-style: normal;
+ text-transform: none;
+ }
+
+ .ztzf-data-cockpit-search-input {
+ .el-input__wrapper {
+ background: #2E2E48;
+ }
+
+ &.is-disabled {
+ .el-input__wrapper {
+ background-color: #2E2E48;
+ }
+ }
+ }
+
+ .ztzf-data-cockpit-date-picker {
+ background: #2E2E48;
+ }
+
+ .ztzf-data-cockpit-date-picker-popper {
+ background: #2E2E48 !important;
+
+ .el-picker-panel {
+ .el-picker-panel__body-wrapper {
+ .el-picker-panel__body {
+ background: #2E2E48 !important;
+
+ .el-date-picker__time-header {
+
+ .el-date-picker__editor-wrap {
+ .el-time-panel {
+ background: #2E2E48 !important;
+
+ // 将 time-picker-popper 的样式移到这里
+ .el-time-panel__header {
+ background: #2E2E48 !important;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .el-popper__arrow {
+ &::before {
+ background: #2E2E48 !important;
+ border: 1px solid #2E2E48 !important;
+ box-sizing: border-box;
+ }
+ }
+ }
+
+ .history-search-actions {
+ .el-button {
+ width: 96px;
+ background: transparent !important;
+ border-radius: 4px 4px 4px 4px;
+ border: 1px solid rgba(255, 255, 255, 0.3);
+
+ .el-icon {
+ font-size: 16px;
+ color: #fff;
+ }
+ }
+ }
+ }
+
+ .el-dialog__body {
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .table-container {
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .table-content {
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .ztzf-data-cockpit-table {
+ height: 0;
+ flex: 1;
+ background: transparent !important;
+
+ .el-table--border .el-table__inner-wrapper:after, .el-table--border:after, .el-table--border:before, .el-table__inner-wrapper:before {
+ height: 0;
+ }
+
+ tr {
+ background: transparent !important;
+ }
+
+ th.el-table__cell {
+ border-bottom: 1px solid #323241;
+ background: transparent !important;
+
+ .cell {
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 500;
+ font-size: 14px;
+ color: #FFFFFF;
+ line-height: 22px;
+ font-style: normal;
+ text-transform: none;
+ }
+ }
+
+ td.el-table__cell {
+ border-bottom: 1px solid #323241;
+ background: transparent !important;
+
+ .cell {
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 400;
+ font-size: 14px;
+ color: #9E9EBA;
+ line-height: 18px;
+ font-style: normal;
+ text-transform: none;
+ }
+ }
+ }
+ }
+
+ .table-pagination {
+ padding-top: 30px;
+ padding-bottom: 10px;
+ display: flex;
+ justify-content: flex-end;
+
+ .el-pagination__total {
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 400;
+ font-size: 14px;
+ color: #9E9EBA;
+ font-style: normal;
+ text-transform: none;
+ }
+
+ .btn-prev,
+ .btn-next {
+ background: transparent !important;
+
+ .el-icon {
+ color: #C9CDD4;
+ }
+ }
+
+ .el-pager {
+ li {
+ margin-left: 8px;
+ font-family: Nunito Sans, Nunito Sans;
+ font-weight: 600;
+ font-size: 14px;
+ color: #9E9EBA;
+ font-style: normal;
+ text-transform: none;
+ background: transparent;
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &.is-active {
+ font-family: Nunito Sans, Nunito Sans;
+ font-weight: 600;
+ font-size: 14px;
+ color: #477EFF;
+ font-style: normal;
+ text-transform: none;
+ background: rgba(71, 126, 255, 0.16);
+ }
+ }
+ }
+
+ .el-select {
+ .el-select__wrapper {
+ .el-select__placeholder {
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 400;
+ font-size: 14px;
+ color: #9E9EBA;
+ font-style: normal;
+ text-transform: none;
+ }
+
+ background: #1C1C31;
+ border: none;
+ box-shadow: none;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/applications/drone-command/src/styles/element-ui.scss b/applications/drone-command/src/styles/element-ui.scss
index 8dc06ed..46dfc8f 100644
--- a/applications/drone-command/src/styles/element-ui.scss
+++ b/applications/drone-command/src/styles/element-ui.scss
@@ -766,265 +766,4 @@
box-sizing: border-box;
background: #fafafa;
}
-}
-
-.ztzf-data-cockpit-search-input {
- .el-input__wrapper {
- padding: 0 8px !important;
- background: #161B2C;
- box-shadow: none !important;
- border: none;
-
- .el-input__inner {
- color: #ffffff;
-
- &::placeholder {
- color: #86909C;
- }
- }
- }
-
- &.is-disabled {
- .el-input__wrapper {
- background-color: #161B2C;
- }
- }
-}
-
-.ztzf-data-cockpit-date-picker {
- box-shadow: none !important;
- background: #161B2C;
- border: none;
- border-radius: 5px;
- box-sizing: border-box !important;
-
- .el-input__wrapper {
- box-shadow: none !important;
- background-color: transparent !important;
-
- &.is-focus {
- box-shadow: none !important;
- }
- }
-
- .el-range-input {
- color: #fff;
-
- &::placeholder {
- color: #86909C;
- }
- }
-
- .el-range-separator {
- color: #ffffff;
- }
-}
-
-.ztzf-data-cockpit-date-picker-popper {
- background: #161B2C !important;
- border: 1px solid !important;
- border-radius: 0px 0px 8px 8px;
- border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
-
- .el-date-picker__prev-btn,
- .el-date-picker__next-btn {
- .el-icon {
- color: #fff !important;
- }
- }
-
- .el-date-picker__header-label {
- color: #fff !important;
- }
-
- .el-picker-panel {
- .el-picker-panel__body-wrapper {
- .el-picker-panel__body {
- background: #161B2C !important;
- color: #fff !important;
-
- .el-date-picker__time-header {
- border: none;
-
- .el-date-picker__editor-wrap {
- .el-time-panel {
- background: #161B2C !important;
- border-radius: 0px 0px 8px 8px;
- border: 1px solid !important;
- border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
-
- // 将 time-picker-popper 的样式移到这里
- .el-time-panel__header {
- background: #161B2C !important;
- color: #ffffff !important;
- border-bottom: 1px solid rgba(71, 157, 255, 0.3) !important;
- }
-
- .el-time-panel__content {
- .el-time-spinner__item {
- color: #e6e6e6 !important;
-
- &:hover {
- background: rgba(71, 157, 255, 0.3) !important;
- }
-
- &.active {
- color: #479dff !important;
- font-weight: bold;
- }
- }
- }
-
- .el-time-panel__footer {
- background: #012350 !important;
- border-top: 1px solid rgba(71, 157, 255, 0.3) !important;
-
- .el-button {
- color: #fff !important;
-
- &:hover {
- color: #479dff !important;
- }
- }
-
- .el-time-panel__btn.cancel {
- color: #fff !important;
-
- &:hover {
- color: #fff !important;
- }
- }
- }
- }
- }
- }
-
- .el-picker-panel__content {
- .el-date-range-picker__header {
- .el-picker-panel__icon-btn {
- color: #fff !important;
-
- &:hover {
- color: var(--el-color-primary) !important;
- }
-
- .el-icon {
- &::before {
- color: inherit !important;
- }
- }
- }
-
- &>div {
- span {
- color: #fff !important;
- }
- }
- }
-
- .el-date-table {
- th {
- color: #fff !important;
- }
-
- td.disabled div {
- background-color: #00193b !important;
- color: #505050 !important;
- cursor: not-allowed;
- }
-
- // td:hover,
- // td.current:not(.disabled) {
- // background: rgba(60, 121, 202) !important;
- // color: white !important;
- // }
-
- td.start-date span,
- td.start-date span,
- td.end-date span {
- background-color: #012350 !important;
- }
-
- td.in-range div,
- td.in-range div:hover,
- &.is-week-mode .el-date-table__row.current div,
- &.is-week-mode .el-date-table__row:hover div {
- background-color: rgba(60, 121, 202) !important;
- }
- }
- }
- }
- }
- }
-
- .el-popper__arrow {
- &::before {
- background: #161B2C !important;
- border: 1px solid #161B2C !important;
- box-sizing: border-box;
- }
- }
-
- .el-picker-panel__sidebar {
- background: #012350;
- color: #fff;
-
- .el-picker-panel__shortcut {
- color: #ffffff;
- }
- }
-
- .el-picker-panel__body {
- .el-input.is-disabled .el-input__wrapper {
- background: #012350;
- box-shadow: 0 0 0 0.1rem#409eff;
- color: #fff;
-
- .el-input__inner {
- color: #fff !important;
- }
- }
-
- .el-button.is-disabled,
- .el-button.is-disabled:hover {
- background: #012350;
- }
-
- .el-input__wrapper {
- background: #012350;
- box-shadow: 0 0 0 0.1rem#409eff;
- color: #fff;
-
- .el-input__inner {
- color: #fff !important;
- }
- }
- }
-
- .el-picker-panel__footer {
- background: #012350;
- color: #fff;
- border-top: none;
-
- .el-button--small {
- box-shadow: 0 0 0 0.1rem#409eff;
- background-color: #012350;
- color: #fff;
- border: none;
-
- &:hover {
- background-color: #012350;
- color: #fff;
- border-color: #409eff;
- }
- }
- }
-}
-
-.ztzf-data-cockpit-dialog {
- width: 1222px;
- height: 835px;
- background: #1A1A2A;
- border-radius: 6px 6px 6px 6px;
- border: 1px solid #2E2E46;
}
\ No newline at end of file
diff --git a/applications/drone-command/src/styles/top.scss b/applications/drone-command/src/styles/top.scss
index dd2148e..1a8cc46 100644
--- a/applications/drone-command/src/styles/top.scss
+++ b/applications/drone-command/src/styles/top.scss
@@ -56,13 +56,6 @@
}
.top-bar__img {
- margin: 0 5px;
- padding: 2px;
- width: 30px;
- height: 30px;
- border-radius: 100%;
- box-sizing: border-box;
- border: 1px solid #eee;
vertical-align: middle;
}
diff --git a/applications/drone-command/src/utils/mapUtils.js b/applications/drone-command/src/utils/mapUtils.js
index a91d35f..028563a 100644
--- a/applications/drone-command/src/utils/mapUtils.js
+++ b/applications/drone-command/src/utils/mapUtils.js
@@ -315,6 +315,13 @@
// 销毁处理
destroy () {
+ if (!this._viewer || this._viewer.isDestroyed?.()) {
+ this._primitiveCollection = null
+ this._entityCircleDataSource = null
+ this._viewer = null
+ return
+ }
+
if (this._primitiveCollection) {
this._primitiveCollection.removeAll()
this._viewer.scene.primitives.remove(this._primitiveCollection)
diff --git a/applications/drone-command/src/views/api.txt b/applications/drone-command/src/views/api.txt
index 86dc87e..1decdbe 100644
--- a/applications/drone-command/src/views/api.txt
+++ b/applications/drone-command/src/views/api.txt
@@ -1,48 +1,22 @@
-## 信号干扰,诱导驱离
+## 历史告警-实时告警
-**接口地址**:`/cockpit/dataCockpit/interferenceAndExpulsion`
+**接口地址**:`/cockpit/dataCockpit/alarmLog`
-**请求方式**:`POST`
+**请求方式**:`GET`
-**请求数据类型**:`application/json`
+**请求数据类型**:`application/x-www-form-urlencoded`
**响应数据类型**:`*/*`
-**接口描述**:<p>传入fwEffectEval</p>
+**接口描述**:<p>传入fwDroneAlarmRecord</p>
-
-
-**请求示例**:
-
-
-```javascript
-{
- "alarmRecordId": 0,
- "areaCode": "",
- "counterEffect": "",
- "coverRadiusM": 0,
- "deployLatitude": 0,
- "deployLongitude": 0,
- "deviceCode": "",
- "deviceId": 0,
- "deviceModel": "",
- "deviceName": "",
- "deviceType": "",
- "droneDeviceCode": "",
- "droneName": "",
- "droneType": "",
- "findTime": "",
- "id": 0,
- "workMode": ""
-}
-```
**请求参数**:
@@ -53,24 +27,31 @@
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
| -------- | -------- | ----- | -------- | -------- | ------ |
-|fwEffectEval|fwEffectEval|body|true|FwEffectEvalDTO|FwEffectEvalDTO|
-|  alarmRecordId|告警记录ID(ja_fw_drone_alarm_record.id)||false|integer(int64)||
-|  areaCode|区域编码||false|string||
-|  counterEffect|反制效果(success/fail)||false|string||
-|  coverRadiusM|覆盖范围(米)||false|integer(int32)||
-|  deployLatitude|部署纬度(冗余)||false|number(double)||
-|  deployLongitude|部署经度(冗余)||false|number(double)||
-|  deviceCode|反制设备编码(快照)||false|string||
-|  deviceId|反制设备ID(ja_fw_device.id)||false|integer(int64)||
-|  deviceModel|反制设备型号(快照)||false|string||
-|  deviceName|反制设备名称(快照)||false|string||
-|  deviceType|反制设备类型(快照)||false|string||
-|  droneDeviceCode|无人机设备编码||false|string||
-|  droneName|无人机名称||false|string||
-|  droneType|无人机类型||false|string||
-|  findTime|发现时间||false|string(date-time)||
-|  id|主键id||false|integer(int64)||
-|  workMode|工作模式(机动/固定)||false|string||
+|alarmTime|告警时间|query|false|string(date-time)||
+|alarmType|告警类型:1.实时告警/2.历史告警|query|false|string||
+|areaCode|区域编码|query|false|string||
+|areaDivideId|区域id ja_fw_area_divide|query|false|string||
+|areaName|区域名称|query|false|string||
+|counterWay|反制方式:1.信号干扰/2.诱导驱离/无|query|false|string||
+|current|当前页|query|false|integer(int32)||
+|defenseSceneId|场景id ja_fw_defense_scene.id|query|false|string||
+|deviceId|反无设备ID(关联ja_fw_device.id)|query|false|integer(int64)||
+|deviceType|设备类型|query|false|string||
+|droneName|无人机名称|query|false|string||
+|droneSerialNo|无人机序列号|query|false|string||
+|droneType|无人机类型(1.微型机/2.植保机...)|query|false|string||
+|endTime|结束时间|query|false|string||
+|flightHeightM|飞行高度(m)|query|false|integer(int32)||
+|flightSpeedMs|飞行速度(m/s)|query|false|number||
+|flightStatus|飞行状态:1.侦测中/2.反制中|query|false|string||
+|id|主键id|query|false|integer(int64)||
+|latitude|当前纬度|query|false|number(double)||
+|longitude|当前经度|query|false|number(double)||
+|signalFreqMhz|信号频段(MHz)|query|false|integer(int32)||
+|size|每页的数量|query|false|integer(int32)||
+|startTime|开始时间|query|false|string||
+|stayDuration|停留时长 (秒) |query|false|string||
+|triggerReason|触发原因|query|false|string||
**响应状态**:
@@ -78,8 +59,7 @@
| 状态码 | 说明 | schema |
| -------- | -------- | ----- |
-|200|OK|R|
-|201|Created||
+|200|OK|R«IPage«FwDroneAlarmRecordVO»»|
|401|Unauthorized||
|403|Forbidden||
|404|Not Found||
@@ -91,7 +71,33 @@
| 参数名称 | 参数说明 | 类型 | schema |
| -------- | -------- | ----- |----- |
|code|状态码|integer(int32)|integer(int32)|
-|data|承载数据|object||
+|data|承载数据|IPage«FwDroneAlarmRecordVO»|IPage«FwDroneAlarmRecordVO»|
+|  current||integer(int64)||
+|  pages||integer(int64)||
+|  records||array|FwDroneAlarmRecordVO|
+|    alarmTime|告警时间|string||
+|    alarmType|告警类型:1.实时告警/2.历史告警|string||
+|    areaCode|区域编码|string||
+|    areaName|区域名称|string||
+|    counterWay|反制方式:1.信号干扰/2.诱导驱离/3.无|string||
+|    deviceId|反无设备ID(关联ja_fw_device.id)|integer||
+|    deviceModel|设备型号|string||
+|    deviceName|设备名称|string||
+|    deviceType|设备类型(1.便捷侦测箱/2.反制枪/3.察打一体)|string||
+|    droneName|无人机名称|string||
+|    droneSerialNo|无人机序列号|string||
+|    droneType|无人机类型(1.微型机/2.植保机...)|string||
+|    flightHeightM|飞行高度(m)|integer||
+|    flightSpeedMs|飞行速度(m/s)|number||
+|    flightStatus|飞行状态:1.侦测中/2.反制中|string||
+|    id|主键id|integer||
+|    latitude|当前纬度|number||
+|    longitude|当前经度|number||
+|    signalFreqMhz|信号频段(MHz)|integer||
+|    stayDuration|停留时长(秒)|string||
+|    triggerReason|触发原因|string||
+|  size||integer(int64)||
+|  total||integer(int64)||
|msg|返回消息|string||
|success|是否成功|boolean||
@@ -100,7 +106,37 @@
```javascript
{
"code": 0,
- "data": {},
+ "data": {
+ "current": 0,
+ "pages": 0,
+ "records": [
+ {
+ "alarmTime": "",
+ "alarmType": "",
+ "areaCode": "",
+ "areaName": "",
+ "counterWay": "",
+ "deviceId": 0,
+ "deviceModel": "",
+ "deviceName": "",
+ "deviceType": "",
+ "droneName": "",
+ "droneSerialNo": "",
+ "droneType": "",
+ "flightHeightM": 0,
+ "flightSpeedMs": 0,
+ "flightStatus": "",
+ "id": 0,
+ "latitude": 0,
+ "longitude": 0,
+ "signalFreqMhz": 0,
+ "stayDuration": "",
+ "triggerReason": ""
+ }
+ ],
+ "size": 0,
+ "total": 0
+ },
"msg": "",
"success": true
}
diff --git a/applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue b/applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue
index c9b0954..1ac9a90 100644
--- a/applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue
@@ -1,94 +1,87 @@
<template>
- <el-dialog
- class="ztzf-data-cockpit-dialog"
- append-to-body
- v-model="visibleModel"
- :title="dialogTitle"
- :close-on-click-modal="false"
- :destroy-on-close="true"
- >
- <div class="dialog-body">
- <el-form ref="queryParamsRef" :model="searchParams" class="history-search">
- <el-row :gutter="12">
- <el-col :span="5">
- <el-form-item label="关键字" prop="keyword">
- <el-input v-model="searchParams.keyword" placeholder="名称/序列号" clearable @clear="handleSearch" />
- </el-form-item>
- </el-col>
- <el-col :span="5">
- <el-form-item label="日期" prop="dateRange">
- <el-date-picker
- v-model="searchParams.dateRange"
- type="daterange"
- range-separator="至"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- value-format="YYYY/MM/DD"
- @change="handleSearch"
- />
- </el-form-item>
- </el-col>
- <el-col :span="5">
- <el-form-item label="区域" prop="area">
- <el-tree-select
- v-model="searchParams.area"
- :data="areaTree"
- :props="areaTreeProps"
- node-key="value"
- check-strictly
- clearable
- placeholder="请选择"
- @change="handleSearch"
- />
- </el-form-item>
- </el-col>
- <el-col :span="5">
- <el-form-item label="无人机类型" prop="droneType">
- <el-select
- v-model="searchParams.droneType"
- placeholder="请选择"
- clearable
- @change="handleSearch"
- >
- <el-option v-for="item in droneTypeOptions" :key="item" :label="item" :value="item" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="4" class="history-search-actions">
- <el-form-item>
- <el-button @click="resetForm">重置</el-button>
- <el-button type="primary" @click="handleSearch">查询</el-button>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
+ <el-dialog class="ztzf-data-cockpit-dialog" append-to-body v-model="visibleModel" :title="dialogTitle"
+ :close-on-click-modal="false" :destroy-on-close="true">
+ <el-form ref="queryParamsRef" :model="searchParams" class="history-search">
+ <el-row :gutter="12">
+ <el-col :span="4">
+ <el-form-item label="关键字" prop="keyword">
+ <el-input class="ztzf-data-cockpit-search-input" v-model="searchParams.keyword"
+ placeholder="名称/序列号" clearable @clear="handleSearch" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="5">
+ <el-form-item prop="dateRange">
+ <el-date-picker class="ztzf-data-cockpit-date-picker"
+ popper-class="ztzf-data-cockpit-date-picker-popper" v-model="searchParams.dateRange"
+ type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
+ value-format="YYYY/MM/DD" @change="handleSearch" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="区域" prop="area">
+ <el-tree-select class="ztzf-data-cockpit-select" popper-class="ztzf-tree-cockpit-select-popper" v-model="searchParams.area" :data="areaTree" :props="areaTreeProps"
+ node-key="value" check-strictly clearable placeholder="请选择" @change="handleSearch" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="5">
+ <el-form-item label="无人机类型" prop="droneType">
+ <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
+ v-model="searchParams.droneType" placeholder="请选择" clearable @change="handleSearch">
+ <el-option v-for="item in droneTypeOptions" :key="item.value" :label="item.label"
+ :value="item.value" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="6" class="history-search-actions">
+ <el-form-item>
+ <el-button :icon="RefreshRight" @click="resetForm"></el-button>
+ <el-button :icon="Search" @click="handleSearch"></el-button>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
- <el-table v-loading="loading" :data="pagedList" height="360" class="history-table">
- <el-table-column type="index" width="60" label="序号" />
- <el-table-column prop="droneName" label="无人机名称" min-width="120" />
- <el-table-column prop="serialNumber" label="无人机序列号" min-width="140" />
- <el-table-column prop="droneType" label="无人机类型" min-width="110" />
- <el-table-column prop="area" label="区域" min-width="90" />
- <el-table-column prop="dataSource" label="数据来源" min-width="110" />
- <el-table-column prop="frequency" label="信号频段" min-width="100" />
- <el-table-column prop="discoveredAt" label="发现时间" min-width="150" />
- <el-table-column prop="stayTime" label="停留时间" min-width="110" />
- <el-table-column prop="counterMethod" label="反制方式" min-width="110" />
- </el-table>
-
- <div class="history-pagination">
- <el-pagination
- v-model:current-page="searchParams.current"
- v-model:page-size="searchParams.size"
- :total="total"
- />
+ <div class="table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
+ <div class="table-content">
+ <el-table class="ztzf-data-cockpit-table" :data="list">
+ <el-table-column type="index" width="60" label="序号" />
+ <el-table-column prop="droneName" label="无人机名称" min-width="120" />
+ <el-table-column prop="droneSerialNo" label="无人机序列号" min-width="140" />
+ <el-table-column label="无人机类型" min-width="110">
+ <template #default="{ row }">
+ {{ getDroneTypeText(row.droneType) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="areaName" label="区域" min-width="90" />
+ <el-table-column prop="deviceName" label="数据来源" min-width="110" />
+ <el-table-column label="信号频段" min-width="100">
+ <template #default="{ row }">
+ {{ formatFrequency(row.signalFreqMhz) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="alarmTime" label="发现时间" min-width="150" />
+ <el-table-column prop="stayDuration" label="停留时间" min-width="110" />
+ <el-table-column label="反制方式" min-width="110">
+ <template #default="{ row }">
+ {{ getCounterWayText(row.counterWay) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <div class="table-pagination">
+ <el-pagination popper-class="ztzf-data-cockpit-select-popper" v-model:current-page="searchParams.current" v-model:page-size="searchParams.size"
+ layout="total, prev, pager, next, sizes"
+ :total="total" @change="getList" />
</div>
</div>
</el-dialog>
</template>
<script setup>
-import { computed, ref } from 'vue'
+import { Search, RefreshRight } from '@element-plus/icons-vue'
+import { computed, ref, watch } from 'vue'
+import { alarmLogApi } from '@/api/dataCockpit'
const props = defineProps({
modelValue: {
@@ -118,11 +111,13 @@
area: '',
droneType: '',
current: 1,
- size: 6
+ size: 10
})
const searchParams = ref(initSearchParams())
const queryParamsRef = ref(null)
const loading = ref(false)
+const list = ref([])
+const total = ref(0)
const areaTree = ref([
{
label: '古村区',
@@ -142,167 +137,79 @@
children: 'children'
}
-const historyList = computed(() => {
- const dataSource = props.device?.name || '侦测反制设备名称'
- return [
- {
- id: 'his_001',
- droneName: '无人机名称',
- serialNumber: 'XLH789456',
- droneType: '微型机',
- area: '古村区',
- dataSource,
- frequency: '5800MHZ',
- discoveredAt: '2025/12/26 12:15:26',
- stayTime: '00:10:50',
- counterMethod: '信号干扰'
- },
- {
- id: 'his_002',
- droneName: '无人机名称',
- serialNumber: 'XLH789456',
- droneType: '微型机',
- area: '古村区',
- dataSource,
- frequency: '5800MHZ',
- discoveredAt: '2025/12/26 12:18:06',
- stayTime: '00:08:12',
- counterMethod: '信号干扰'
- },
- {
- id: 'his_003',
- droneName: '无人机名称',
- serialNumber: 'XLH789456',
- droneType: '微型机',
- area: '古村区',
- dataSource,
- frequency: '2400MHZ',
- discoveredAt: '2025/12/26 12:22:31',
- stayTime: '00:06:09',
- counterMethod: '诱导驱离'
- },
- {
- id: 'his_004',
- droneName: '无人机名称',
- serialNumber: 'XLH789456',
- droneType: '微型机',
- area: '古村区',
- dataSource,
- frequency: '2400MHZ',
- discoveredAt: '2025/12/26 12:28:44',
- stayTime: '00:12:03',
- counterMethod: '诱导驱离'
- },
- {
- id: 'his_005',
- droneName: '无人机名称',
- serialNumber: 'XLH789456',
- droneType: '微型机',
- area: '古村区',
- dataSource,
- frequency: '5800MHZ',
- discoveredAt: '2025/12/26 12:32:10',
- stayTime: '00:04:39',
- counterMethod: '信号干扰'
- },
- {
- id: 'his_006',
- droneName: '无人机名称',
- serialNumber: 'XLH789456',
- droneType: '微型机',
- area: '古村区',
- dataSource,
- frequency: '5800MHZ',
- discoveredAt: '2025/12/26 12:36:42',
- stayTime: '00:07:21',
- counterMethod: '信号干扰'
+const droneTypeOptions = [
+ { label: '微型机', value: '1' },
+ { label: '植保机', value: '2' }
+]
+
+const getList = async () => {
+ if (!props.device?.id) {
+ list.value = []
+ total.value = 0
+ return
+ }
+ const [startTime, endTime] = searchParams.value.dateRange || []
+ const keyword = (searchParams.value.keyword || '').trim()
+ const params = {
+ alarmType: '2',
+ deviceId: props.device.id,
+ current: searchParams.value.current,
+ size: searchParams.value.size,
+ startTime,
+ endTime,
+ areaName: searchParams.value.area || undefined,
+ droneType: searchParams.value.droneType || undefined,
+ droneName: keyword || undefined,
+ }
+ loading.value = true
+ try {
+ const res = await alarmLogApi(params)
+ list.value = res?.data?.data?.records ?? []
+ total.value = res?.data?.data?.total ?? 0
+ } finally {
+ loading.value = false
+ }
+}
+
+function handleSearch () {
+ searchParams.value.current = 1
+ getList()
+}
+
+function resetForm () {
+ searchParams.value = initSearchParams()
+
+ getList()
+}
+
+const formatFrequency = (value) => {
+ if (value == null || value === '') return '-'
+ return `${value}MHZ`
+}
+
+const getCounterWayText = (value) => {
+ if (value === 1 || value === '1') return '信号干扰'
+ if (value === 2 || value === '2') return '诱导驱离'
+ if (value === 3 || value === '3') return '无'
+ return value || '-'
+}
+
+const getDroneTypeText = (value) => {
+ if (value === 1 || value === '1') return '微型机'
+ if (value === 2 || value === '2') return '植保机'
+ return value || '-'
+}
+
+watch(
+ () => visibleModel.value,
+ (visible) => {
+ if (visible) {
+ searchParams.value.current = 1
+ getList()
}
- ]
-})
-
-const droneTypeOptions = computed(() => {
- const types = historyList.value.map((item) => item.droneType).filter(Boolean)
- return Array.from(new Set(types))
-})
-
-const filteredList = computed(() => {
- const { keyword, dateRange, area, droneType } = searchParams.value
- const [startDate, endDate] = dateRange || []
- return historyList.value.filter((item) => {
- const keywordMatch =
- !keyword || item.droneName.includes(keyword) || item.serialNumber.includes(keyword)
- const typeMatch = !droneType || item.droneType === droneType
- const areaMatch = !area || item.area.includes(area)
- const dateMatch =
- !startDate || !endDate || isWithinDateRange(item.discoveredAt, startDate, endDate)
- return keywordMatch && typeMatch && areaMatch && dateMatch
- })
-})
-
-const total = computed(() => filteredList.value.length)
-
-const pagedList = computed(() => {
- const { current, size } = searchParams.value
- const start = (current - 1) * size
- return filteredList.value.slice(start, start + size)
-})
-
-function handleSearch() {
- searchParams.value.current = 1
-}
-
-function resetForm() {
- queryParamsRef.value?.resetFields()
- searchParams.value.current = 1
-}
-
-function isWithinDateRange(value, start, end) {
- const dateValue = new Date(value)
- const startDate = new Date(`${start} 00:00:00`)
- const endDate = new Date(`${end} 23:59:59`)
- return dateValue >= startDate && dateValue <= endDate
-}
+ }
+)
</script>
-<style lang="scss" scoped>
-.dialog-body {
- display: flex;
- flex-direction: column;
- gap: 16px;
- color: #C3C3DD;
-}
-
-.history-search {
- padding: 8px 12px 4px;
- background: rgba(10, 14, 34, 0.6);
- border: 1px solid rgba(255, 255, 255, 0.08);
- border-radius: 6px;
-}
-
-.history-search :deep(.el-date-editor),
-.history-search :deep(.el-select),
-.history-search :deep(.el-tree-select) {
- width: 100%;
-}
-
-.history-search-actions :deep(.el-form-item__content) {
- justify-content: flex-end;
- gap: 8px;
-}
-
-.history-table {
- margin-top: 8px;
- --el-table-border-color: rgba(255, 255, 255, 0.08);
- --el-table-header-bg-color: rgba(20, 24, 48, 0.9);
- --el-table-row-hover-bg-color: rgba(255, 255, 255, 0.03);
- --el-table-text-color: #C3C3DD;
- --el-table-header-text-color: #FFFFFF;
-}
-
-.history-pagination {
- display: flex;
- justify-content: flex-end;
- padding-top: 8px;
-}
-</style>
+<style lang="scss" scoped></style>
diff --git a/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue b/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue
index 07b66eb..7b54029 100644
--- a/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue
@@ -115,17 +115,12 @@
position: absolute;
top: 50%;
left: 357px;
- background: rgba(17, 23, 34, 0.8);
- border: 1px solid #333355;
- border-left: none;
- border-radius: 0 4px 4px 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
z-index: 10;
- backdrop-filter: blur(5px);
transform: translateY(-50%);
.arrow {
@@ -140,8 +135,19 @@
}
.category-container {
+ position: relative;
display: flex;
justify-content: space-between;
+
+ &::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ height: 1px;
+ background: #333355;
+ }
.category-item {
position: relative;
@@ -175,8 +181,7 @@
left: 0;
bottom: 0;
width: 100%;
- height: 1px;
- background: #333355;
+ height: 0;
}
&.active::after {
@@ -184,6 +189,7 @@
background: #284FE3;
box-shadow: 0px 3px 2px 0px rgba(15, 89, 255, 0.1), 0px 7px 5px 0px rgba(15, 89, 255, 0.15), 0px 13px 10px 0px rgba(15, 89, 255, 0.18), 0px 22px 18px 0px rgba(15, 89, 255, 0.21), 0px 42px 33px 0px rgba(15, 89, 255, 0.26), 0px 100px 80px 0px rgba(15, 89, 255, 0.36);
border-radius: 5px 5px 0px 0px;
+ z-index: 9;
}
.badge {
diff --git a/applications/drone-command/src/views/dataCockpit/components/MapContainer.vue b/applications/drone-command/src/views/dataCockpit/components/MapContainer.vue
index aa94178..4576555 100644
--- a/applications/drone-command/src/views/dataCockpit/components/MapContainer.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/MapContainer.vue
@@ -1,28 +1,274 @@
<template>
- <div class="ztzf-cesium map-container" id="cesium">
+ <div class="ztzf-cesium map-container" id="cesium"></div>
+ <div class="layer-control-root" :class="{ collapsed: props.leftCollapsed }">
+ <div class="layer-control-wrap" ref="layerWrapRef">
+ <div class="layer-control" @click="toggleLayerPanel">
+ <img :src="layerControlIcon" alt="图层控制">
+ </div>
+ <div v-if="showLayerPanel" class="layer-panel">
+ <div class="panel-title">图层管理</div>
+ <div class="panel-content">
+ <el-tree :data="layerTree" show-checkbox default-expand-all node-key="key" :props="layerTreeProps"
+ :default-checked-keys="defaultCheckedKeys" />
+ </div>
+ </div>
+ </div>
</div>
</template>
<script setup>
+import * as Cesium from 'cesium'
import { PublicCesium } from '@/utils/cesium/publicCesium'
+import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png'
+import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png'
+
+const props = defineProps({
+ onlineDevices: {
+ type: Array,
+ default: () => []
+ },
+ leftCollapsed: {
+ type: Boolean,
+ default: false
+ }
+})
+
let viewInstance = null
let viewer = null
+const deviceEntityIds = new Set()
+const showLayerPanel = ref(false)
+const layerWrapRef = ref(null)
+const layerTreeProps = {
+ label: 'label',
+ children: 'children'
+}
+const defaultCheckedKeys = ['global', 'city-base']
+const layerTree = ref([
+ {
+ key: 'base',
+ label: '地理信息图层',
+ children: [
+ { key: 'global', label: '全球地形' },
+ { key: 'admin', label: '行政区划' }
+ ]
+ },
+ {
+ key: 'city',
+ label: '城市CIM图层',
+ children: [
+ { key: 'city-base', label: '皖山白模' },
+ { key: 'city-grid', label: '皖山白模光栅网格' },
+ { key: 'city-tilt', label: '皖山倾斜摄影' },
+ { key: 'city-tilt-grid', label: '皖山倾斜摄影网格' }
+ ]
+ },
+ {
+ key: 'sky',
+ label: '空域要素图层',
+ children: [
+ { key: 'airspace', label: '空域边界' },
+ { key: 'route', label: '飞行航路' }
+ ]
+ }
+])
+
+const getDevicePosition = (item) => {
+ const longitudeRaw = item.longitude ?? item.lng ?? item.lon
+ const latitudeRaw = item.latitude ?? item.lat
+ if (longitudeRaw == null || latitudeRaw == null) return null
+ const longitude = Number(longitudeRaw)
+ const latitude = Number(latitudeRaw)
+ if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) return null
+ return { longitude, latitude }
+}
+
+const clearDeviceEntities = () => {
+ if (!viewer) return
+ deviceEntityIds.forEach((id) => {
+ viewer.entities.removeById(id)
+ })
+ deviceEntityIds.clear()
+}
+
+const RING_STYLES = [
+ { inner: 0, outer: 5000, gradient: ['#FF361C', '#360B00'] },
+ { inner: 5000, outer: 8000, gradient: ['#FFC609', '#583300'] },
+ { inner: 8000, outer: 10000, gradient: ['#2AEDBF', '#012B11'] }
+]
+
+const MATERIAL_TYPE = 'RadialGradientMaterial'
+let materialRegistered = false
+
+const registerRadialGradientMaterial = () => {
+ if (materialRegistered || !Cesium?.Material) return
+ materialRegistered = true
+ Cesium.Material._materialCache.addMaterial(MATERIAL_TYPE, {
+ fabric: {
+ type: MATERIAL_TYPE,
+ uniforms: {
+ color1: new Cesium.Color(1.0, 1.0, 1.0, 1.0),
+ color2: new Cesium.Color(0.0, 0.0, 0.0, 1.0)
+ },
+ source: `
+ czm_material czm_getMaterial(czm_materialInput materialInput) {
+ czm_material material = czm_getDefaultMaterial(materialInput);
+ vec2 st = materialInput.st - vec2(0.5);
+ float t = clamp(length(st) * 2.0, 0.0, 1.0);
+ vec4 color = mix(color1, color2, t);
+ material.diffuse = color.rgb;
+ material.alpha = color.a;
+ return material;
+ }
+ `
+ },
+ translucent: () => true
+ })
+}
+
+class RadialGradientMaterialProperty {
+ constructor(color1, color2) {
+ this._definitionChanged = new Cesium.Event()
+ this.color1 = color1
+ this.color2 = color2
+ }
+
+ get isConstant () {
+ return true
+ }
+
+ get definitionChanged () {
+ return this._definitionChanged
+ }
+
+ getType () {
+ return MATERIAL_TYPE
+ }
+
+ getValue (time, result) {
+ const target = result || {}
+ target.color1 = this.color1
+ target.color2 = this.color2
+ return target
+ }
+
+ equals (other) {
+ return (
+ other instanceof RadialGradientMaterialProperty &&
+ Cesium.Color.equals(this.color1, other.color1) &&
+ Cesium.Color.equals(this.color2, other.color2)
+ )
+ }
+}
+
+const buildCirclePositions = (center, radiusMeters, steps = 64) => {
+ const positions = []
+ const latRad = Cesium.Math.toRadians(center.latitude)
+ const metersToLat = 1 / 111320
+ const metersToLon = 1 / (111320 * Math.cos(latRad))
+ for (let i = 0; i <= steps; i += 1) {
+ const angle = Cesium.Math.toRadians((i / steps) * 360)
+ const dx = radiusMeters * Math.cos(angle)
+ const dy = radiusMeters * Math.sin(angle)
+ const lon = center.longitude + dx * metersToLon
+ const lat = center.latitude + dy * metersToLat
+ positions.push(Cesium.Cartesian3.fromDegrees(lon, lat))
+ }
+ return positions
+}
+
+const addDeviceRings = (center, baseId) => {
+ RING_STYLES.forEach((ring, index) => {
+ const outerPositions = buildCirclePositions(center, ring.outer)
+ const holes = ring.inner
+ ? [new Cesium.PolygonHierarchy(buildCirclePositions(center, ring.inner))]
+ : []
+ const entityId = `${baseId}-ring-${index}`
+ deviceEntityIds.add(entityId)
+ registerRadialGradientMaterial()
+ const [startColor, endColor] = ring.gradient
+ const color1 = Cesium.Color.fromCssColorString(startColor).withAlpha(0.34)
+ const color2 = Cesium.Color.fromCssColorString(endColor).withAlpha(0.34)
+ const material = new RadialGradientMaterialProperty(color1, color2)
+ viewer.entities.add({
+ id: entityId,
+ polygon: {
+ hierarchy: new Cesium.PolygonHierarchy(outerPositions, holes),
+ material
+ }
+ })
+ })
+}
+
+const renderDeviceEntities = (devices) => {
+ console.log(devices, 'devices')
+
+ if (!viewer) return
+
+ clearDeviceEntities()
+ devices.forEach((item, index) => {
+ const position = getDevicePosition(item)
+ if (!position) return
+ const entityId = `online-device-${item.id ?? index}`
+ deviceEntityIds.add(entityId)
+ addDeviceRings(position, entityId)
+ viewer.entities.add({
+ id: entityId,
+ position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
+ billboard: {
+ image: equipmentIcon,
+ width: 40.34,
+ height: 40.34
+ }
+ })
+ })
+}
+
+watch(
+ () => props.onlineDevices,
+ (devices) => {
+ renderDeviceEntities(devices || [])
+ },
+ { deep: true }
+)
+
+watch(
+ () => props.leftCollapsed,
+ (isCollapsed) => {
+ if (isCollapsed) showLayerPanel.value = false
+ }
+)
+
+const toggleLayerPanel = () => {
+ showLayerPanel.value = !showLayerPanel.value
+}
+
+const handleClickOutside = (event) => {
+ if (!showLayerPanel.value) return
+ const target = event.target
+ if (layerWrapRef.value?.contains(target)) return
+ showLayerPanel.value = false
+}
+
onMounted(() => {
+ document.addEventListener('click', handleClickOutside)
viewInstance = new PublicCesium({
dom: 'cesium',
flatMode: false,
terrain: false,
layerMode: 4,
- contour: false,
+ contour: false
})
viewer = viewInstance.getViewer()
-})
+ renderDeviceEntities(props.onlineDevices)
+})
onBeforeUnmount(() => {
+ document.removeEventListener('click', handleClickOutside)
+ clearDeviceEntities()
if (viewInstance) {
- viewInstance?.destroy()
+ viewInstance?.viewerDestroy()
viewInstance = null
}
@@ -38,4 +284,148 @@
width: 100%;
height: 100%;
}
-</style>
\ No newline at end of file
+
+.layer-control-root {
+ position: absolute;
+ left: 411px;
+ bottom: 22px;
+ z-index: 9;
+ transition: transform 0.3s ease-in-out;
+ pointer-events: none;
+
+ &.collapsed {
+ transform: translateX(-401px);
+ }
+}
+
+.layer-control-wrap {
+ position: relative;
+ display: flex;
+ align-items: flex-end;
+ pointer-events: auto;
+}
+
+.layer-control {
+ width: 34px;
+ height: 34px;
+ cursor: pointer;
+
+ img {
+ width: 100%;
+ height: 100%;
+ display: block;
+ }
+}
+
+.layer-panel {
+ display: flex;
+ flex-direction: column;
+ position: absolute;
+ left: 46px;
+ bottom: 0;
+ width: 160px;
+ max-height: 442px;
+ background: #191932;
+ border-radius: 8px 8px 8px 8px;
+
+ .panel-title {
+ padding: 0 16px;
+ line-height: 42px;
+
+ font-family: Open Sans, Open Sans;
+ font-weight: 400;
+ font-size: 12px;
+ color: #FFFFFF;
+ text-align: left;
+ font-style: normal;
+ text-transform: none;
+
+ border-bottom: 1px solid rgba(70,70,100,0.5);
+
+ box-sizing: border-box;
+ }
+
+ .panel-content {
+ padding: 0 16px;
+ height: 0;
+ flex: 1;
+ overflow: auto;
+ }
+
+ ::v-deep(.el-tree) {
+ background: transparent;
+ color: #C3C3DD;
+ font-size: 12px;
+
+ .el-tree-node {
+ line-height: 30px !important;
+
+ .el-tree-node__content {
+ display: flex;
+ align-items: center;
+ height: 40px !important;
+ line-height: 40px !important;
+ border-bottom: 1px solid rgba(70,70,100,0.5);
+ box-sizing: border-box;
+ }
+
+ .el-tree-node__children {
+ .el-tree-node__content {
+ border: none;
+ }
+ }
+ }
+
+ }
+}
+
+
+
+.layer-panel :deep(.el-tree-node__label) {
+ color: #C3C3DD;
+}
+
+.layer-panel :deep(.el-tree-node__expand-icon) {
+ order: 3;
+ margin-left: auto;
+}
+
+.layer-panel :deep(.el-tree-node__expand-icon.is-leaf) {
+ visibility: hidden;
+}
+
+.layer-panel :deep(.el-checkbox) {
+ order: 1;
+}
+
+.layer-panel :deep(.el-tree-node__label) {
+ order: 2;
+}
+
+.layer-panel :deep(.el-tree-node__expand-icon:not(.is-leaf) ~ .el-checkbox) {
+ display: none;
+}
+
+.layer-panel :deep(.el-tree-node__content:hover) {
+ background: transparent !important;
+}
+
+.layer-panel :deep(.el-tree-node.is-current > .el-tree-node__content) {
+ background: transparent !important;
+ color: #FFFFFF;
+}
+
+.layer-panel :deep(.el-tree-node.is-current > .el-tree-node__content .el-tree-node__label) {
+ color: #FFFFFF;
+}
+
+.layer-panel :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
+ background-color: #023AFF;
+ border-color: #023AFF;
+}
+
+.layer-panel :deep(.el-checkbox__inner) {
+ background-color: transparent;
+ border-color: #A1A3D4;
+}
+</style>
diff --git a/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue b/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue
index 33235ae..7a4cfcb 100644
--- a/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue
@@ -2,7 +2,7 @@
* @Author : yuan
* @Date : 2026-01-07 15:17:54
* @LastEditors : yuan
- * @LastEditTime : 2026-01-08 17:04:11
+ * @LastEditTime : 2026-01-09 16:54:31
* @FilePath : \applications\drone-command\src\views\dataCockpit\components\RightContainer.vue
* @Description :
* Copyright 2026 OBKoro1, All Rights Reserved.
@@ -59,7 +59,7 @@
import EmptyState from './EmptyState.vue'
import zcsbLogo from '@/assets/images/dataCockpit/zcsb.png'
import fzsbLogo from '@/assets/images/dataCockpit/fzsb.png'
-import { deviceSearchApi } from '@/api/dataCockpit'
+import { deviceSearchApi, deviceStatisticsApi } from '@/api/dataCockpit'
const props = defineProps({
collapsed: {
@@ -68,7 +68,7 @@
}
})
-const emit = defineEmits(['update:collapsed'])
+const emit = defineEmits(['update:collapsed', 'update:onlineDevices'])
const collapsedModel = computed({
get: () => props.collapsed,
@@ -79,13 +79,13 @@
{
id: 1,
name: '侦测设备',
- val: 169,
+ val: 0,
logo: zcsbLogo
},
{
id: 2,
name: '反制设备',
- val: 169,
+ val: 0,
logo: fzsbLogo
}
])
@@ -148,6 +148,7 @@
const res = await deviceSearchApi(params)
const records = res?.data?.data?.records ?? []
counterDeviceStatusList.value = records.map(formatDeviceItem)
+ emit('update:onlineDevices', records)
} finally {
const elapsed = Date.now() - startAt
if (elapsed < minLoadingMs) {
@@ -159,7 +160,26 @@
onMounted(() => {
fetchDeviceList()
+ fetchDeviceStatistics()
})
+
+const normalizeType = (value) => {
+ if (value === 1 || value === '1') return '侦测设备'
+ if (value === 2 || value === '2') return '反制设备'
+ if (value === 'detect') return '侦测设备'
+ if (value === 'counter') return '反制设备'
+ return value
+}
+
+const fetchDeviceStatistics = async () => {
+ const res = await deviceStatisticsApi()
+ const list = res?.data?.data ?? []
+ const map = new Map(list.map((item) => [normalizeType(item.type), item.count]))
+ categoryList.value = categoryList.value.map((item) => ({
+ ...item,
+ val: map.has(item.name) ? map.get(item.name) : item.val
+ }))
+}
</script>
<style lang="scss" scoped>
@@ -247,17 +267,12 @@
position: absolute;
top: 50%;
right: 357px;
- background: rgba(17, 23, 34, 0.8);
- border: 1px solid #333355;
- border-right: none;
- border-radius: 4px 0 0 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
z-index: 10;
- backdrop-filter: blur(5px);
transform: translateY(-50%);
.arrow {
diff --git a/applications/drone-command/src/views/dataCockpit/index.vue b/applications/drone-command/src/views/dataCockpit/index.vue
index f2abbe5..5226ea0 100644
--- a/applications/drone-command/src/views/dataCockpit/index.vue
+++ b/applications/drone-command/src/views/dataCockpit/index.vue
@@ -2,7 +2,7 @@
* @Author : yuan
* @Date : 2026-01-06 16:35:50
* @LastEditors : yuan
- * @LastEditTime : 2026-01-08 09:45:25
+ * @LastEditTime : 2026-01-09 16:34:39
* @FilePath : \applications\drone-command\src\views\dataCockpit\index.vue
* @Description :
* Copyright 2026 OBKoro1, All Rights Reserved.
@@ -10,9 +10,9 @@
-->
<template>
<div class="page-container">
- <MapContainer />
+ <MapContainer :online-devices="onlineDevices" :left-collapsed="leftCollapsed" />
<LeftContainer class="left-container" v-model:activeId="leftActiveId" v-model:collapsed="leftCollapsed" />
- <RightContainer class="right-container" v-model:collapsed="rightCollapsed" />
+ <RightContainer class="right-container" v-model:collapsed="rightCollapsed" v-model:onlineDevices="onlineDevices" />
<CenterContainer @select-warning="onSelectWarning" @select-equipment="onSelectEquipment" />
</div>
</template>
@@ -27,6 +27,7 @@
const leftCollapsed = ref(false);
const rightCollapsed = ref(false);
+ const onlineDevices = ref([]);
const onSelectWarning = (type) => {
leftActiveId.value = type;
@@ -52,7 +53,7 @@
bottom: 0;
width: 401px;
z-index: 9;
- backdrop-filter: blur(5px);
+ backdrop-filter: blur(2px);
::v-deep(.wrapper) {
display: flex;
@@ -63,6 +64,7 @@
padding: 20px;
width: 300px;
background: rgba(0,0,0,0.3);
+ backdrop-filter: blur(16px);
border-radius: 10px 10px 10px 10px;
}
}
--
Gitblit v1.9.3