From 3bd523832dd6e70ca7ad5b886de7895de1d18dc2 Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Thu, 15 Jan 2026 17:32:28 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'
---
applications/drone-command/src/views/areaManage/precinctInfo/index.vue | 18
applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue | 126
applications/drone-command/src/styles/common/cockpit.scss | 537 ++++++-
applications/drone-command/src/views/areaManage/areaStatistics/index.vue | 20
applications/drone-command/src/views/detectionCountermeasure/taskSchedule/index.vue | 28
applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/FormDiaLog.vue | 63
uniapps/work-app/src/utils/websocket.js | 188 ++
applications/drone-command/src/views/dataCockpit/components/EquipmentWarning.vue | 2
applications/drone-command/src/views/dataCockpit/components/RightContainer.vue | 2
applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue | 115 +
applications/drone-command/src/views/basicManage/deviceScrap/index.vue | 24
applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/index.vue | 204 +-
applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/FormDiaLog.vue | 126 +
applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue | 110
applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue | 2
applications/drone-command/src/views/basicManage/maintainRecord/index.vue | 20
applications/drone-command/src/views/recordManage/alarmRecords/index.vue | 20
applications/drone-command/src/views/recordManage/historyTracks/index.vue | 14
uniapps/work-app/src/utils/common/index.js | 2
applications/drone-command/src/views/dataCockpit/components/RealWarning.vue | 2
applications/drone-command/src/views/job/components/TaskIntermediateContent/AddTask.vue | 2
applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue | 2
applications/drone-command/src/views/resource/components/spotDetails.vue | 2
applications/drone-command/env/.env.development | 4
applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue | 80
applications/drone-command/src/views/dataCenter/components/dataCenterMap.vue | 2
applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue | 257 +-
applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue | 23
applications/drone-command/src/page/index/top/index.vue | 13
applications/drone-command/src/views/areaManage/sceneConfig/index.vue | 22
uniapps/work-app/src/hooks/useGlobalWS.js | 239 ++-
applications/drone-command/src/views/tickets/ticketComponent/DispatchDialog.vue | 2
uniapps/work-app/src/subPackages/voiceCallDetail/index.vue | 2
applications/drone-command/src/views/tickets/ticket.vue | 2
applications/drone-command/src/store/modules/user.js | 4
applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue | 166 +-
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue | 265 +--
applications/drone-command/src/views/basicManage/maintainRecord/FormDiaLog.vue | 2
applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue | 1126 +++++++++------
applications/drone-command/src/views/layerManagement/components/rightEdit.vue | 2
applications/drone-command/src/views/areaManage/defenseZone/index.vue | 16
uniapps/work-app/src/pages/login/index.vue | 53
applications/drone-command/src/page/index/top/top-qna.vue | 2
applications/drone-command/src/views/job/components/executeNestDetails.vue | 2
applications/drone-command/src/views/job/components/JobRelatedEvents.vue | 2
applications/drone-command/src/views/aICrudTemplate/index.vue | 2
applications/drone-command/src/views/areaManage/partition/index.vue | 10
applications/drone-command/src/views/basicManage/deviceStock/index.vue | 4
applications/drone-command/src/views/layerManagement/components/leftList.vue | 2
applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue | 327 ++--
applications/drone-command/src/views/dataCockpit/components/HistoryWarning.vue | 2
51 files changed, 2,710 insertions(+), 1,552 deletions(-)
diff --git a/applications/drone-command/env/.env.development b/applications/drone-command/env/.env.development
index 73ebb7a..fb3d1c2 100644
--- a/applications/drone-command/env/.env.development
+++ b/applications/drone-command/env/.env.development
@@ -2,7 +2,7 @@
# @Author : yuan
# @Date : 2026-01-07 14:58:30
# @LastEditors : yuan
- # @LastEditTime : 2026-01-13 16:28:33
+ # @LastEditTime : 2026-01-15 10:25:10
# @FilePath : \applications\drone-command\env\.env.development
# @Description :
# Copyright 2026 OBKoro1, All Rights Reserved.
@@ -16,7 +16,7 @@
#开发环境代理地址(推荐本地新建文件 .env.development.local 来进行覆盖)
# VITE_APP_URL = https://wrj.shuixiongit.com/api
-VITE_APP_URL= http://192.168.1.168
+VITE_APP_URL= http://192.168.1.204
#新大屏地址
VITE_APP_DASHBOARD_URL = 'https://wrj.shuixiongit.com/command-center-dashboard/'
diff --git a/applications/drone-command/src/page/index/top/index.vue b/applications/drone-command/src/page/index/top/index.vue
index 84906ca..b3e7f6a 100644
--- a/applications/drone-command/src/page/index/top/index.vue
+++ b/applications/drone-command/src/page/index/top/index.vue
@@ -8,12 +8,13 @@
<div class="top-bar__right">
<div class="top-user">
<div class="icon-box">
- <img class="gateway" @click="jumpMH" src="@/assets/images/mh.png" alt="进入门户" title="进入门户">
+ <img class="gateway" @click="jumpMH" src="@/assets/images/mh.png" alt="进入门户" title="进入门户">
</div>
<el-dropdown>
<span class="el-dropdown-link">
- <img class="top-bar__img" :src="userInfo.avatar" alt="" />
+ <!-- <img v-if="userInfo.avatar" class="top-bar__img top-bar__user__img" :src="userInfo.avatar" alt="" /> -->
+ <img class="top-bar__img" :src="defaultAva" alt="" />
</span>
<template #dropdown>
@@ -36,6 +37,8 @@
</template>
<script>
+import defaultAva from '@/assets/images/defaultava.png'
+
import logo from '@/assets/images/topContainer/logo.png'
import { mapGetters } from 'vuex'
@@ -67,6 +70,7 @@
data () {
return {
logoUrl: logo,
+ defaultAva: defaultAva
}
},
filters: {},
@@ -190,6 +194,11 @@
height: 21px;
}
+.top-bar__user__img {
+ width: 24px;
+ height: 24px;
+}
+
.el-dropdown-link {
cursor: pointer;
color: #606266;
diff --git a/applications/drone-command/src/page/index/top/top-qna.vue b/applications/drone-command/src/page/index/top/top-qna.vue
index 18c4de6..68e0def 100644
--- a/applications/drone-command/src/page/index/top/top-qna.vue
+++ b/applications/drone-command/src/page/index/top/top-qna.vue
@@ -25,7 +25,7 @@
const systemupdateFilePath = ref(null);
const rumenPath = ref(null);
const fileData = ref([]);
-const loading = ref(false);
+const loading = ref(true);
const getlistDataAPI = async () => {
loading.value = true;
try {
diff --git a/applications/drone-command/src/store/modules/user.js b/applications/drone-command/src/store/modules/user.js
index 3f6ba06..371e9e7 100644
--- a/applications/drone-command/src/store/modules/user.js
+++ b/applications/drone-command/src/store/modules/user.js
@@ -8,7 +8,6 @@
import { setStore, getStore } from '@/utils/store';
import { validatenull } from '@/utils/validate';
import { deepClone } from '@/utils/util';
-import defaultAva from '@/assets/images/defaultava.png'
import {
loginByUsername,
loginBySocial,
@@ -336,9 +335,6 @@
if (validatenull(userInfo.user_id) && validatenull(userInfo.account)) {
state.userInfo = { user_name: 'unauth', role_name: 'unauth' };
} else {
- if (validatenull(userInfo.avatar)) {
- userInfo.avatar = defaultAva;
- }
state.userInfo = userInfo;
}
setStore({ name: 'userInfo', content: state.userInfo });
diff --git a/applications/drone-command/src/styles/common/cockpit.scss b/applications/drone-command/src/styles/common/cockpit.scss
index e8c8651..00b878e 100644
--- a/applications/drone-command/src/styles/common/cockpit.scss
+++ b/applications/drone-command/src/styles/common/cockpit.scss
@@ -53,9 +53,8 @@
.ztzf-data-cockpit-date-picker-popper {
background: #161B2C !important;
- border: 1px solid !important;
+ border: none !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 {
@@ -73,6 +72,13 @@
.el-picker-panel__body {
background: #161B2C !important;
color: #fff !important;
+
+ .el-date-range-picker__time-header {
+ .el-input__wrapper {
+ box-shadow: none;
+ background: #2E2E48 !important;
+ }
+ }
.el-date-picker__time-header {
border: none;
@@ -107,7 +113,7 @@
}
.el-time-panel__footer {
- background: #012350 !important;
+ background: #161B2C !important;
border-top: 1px solid rgba(71, 157, 255, 0.3) !important;
.el-button {
@@ -173,7 +179,7 @@
td.start-date span,
td.start-date span,
td.end-date span {
- background-color: #012350 !important;
+ background-color: #161B2C !important;
}
td.in-range div,
@@ -197,7 +203,7 @@
}
.el-picker-panel__sidebar {
- background: #012350;
+ background: #161B2C;
color: #fff;
.el-picker-panel__shortcut {
@@ -207,7 +213,7 @@
.el-picker-panel__body {
.el-input.is-disabled .el-input__wrapper {
- background: #012350;
+ background: #161B2C;
box-shadow: 0 0 0 0.1rem#409eff;
color: #fff;
@@ -218,11 +224,11 @@
.el-button.is-disabled,
.el-button.is-disabled:hover {
- background: #012350;
+ background: #161B2C;
}
.el-input__wrapper {
- background: #012350;
+ background: #161B2C;
box-shadow: 0 0 0 0.1rem#409eff;
color: #fff;
@@ -233,18 +239,18 @@
}
.el-picker-panel__footer {
- background: #012350;
+ background: #161B2C;
color: #fff;
border-top: none;
.el-button--small {
box-shadow: 0 0 0 0.1rem#409eff;
- background-color: #012350;
+ background-color: #161B2C;
color: #fff;
border: none;
&:hover {
- background-color: #012350;
+ background-color: #161B2C;
color: #fff;
border-color: #409eff;
}
@@ -258,6 +264,7 @@
}
.el-select__wrapper {
+ padding: 0 8px;
background: #2E2E48;
box-shadow: none !important;
border: none;
@@ -283,11 +290,11 @@
}
.el-select .el-input__wrapper {
- background-color: #012350 !important;
+ background-color: #161B2C !important;
}
.el-input__wrapper {
- --el-input-text-color: #012350 !important;
+ --el-input-text-color: #161B2C !important;
}
}
@@ -698,6 +705,11 @@
.el-button {
min-width: 88px;
}
+
+ .el-button.is-disabled, .el-button.is-disabled:hover {
+ background: #1A2652;
+ border: none;
+ }
}
.ztzf-table-container {
@@ -713,82 +725,82 @@
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 {
- height: 40px;
- border-bottom: 1px solid #323241;
- background: transparent !important;
-
- .cell {
- padding: 0;
- padding-left: 16px;
- 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 {
- padding: 9px 0;
- height: 56px;
- border-bottom: 1px solid #323241;
- background: transparent !important;
-
- .cell {
- padding: 0;
- padding-left: 16px;
- font-family: Source Han Sans CN, Source Han Sans CN;
- font-weight: 400;
- font-size: 14px;
- color: #D2D2E7;
- line-height: 18px;
- font-style: normal;
- text-transform: none;
- }
- }
-
- td.operation-btns .cell {
- display: flex;
- flex-wrap: nowrap;
- gap: 12px;
- /* 按钮间距(推荐) */
- white-space: nowrap;
- /* 双保险 */
-
- a {
- min-width: 28px;
- color: #6A89FF;
-
- &:last-child {
- margin-right: 0;
- }
- }
- }
- }
}
.ztzf-table-content-bg {
background: #1A1A2A;
+ }
+}
+
+.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 {
+ height: 40px;
+ border-bottom: 1px solid #323241;
+ background: transparent !important;
+
+ .cell {
+ padding: 0;
+ padding-left: 16px;
+ 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 {
+ padding: 9px 0;
+ height: 56px;
+ border-bottom: 1px solid #323241;
+ background: transparent !important;
+
+ .cell {
+ padding: 0;
+ padding-left: 16px;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 400;
+ font-size: 14px;
+ color: #D2D2E7;
+ line-height: 18px;
+ font-style: normal;
+ text-transform: none;
+ }
+ }
+
+ td.operation-btns .cell {
+ display: flex;
+ flex-wrap: nowrap;
+ gap: 12px;
+ /* 按钮间距(推荐) */
+ white-space: nowrap;
+ /* 双保险 */
+
+ a {
+ min-width: 28px;
+ color: #6A89FF;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
}
}
@@ -862,6 +874,7 @@
}
.ztzf-page-view-dialog {
+ padding: 30px;
display: flex;
flex-direction: column;
width: 770px;
@@ -947,7 +960,6 @@
}
.dialog-form {
-
.el-form-item {
display: flex;
align-items: center;
@@ -968,7 +980,7 @@
.el-form-item__content {
display: flex;
align-items: center;
-
+
.el-input {
width: 0;
flex: 1;
@@ -989,6 +1001,44 @@
background: #2E2E48 !important;
border: none;
box-shadow: none;
+ }
+
+ .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;
+ }
+ }
}
}
@@ -1069,3 +1119,316 @@
border-color: #2B2B4C !important;
color: #FFFFFF !important;
}
+
+.ztzf-page-map-view-dialog {
+ padding: 30px;
+ display: flex;
+ flex-direction: column;
+ width: 80%;
+ height: 812px;
+ background: #1A1A2A;
+ border-radius: 6px 6px 6px 6px;
+ border: 1px solid #2E2E46;
+
+ .el-dialog__header,
+ .el-dialog__footer {
+ margin: 0;
+ padding: 0;
+ }
+
+ .el-dialog__footer {
+ .el-button {
+ margin-top: 30px;
+ }
+ }
+
+ .el-dialog__body {
+ display: flex;
+ flex-direction: column;
+ height: 0;
+ flex: 1;
+
+ .dialog-container {
+ display: flex;
+ height: 0;
+ flex: 1;
+
+ .left-container {
+ width: 0;
+ flex: 1;
+ height: 100%;
+
+ .leftMap {
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ .right-container {
+ display: flex;
+ flex-direction: column;
+ width: 344px;
+ height: 100%;
+
+ .header {
+ padding: 0 16px;
+ padding-bottom: 16px;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 400;
+ font-size: 18px;
+ color: #FFFFFF;
+ text-align: left;
+ font-style: normal;
+ text-transform: none;
+ }
+
+ .dialog-container {
+ padding: 0 16px;
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .detail-title {
+ margin-bottom: 30px;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 500;
+ font-size: 18px;
+ color: #FFFFFF;
+ text-align: left;
+ font-style: normal;
+ text-transform: none;
+ }
+
+ .el-row {
+ .el-col {
+ margin-bottom: 20px;
+ display: flex;
+ align-items: center;
+
+ .label {
+ width: 112px;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 500;
+ font-size: 14px;
+ color: #D4D5D7;
+ line-height: 22px;
+ text-align: right;
+ font-style: normal;
+ text-transform: none;
+ }
+
+ .val {
+ width: 0;
+ flex: 1;
+ margin-left: 12px;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 400;
+ font-size: 14px;
+ color: #9E9EBA;
+ text-align: left;
+ font-style: normal;
+ text-transform: none;
+ }
+ }
+ }
+
+
+ .ztzf-table-container {
+ height: 360px;
+
+ .ztzf-table-pagination {
+ justify-content: center;
+ }
+ }
+ }
+
+ .dialog-form {
+ padding-left: 16px;
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .el-form-item {
+ display: flex;
+ align-items: center;
+
+ .el-form-item__label {
+ display: flex;
+ align-items: center;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 500;
+ font-size: 14px;
+ color: #D4D5D7;
+ line-height: 22px;
+ text-align: right;
+ font-style: normal;
+ text-transform: none;
+ }
+
+ .el-form-item__content {
+ display: flex;
+ align-items: center;
+
+ .el-input,
+ .el-input-number {
+ width: 0;
+ flex: 1;
+ }
+
+ .el-textarea {
+
+ .el-textarea__inner {
+ color: #fff;
+ background: #2E2E48 !important;
+ border: none;
+ box-shadow: none;
+ }
+ }
+
+ .el-button {
+ color: #fff;
+ background: #2E2E48 !important;
+ border: none;
+ box-shadow: none;
+ }
+
+ >div.val {
+ color: #fff;
+ }
+
+ .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;
+ }
+ }
+ }
+ }
+
+ .el-input {
+ .el-input__wrapper {
+ background: #2E2E48 !important;
+
+ .el-input__inner {
+ color: #ffffff;
+ }
+ }
+ }
+ }
+
+ .search-table-container {
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .search-box {
+ display: flex;
+ margin-bottom: 18px;
+
+ .label {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+
+ padding-right: 12px;
+ width: 96px;
+
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 500;
+ font-size: 14px;
+ color: #D4D5D7;
+ font-style: normal;
+ text-transform: none;
+
+ box-sizing: border-box;
+ }
+
+ .el-input {
+ width: 0;
+ flex: 1;
+
+ .el-input__wrapper {
+ background: #2E2E48 !important;
+
+ .el-input__inner {
+ color: #ffffff;
+ }
+ }
+ }
+ }
+
+ .el-form-item {
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .el-form-item__content {
+ width: 100%;
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .el-table {
+ height: 0;
+ flex: 1;
+ }
+ }
+ }
+ }
+ }
+
+ .footer {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .el-button {
+ margin-left: 16px;
+ width: 96px;
+ height: 36px;
+
+ &:first-child {
+ margin-left: 0;
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/applications/drone-command/src/views/aICrudTemplate/index.vue b/applications/drone-command/src/views/aICrudTemplate/index.vue
index 2e596b9..ac5fe45 100644
--- a/applications/drone-command/src/views/aICrudTemplate/index.vue
+++ b/applications/drone-command/src/views/aICrudTemplate/index.vue
@@ -110,7 +110,7 @@
})
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const selectedIds = ref([]) // 勾选的设备ID列表
const queryParamsRef = ref(null) // 查询表单实例
diff --git a/applications/drone-command/src/views/areaManage/areaStatistics/index.vue b/applications/drone-command/src/views/areaManage/areaStatistics/index.vue
index bc667e2..0632e94 100644
--- a/applications/drone-command/src/views/areaManage/areaStatistics/index.vue
+++ b/applications/drone-command/src/views/areaManage/areaStatistics/index.vue
@@ -32,23 +32,23 @@
<div class="ztzf-table-content ztzf-table-content-bg">
<el-table class="ztzf-data-cockpit-table" :data="list">
<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
- <el-table-column prop="areaName" show-overflow-tooltip width="150" label="区域名称" />
- <el-table-column prop="areaType" show-overflow-tooltip width="130" label="区域类型">
+ <el-table-column prop="areaName" show-overflow-tooltip label="区域名称" />
+ <el-table-column prop="areaType" show-overflow-tooltip label="区域类型">
<template v-slot="{ row }">
{{ getDictLabel(row.areaType, dictObj.areaType) }}
</template>
</el-table-column>
- <el-table-column prop="sceneName" show-overflow-tooltip width="150" label="关联场景" />
- <el-table-column prop="sceneType" show-overflow-tooltip width="130" label="场景类型">
+ <el-table-column prop="sceneName" show-overflow-tooltip label="关联场景" />
+ <el-table-column prop="sceneType" show-overflow-tooltip label="场景类型">
<template v-slot="{ row }">
{{ getDictLabel(row.sceneType, dictObj.sceneType) }}
</template>
</el-table-column>
- <el-table-column prop="deviceCount" show-overflow-tooltip width="110" label="设备数量" />
- <el-table-column prop="alarmCount" show-overflow-tooltip width="110" label="告警次数" />
- <el-table-column prop="counterCount" show-overflow-tooltip width="120" label="侦测反制次数" />
- <el-table-column prop="counterSuccessCount" show-overflow-tooltip width="130" label="反制成功次数" />
- <el-table-column prop="controlEffect" show-overflow-tooltip width="110" label="管控效果" />
+ <el-table-column prop="deviceCount" show-overflow-tooltip label="设备数量" />
+ <el-table-column prop="alarmCount" show-overflow-tooltip label="告警次数" />
+ <el-table-column prop="counterCount" show-overflow-tooltip label="侦测反制次数" />
+ <el-table-column prop="counterSuccessCount" show-overflow-tooltip label="反制成功次数" />
+ <el-table-column prop="controlEffect" show-overflow-tooltip label="管控效果" />
<el-table-column label="操作" class-name="operation-btns">
<template v-slot="{ row }">
<el-link @click="openForm(row)">查看</el-link>
@@ -87,7 +87,7 @@
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const queryParamsRef = ref(null) // 查询表单实例
const dialogRef = ref(null) // 弹框实例
diff --git a/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue
index ed069a0..7018438 100644
--- a/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue
@@ -1,64 +1,88 @@
<template>
- <el-dialog v-model="visible" :title="titleEnum[dialogMode]" :close-on-click-modal="false" width="80%">
- <div class="bodyBox">
- <div class="leftMap ztzf-cesium" id="mapContainer"></div>
- <div class="rightInfo">
- <div v-if="readonly">
- <div>防区名称: {{ formData.zoneName }}</div>
- <div>防区面积: {{ formatZoneArea(formData.zoneArea) }}</div>
- <el-table :data="sceneList" row-key="id">
- <el-table-column prop="sceneName" label="场景名称" />
- <el-table-column prop="sceneType" label="场景类型">
- <template v-slot="{ row }">
- {{ getDictLabel(row.sceneType, dictObj.sceneType) }}
- </template>
- </el-table-column>
- </el-table>
+ <el-dialog class="ztzf-page-map-view-dialog" v-model="visible" :show-close="false" :close-on-click-modal="false">
+ <div class="dialog-container">
+ <div class="left-container">
+ <div class="leftMap ztzf-cesium" id="mapContainer"></div>
+ </div>
+
+ <div class="right-container">
+ <div class="header">
+ {{ titleEnum[dialogMode] }}
</div>
- <el-form v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly" label-width="100px">
+
+ <div class="dialog-container" v-if="readonly">
+ <div class="detail-title">防区详情</div>
<el-row>
<el-col :span="24">
- <el-form-item label="防区名称" prop="zoneName">
- <el-input v-model="formData.zoneName" maxlength="50" placeholder="请输入" clearable />
- </el-form-item>
+ <div class="label">防区名称</div>
+ <div class="val">{{ formData.zoneName }}</div>
</el-col>
<el-col :span="24">
- <el-form-item label="防区面积" prop="zoneArea">{{ formatZoneArea(formData.zoneArea) }}</el-form-item>
- </el-col>
- <el-col :span="24">
- <div>
- <div>
- 关联场景
- <el-input v-model="searchName" placeholder="请输入" clearable></el-input>
- </div>
- <el-form-item label="关联场景" prop="defenseSceneIds" label-width="0">
- <el-table
- ref="sceneTableRef"
- :data="sceneList.filter(item => item.sceneName.includes(searchName))"
- row-key="id"
- @selection-change="handleSceneSelectionChange"
- >
- <el-table-column type="selection" width="55" :reserve-selection="true" />
- <el-table-column prop="sceneName" label="场景名称" />
- <el-table-column prop="sceneType" label="场景类型">
- <template v-slot="{ row }">
- {{ getDictLabel(row.sceneType, dictObj.sceneType) }}
- </template>
- </el-table-column>
- </el-table>
- </el-form-item>
- </div>
+ <div class="label">防区面积</div>
+ <div class="val">{{ formatZoneArea(formData.zoneArea) }}</div>
</el-col>
</el-row>
+
+ <div class="detail-title">关联场景</div>
+ <div class="ztzf-table-container">
+ <div class="ztzf-table-content">
+ <el-table class="ztzf-data-cockpit-table" :data="sceneList" row-key="id">
+ <el-table-column prop="sceneName" label="场景名称" />
+ <el-table-column prop="sceneType" label="场景类型">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.sceneType, dictObj.sceneType) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+ </div>
+
+ <el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly"
+ label-width="96px">
+ <el-form-item label="防区名称" prop="zoneName">
+ <el-input class="ztzf-data-cockpit-search-input" v-model="formData.zoneName" maxlength="50"
+ placeholder="请输入" clearable />
+ </el-form-item>
+ <el-form-item label="防区面积" prop="zoneArea">
+ <div class="val">{{ formatZoneArea(formData.zoneArea) }}</div>
+ </el-form-item>
+
+ <div class="search-table-container">
+ <div class="search-box">
+ <div class="label">
+ 关联场景
+ </div>
+
+ <el-input class="ztzf-data-cockpit-search-input" v-model="searchName" placeholder="请输入"
+ clearable></el-input>
+ </div>
+
+ <el-form-item prop="defenseSceneIds" label-width="0">
+ <el-table class="ztzf-data-cockpit-table" ref="sceneTableRef"
+ :data="sceneList.filter(item => item.sceneName.includes(searchName))" row-key="id"
+ @selection-change="handleSceneSelectionChange">
+ <el-table-column type="selection" width="55" :reserve-selection="true" />
+ <el-table-column prop="sceneName" label="场景名称" />
+ <el-table-column prop="sceneType" label="场景类型">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.sceneType, dictObj.sceneType) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form-item>
+ </div>
</el-form>
+
+ <div class="footer">
+ <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting"
+ :disabled="submitting" @click="handleSubmit">
+ 确定
+ </el-button>
+ </div>
</div>
</div>
- <template #footer>
- <el-button @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
- <el-button v-if="!readonly" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit">
- 确定
- </el-button>
- </template>
</el-dialog>
</template>
@@ -106,12 +130,12 @@
}
// 关闭弹框
-function handleCancel() {
+function handleCancel () {
visible.value = false
}
// 提交新增/编辑
-async function handleSubmit() {
+async function handleSubmit () {
const isValid = await formRef.value?.validate().catch(() => false)
if (!isValid) return
if (!pointList.length) {
@@ -134,14 +158,14 @@
}
// 加载详情
-async function loadDetail() {
+async function loadDetail () {
if (!formData.value.id) return
const res = await fwDefenseZoneDetailApi({ id: formData.value.id })
formData.value = res?.data?.data ?? initForm()
}
// 获取场景列表
-async function getSceneList() {
+async function getSceneList () {
const res = await fwDefenseSceneListApi({ filterSelected: 1, zoneId: formData.value.id })
sceneList.value = res?.data?.data ?? []
if (readonly.value) {
@@ -151,19 +175,19 @@
}
// 获取场景ID列表
-function getSceneIdList() {
+function getSceneIdList () {
if (!formData.value.defenseSceneIds) return []
return formData.value.defenseSceneIds.split(',')
}
// 关联场景变更
-function handleSceneSelectionChange(rows) {
+function handleSceneSelectionChange (rows) {
selectedSceneRows.value = rows
formData.value.defenseSceneIds = rows.map(item => item.id).join(',')
}
// 同步关联场景
-function syncSceneSelection() {
+function syncSceneSelection () {
if (!sceneTableRef.value) return
sceneTableRef.value.clearSelection()
const ids = getSceneIdList()
@@ -178,13 +202,13 @@
}
// 格式化防区面积
-function formatZoneArea(value) {
+function formatZoneArea (value) {
if (value == null || value === '') return ''
return `${value}k㎡`
}
// 初始化地图实例
-function initMap() {
+function initMap () {
const publicCesiumInstance = new PublicCesium({
dom: 'mapContainer',
flatMode: false,
@@ -210,14 +234,14 @@
}
// 新增模式绘制
-function addPolygon() {
+function addPolygon () {
drawPolygonExample = new DrawPolygon(viewer)
drawPolygonExample.initHandler(viewer)
drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
}
// 编辑面
-function editPolygon() {
+function editPolygon () {
if (!formData.value?.geom) return
pointList = geomAnalysis(formData.value.geom)
drawPolygonExample?.destroy()
@@ -232,7 +256,7 @@
}
// 查看面
-function viewPolygon() {
+function viewPolygon () {
if (!formData.value?.geom) return
pointList = geomAnalysis(formData.value.geom)
const result = pointList.map(item => [item.longitude, item.latitude]).flat()
@@ -250,7 +274,7 @@
}
// 打开弹框
-async function open({ mode, row } = {}) {
+async function open ({ mode, row } = {}) {
dialogMode.value = mode || 'add'
formData.value = dialogMode.value === 'add' ? initForm() : row
selectedSceneRows.value = []
@@ -277,18 +301,4 @@
})
</script>
-<style scoped lang="scss">
-.bodyBox {
- display: flex;
- height: 600px;
- .leftMap {
- width: 70%;
- height: 100%;
- }
- .rightInfo {
- width: 30%;
- height: 100%;
- overflow: auto;
- }
-}
-</style>
+<style scoped lang="scss"></style>
diff --git a/applications/drone-command/src/views/areaManage/defenseZone/index.vue b/applications/drone-command/src/views/areaManage/defenseZone/index.vue
index 239421e..aa8fe67 100644
--- a/applications/drone-command/src/views/areaManage/defenseZone/index.vue
+++ b/applications/drone-command/src/views/areaManage/defenseZone/index.vue
@@ -14,7 +14,7 @@
<div class="ztzf-table-toolbar">
<el-button :icon="Plus" color="#284FE3" type="primary" @click="openForm('add')">新增</el-button>
- <el-button type="danger" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
+ <el-button :icon="Delete" color="#1A2652" type="primary" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
</div>
<div class="ztzf-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
@@ -22,11 +22,11 @@
<el-table class="ztzf-data-cockpit-table" :data="list" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="46" />
<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
- <el-table-column prop="zoneName" show-overflow-tooltip width="150" label="防区名称" />
- <el-table-column prop="zoneArea" show-overflow-tooltip width="130" label="防区面积(k㎡)" />
- <el-table-column prop="sceneCount" show-overflow-tooltip width="120" label="场景数量" />
- <el-table-column prop="areaCount" show-overflow-tooltip width="120" label="区域数量" />
- <el-table-column prop="deviceCount" show-overflow-tooltip width="120" label="设备数量" />
+ <el-table-column prop="zoneName" show-overflow-tooltip label="防区名称" />
+ <el-table-column prop="zoneArea" show-overflow-tooltip label="防区面积(k㎡)" />
+ <el-table-column prop="sceneCount" show-overflow-tooltip label="场景数量" />
+ <el-table-column prop="areaCount" show-overflow-tooltip label="区域数量" />
+ <el-table-column prop="deviceCount" show-overflow-tooltip label="设备数量" />
<el-table-column label="操作" class-name="operation-btns">
<template v-slot="{ row }">
<el-link @click="openForm('view', row)">查看</el-link>
@@ -49,7 +49,7 @@
</template>
<script setup>
-import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
+import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue'
import { nextTick, onMounted, provide, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fwDefenseZonePageApi, fwDefenseZoneRemoveApi } from './defenseZoneApi'
@@ -66,7 +66,7 @@
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const selectedIds = ref([]) // 勾选的ID列表
const queryParamsRef = ref(null) // 查询表单实例
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
index 9835b94..e23df92 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -1,10 +1,17 @@
<template>
- <el-dialog class="ztzf-page-view-dialog" v-model="visible" :title="titleEnum[dialogMode]"
- :close-on-click-modal="false" width="80%">
- <div class="bodyBox">
- <div class="leftMap ztzf-cesium" id="mapContainer"></div>
- <div class="rightInfo">
- <div class="detail-container" v-if="readonly">
+ <el-dialog class="ztzf-page-map-view-dialog" v-model="visible" :show-close="false"
+ :close-on-click-modal="false">
+ <div class="dialog-container">
+ <div class="left-container">
+ <div class="leftMap ztzf-cesium" id="mapContainer"></div>
+ </div>
+
+ <div class="right-container">
+ <div class="header">
+ {{ titleEnum[dialogMode] }}
+ </div>
+
+ <div class="dialog-container" v-if="readonly">
<div class="detail-title">区域详情</div>
<el-row>
<el-col :span="24">
@@ -60,126 +67,90 @@
</div>
</div>
</div>
+
<el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly"
- label-width="100px">
- <el-row>
- <el-col :span="24">
- <el-form-item label="区域名称" prop="areaName">
- <el-input class="ztzf-data-cockpit-search-input" v-model="formData.areaName"
- maxlength="50" placeholder="请输入" clearable />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="区域位置" prop="longitude">
- <div>{{ formatLocation(formData) }}</div>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="区域面积" prop="areaSize">{{ formData.areaSize || '-' }}k㎡</el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="区域类型" prop="areaType">
- <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
- v-model="formData.areaType" placeholder="请选择" clearable>
- <el-option
- v-for="item in dictObj.areaType"
- :key="item.dictKey"
- :label="item.dictValue"
- :value="item.dictKey"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="响应机制" prop="responseMechanism">
- <el-input class="ztzf-data-cockpit-search-input" v-model="formData.responseMechanism"
- maxlength="200" placeholder="请输入" clearable />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="触发条件" prop="triggerCondition">
- <el-input class="ztzf-data-cockpit-search-input" v-model="formData.triggerCondition"
- maxlength="200" placeholder="请输入" clearable />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="管控级别" prop="controlLevel">
- <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
- v-model="formData.controlLevel" placeholder="请选择" clearable>
- <el-option
- v-for="item in dictObj.controlLevel"
- :key="item.dictKey"
- :label="item.dictValue"
- :value="item.dictKey"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="可飞行时段" prop="flyDateStart">
- <el-date-picker
- class="ztzf-data-cockpit-date-picker"
- popper-class="ztzf-data-cockpit-date-picker-popper"
- v-model="flyDateRange"
- type="datetimerange"
- range-separator="至"
- start-placeholder="开始时间"
- end-placeholder="结束时间"
- value-format="YYYY-MM-DD HH:mm:ss"
- clearable
- />
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="关联派出所" prop="policeStationId">
- <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
- v-model="formData.policeStationId" placeholder="请选择" clearable>
- <el-option
- v-for="item in policeStationOptions"
- :key="item.id"
- :label="item.stationName"
- :value="item.id"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <div>
- <div>
- 关联设备
- <el-input class="ztzf-data-cockpit-search-input" v-model="searchName" placeholder="请输入"
- clearable></el-input>
- </div>
- <el-form-item prop="deviceIds" label-width="0">
- <el-table
- class="ztzf-data-cockpit-table"
- ref="deviceTableRef"
- :data="deviceOptions.filter(item => item.deviceName.includes(searchName))"
- row-key="id"
- @selection-change="handleDeviceSelectionChange"
- >
- <el-table-column type="selection" width="55" :reserve-selection="true" />
- <el-table-column prop="deviceName" label="设备名称" />
- <el-table-column prop="deviceType" label="类型">
- <template v-slot="{ row }">
- {{ getDictLabel(row.deviceType, dictObj.deviceType) }}
- </template>
- </el-table-column>
- </el-table>
- </el-form-item>
+ label-width="96px">
+ <el-form-item label="区域名称" prop="areaName">
+ <el-input class="ztzf-data-cockpit-search-input" v-model="formData.areaName" maxlength="50"
+ placeholder="请输入" clearable />
+ </el-form-item>
+ <el-form-item label="区域位置" prop="longitude">
+ <div class="val">{{ formatLocation(formData) }}</div>
+ </el-form-item>
+ <el-form-item label="区域面积" prop="areaSize">
+ <div class="val">{{ formData.areaSize || '-' }}k㎡</div>
+ </el-form-item>
+ <el-form-item label="区域类型" prop="areaType">
+ <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.areaType" placeholder="请选择" clearable>
+ <el-option v-for="item in dictObj.areaType" :key="item.dictKey" :label="item.dictValue"
+ :value="item.dictKey" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="响应机制" prop="responseMechanism">
+ <el-input class="ztzf-data-cockpit-search-input" v-model="formData.responseMechanism"
+ maxlength="200" placeholder="请输入" clearable />
+ </el-form-item>
+ <el-form-item label="触发条件" prop="triggerCondition">
+ <el-input class="ztzf-data-cockpit-search-input" v-model="formData.triggerCondition"
+ maxlength="200" placeholder="请输入" clearable />
+ </el-form-item>
+ <el-form-item label="管控级别" prop="controlLevel">
+ <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.controlLevel" placeholder="请选择" clearable>
+ <el-option v-for="item in dictObj.controlLevel" :key="item.dictKey" :label="item.dictValue"
+ :value="item.dictKey" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="可飞行时段" prop="flyDateStart">
+ <el-date-picker class="ztzf-data-cockpit-date-picker"
+ popper-class="ztzf-data-cockpit-date-picker-popper" v-model="flyDateRange"
+ type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"
+ value-format="YYYY-MM-DD HH:mm:ss" clearable />
+ </el-form-item>
+ <el-form-item label="关联派出所" prop="policeStationId">
+ <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.policeStationId" placeholder="请选择" clearable>
+ <el-option v-for="item in policeStationOptions" :key="item.id" :label="item.stationName"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+
+ <div class="search-table-container">
+ <div class="search-box">
+ <div class="label">
+ 关联设备
</div>
- </el-col>
- </el-row>
+
+ <el-input class="ztzf-data-cockpit-search-input" v-model="searchName" placeholder="请输入"
+ clearable></el-input>
+ </div>
+
+ <el-form-item prop="deviceIds" label-width="0">
+ <el-table class="ztzf-data-cockpit-table" ref="deviceTableRef"
+ :data="deviceOptions.filter(item => item.deviceName.includes(searchName))" row-key="id"
+ @selection-change="handleDeviceSelectionChange">
+ <el-table-column type="selection" width="55" :reserve-selection="true" />
+ <el-table-column prop="deviceName" label="设备名称" />
+ <el-table-column prop="deviceType" label="类型">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.deviceType, dictObj.deviceType) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form-item>
+ </div>
</el-form>
+
+ <div class="footer">
+ <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting"
+ :disabled="submitting" @click="handleSubmit">
+ 确定
+ </el-button>
+ </div>
</div>
</div>
- <template #footer>
- <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
- <el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting" :disabled="submitting"
- @click="handleSubmit">
- 确定
- </el-button>
- </template>
</el-dialog>
</template>
@@ -218,7 +189,7 @@
const dialogMode = ref('add') // 弹框模式
const submitting = ref(false) // 提交中
const readonly = computed(() => dialogMode.value === 'view')
-const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
+const titleEnum = ref({ edit: '编辑场景', view: '查看场景', add: '新增场景' })
const flyDateRange = ref([]) // 飞行时间
const deviceTableRef = ref(null)
const searchName = ref('')
@@ -243,12 +214,12 @@
}
// 关闭弹框
-function handleCancel() {
+function handleCancel () {
visible.value = false
}
// 提交新增/编辑
-async function handleSubmit() {
+async function handleSubmit () {
const isValid = await formRef.value?.validate().catch(() => false)
if (!isValid) return
submitting.value = true
@@ -273,7 +244,7 @@
)
// 加载详情
-async function loadDetail() {
+async function loadDetail () {
if (!formData.value.id) return
const res = await fwAreaDivideDetailApi({ id: formData.value.id })
formData.value = res?.data?.data ?? initForm()
@@ -281,19 +252,19 @@
}
// 格式化区域位置
-function formatLocation(row) {
+function formatLocation (row) {
if (row?.longitude == null || row?.latitude == null) return ''
return `${_.round(row.longitude, 6)}, ${_.round(row.latitude, 6)}`
}
// 格式化可飞行时间段
-function formatFlyDate(row) {
+function formatFlyDate (row) {
if (!row?.flyDateStart && !row?.flyDateEnd) return ''
return `${row.flyDateStart || '-'} ~ ${row.flyDateEnd || '-'}`
}
// 初始化地图实例
-function initMap() {
+function initMap () {
const publicCesiumInstance = new PublicCesium({
dom: 'mapContainer',
flatMode: false,
@@ -321,14 +292,14 @@
}
// 新增模式绘制
-function addPolygon() {
+function addPolygon () {
drawPolygonExample = new DrawPolygon(viewer)
drawPolygonExample.initHandler(viewer)
drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
}
// 编辑面
-function editPolygon() {
+function editPolygon () {
if (!formData.value?.geom) return
pointList = geomAnalysis(formData.value.geom)
drawPolygonExample?.destroy()
@@ -343,7 +314,7 @@
}
// 查看面
-function viewPolygon() {
+function viewPolygon () {
if (!formData.value?.geom) return
pointList = geomAnalysis(formData.value.geom)
const result = pointList.map(item => [item.longitude, item.latitude]).flat()
@@ -361,13 +332,13 @@
}
// 获取派出所列表
-async function getPoliceStationList() {
+async function getPoliceStationList () {
const res = await fwPoliceStationListApi()
policeStationOptions.value = res?.data?.data ?? []
}
// 获取设备列表
-async function getDeviceList() {
+async function getDeviceList () {
if (deviceOptions.value.length) return
const res = await fwDeviceListApi({ isAreaSelect: 1, areaId: formData.value.id })
deviceOptions.value = res?.data?.data ?? []
@@ -377,14 +348,14 @@
}
// 关联设备变更
-function handleDeviceSelectionChange(rows) {
+function handleDeviceSelectionChange (rows) {
selectedDeviceRows.value = rows
const ids = rows.map(item => item.id)
formData.value.deviceIds = ids.join(',')
}
// 同步选择状态
-function syncDeviceSelection() {
+function syncDeviceSelection () {
if (!deviceTableRef.value) return
deviceTableRef.value.clearSelection()
const rows = []
@@ -399,7 +370,7 @@
}
// 打开弹框
-async function open({ mode, row } = {}) {
+async function open ({ mode, row } = {}) {
dialogMode.value = mode || 'add'
formData.value = dialogMode.value === 'add' ? initForm() : row
selectedDeviceRows.value = []
@@ -429,24 +400,4 @@
})
</script>
-<style scoped lang="scss">
-.bodyBox {
- display: flex;
- height: 600px;
- .leftMap {
- width: 70%;
- height: 100%;
- }
- .rightInfo {
- width: 30%;
- height: 100%;
- overflow: auto;
-
- .ztzf-table-container,
- .ztzf-table-content {
- height: auto;
- flex: none;
- }
- }
-}
-</style>
+<style scoped lang="scss"></style>
diff --git a/applications/drone-command/src/views/areaManage/partition/index.vue b/applications/drone-command/src/views/areaManage/partition/index.vue
index 0665089..5f193b3 100644
--- a/applications/drone-command/src/views/areaManage/partition/index.vue
+++ b/applications/drone-command/src/views/areaManage/partition/index.vue
@@ -22,7 +22,7 @@
<div class="ztzf-table-toolbar">
<el-button :icon="Plus" color="#284FE3" type="primary" @click="openForm('add')">新增</el-button>
- <el-button type="danger" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
+ <el-button :icon="Delete" color="#1A2652" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
</div>
<div class="ztzf-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
@@ -49,8 +49,8 @@
{{ getDictLabel(row.controlLevel, dictObj.controlLevel) }}
</template>
</el-table-column>
- <el-table-column prop="policeStationName" show-overflow-tooltip width="140" label="关联派出所" />
- <el-table-column show-overflow-tooltip width="160" label="可飞行时间段">
+ <el-table-column prop="policeStationName" show-overflow-tooltip label="关联派出所" />
+ <el-table-column show-overflow-tooltip label="可飞行时间段">
<template v-slot="{ row }">
{{ formatFlyDate(row) }}
</template>
@@ -77,7 +77,7 @@
</template>
<script setup>
-import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
+import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue'
import { nextTick, onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fwAreaDividePageApi, fwAreaDivideRemoveApi } from './partitionApi'
@@ -94,7 +94,7 @@
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const selectedIds = ref([]) // 勾选的ID列表
const queryParamsRef = ref(null) // 查询表单实例
diff --git a/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
index 64ac02f..5679269 100644
--- a/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
@@ -45,7 +45,7 @@
<el-col :span="24">
<el-form-item label="位置" prop="longitude">
<el-button @click="selectLocation" style="width: 100%">
- {{ formData.longitude }}, {{ formData.longitude }}, {{ formData.address }}
+ {{ formData.longitude }}, {{ formData.latitude }}, {{ formData.address }}
</el-button>
</el-form-item>
</el-col>
@@ -59,11 +59,17 @@
</el-button>
</template>
- <el-dialog v-model="visible1" title="选择位置" destroy-on-close @closed="positionClosed">
- <div class="ztzf-cesium" id="mapContainer"></div>
+ <el-dialog class="ztzf-page-map-view-dialog" v-model="visible1" :show-close="false"
+ :close-on-click-modal="false" destroy-on-close @closed="positionClosed">
+ <div class="dialog-container">
+ <div class="left-container">
+ <div class="leftMap ztzf-cesium" id="mapContainer"></div>
+ </div>
+ </div>
+
<template #footer>
- <el-button @click="handleMapCancel">取消</el-button>
- <el-button type="primary" @click="handleMapConfirm">确定</el-button>
+ <el-button color="#2B2B4C" @click="handleMapCancel">取消</el-button>
+ <el-button color="#284FE3" type="primary" @click="handleMapConfirm">确定</el-button>
</template>
</el-dialog>
</el-dialog>
@@ -106,12 +112,12 @@
}
// 关闭弹框
-function handleCancel() {
+function handleCancel () {
visible.value = false
}
// 提交新增/编辑
-async function handleSubmit() {
+async function handleSubmit () {
const isValid = await formRef.value?.validate().catch(() => false)
if (!isValid) return
submitting.value = true
@@ -126,13 +132,13 @@
}
// 加载详情
-async function loadDetail() {
+async function loadDetail () {
if (!formData.value.id) return
const res = await fwPoliceStationDetailApi({ id: formData.value.id })
formData.value = res?.data?.data ?? {}
}
-function LeftClickEvent(click) {
+function LeftClickEvent (click) {
const pos = click.position // Cartesian2 屏幕坐标
// 屏幕坐标 -> 椭球面坐标(不考虑地形/模型)
const cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
@@ -174,13 +180,49 @@
}
}
-function positionClosed() {
+function positionClosed () {
redPointEntity = null
}
let viewer
let redPointEntity
-async function selectLocation() {
+function setMapPoint (longitude, latitude) {
+ if (!viewer || longitude == null || latitude == null) return
+ if (!redPointEntity) {
+ redPointEntity = viewer.entities.add({
+ position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
+ point: {
+ color: Cesium.Color.RED,
+ pixelSize: 10,
+ },
+ label: {
+ text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
+ font: '14px',
+ fillColor: Cesium.Color.WHITE,
+ backgroundColor: Cesium.Color.BLACK.withAlpha(0.45),
+ backgroundPadding: new Cesium.Cartesian2(5, 5),
+ pixelOffset: new Cesium.Cartesian2(0, -20),
+ showBackground: true,
+ style: Cesium.LabelStyle.FILL_AND_OUTLINE,
+ horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
+ verticalOrigin: Cesium.VerticalOrigin.CENTER,
+ outlineWidth: 1,
+ outlineColor: Cesium.Color.WHITE,
+ eyeOffset: new Cesium.Cartesian3(0, 0, -20),
+ },
+ })
+ } else {
+ redPointEntity.position = Cesium.Cartesian3.fromDegrees(longitude, latitude)
+ redPointEntity.label.text = `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
+ }
+}
+
+function formatCoord (value) {
+ if (value == null || value === '') return '-'
+ return _.round(Number(value), 6)
+}
+
+async function selectLocation () {
visible1.value = true
await nextTick()
const publicCesiumInstance = new PublicCesium({
@@ -192,14 +234,18 @@
})
publicCesiumInstance.addLeftClickEvent(null, LeftClickEvent)
viewer = publicCesiumInstance.getViewer()
+ if (formData.value.longitude != null && formData.value.latitude != null) {
+ tempLocation.value = { longitude: formData.value.longitude, latitude: formData.value.latitude }
+ setMapPoint(formData.value.longitude, formData.value.latitude)
+ }
}
-function handleMapCancel() {
+function handleMapCancel () {
tempLocation.value = { longitude: null, latitude: null }
visible1.value = false
}
-async function getLocationName() {
+async function getLocationName () {
const tk = import.meta.env.VITE_APP_TDT_TOKEN
const { latitude, longitude } = tempLocation.value
const http = axios.create({ withCredentials: false })
@@ -210,7 +256,7 @@
formData.value.address = res.data.result.formatted_address
}
-function handleMapConfirm() {
+function handleMapConfirm () {
if (tempLocation.value.longitude != null && tempLocation.value.latitude != null) {
formData.value.longitude = _.round(tempLocation.value.longitude, 6)
formData.value.latitude = _.round(tempLocation.value.latitude, 6)
@@ -219,7 +265,7 @@
visible1.value = false
}
// 打开弹框
-async function open({ mode, row } = {}) {
+async function open ({ mode, row } = {}) {
dialogMode.value = mode || 'add'
formData.value = dialogMode.value === 'add' ? initForm() : row
dialogMode.value !== 'add' && (await loadDetail())
@@ -228,8 +274,4 @@
defineExpose({ open })
</script>
<style scoped lang="scss">
-#mapContainer {
- width: 100%;
- height: 500px;
-}
</style>
diff --git a/applications/drone-command/src/views/areaManage/precinctInfo/index.vue b/applications/drone-command/src/views/areaManage/precinctInfo/index.vue
index a81fb81..71c88c4 100644
--- a/applications/drone-command/src/views/areaManage/precinctInfo/index.vue
+++ b/applications/drone-command/src/views/areaManage/precinctInfo/index.vue
@@ -14,7 +14,7 @@
<div class="ztzf-table-toolbar">
<el-button :icon="Plus" color="#284FE3" type="primary" @click="openForm('add')">新增</el-button>
- <el-button type="danger" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
+ <el-button :icon="Delete" color="#1A2652" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
</div>
<div class="ztzf-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
@@ -22,12 +22,12 @@
<el-table class="ztzf-data-cockpit-table" :data="list" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="46" />
<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
- <el-table-column prop="stationName" show-overflow-tooltip width="160" label="派出所名称" />
- <el-table-column prop="address" show-overflow-tooltip width="200" label="位置" />
- <el-table-column prop="longitude" show-overflow-tooltip width="120" label="经度" />
- <el-table-column prop="latitude" show-overflow-tooltip width="120" label="纬度" />
- <el-table-column prop="contactPerson" show-overflow-tooltip width="120" label="联系人" />
- <el-table-column prop="contactPhone" show-overflow-tooltip width="140" label="联系方式" />
+ <el-table-column prop="stationName" show-overflow-tooltip label="派出所名称" />
+ <el-table-column prop="address" show-overflow-tooltip label="位置" />
+ <el-table-column prop="longitude" show-overflow-tooltip label="经度" />
+ <el-table-column prop="latitude" show-overflow-tooltip label="纬度" />
+ <el-table-column prop="contactPerson" show-overflow-tooltip label="联系人" />
+ <el-table-column prop="contactPhone" show-overflow-tooltip label="联系方式" />
<el-table-column label="操作" class-name="operation-btns">
<template v-slot="{ row }">
<el-link @click="openForm('edit', row)">编辑</el-link>
@@ -49,7 +49,7 @@
</template>
<script setup>
-import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
+import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue'
import { nextTick, onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fwPoliceStationPageApi, fwPoliceStationRemoveApi } from './precinctInfoApi'
@@ -62,7 +62,7 @@
})
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const selectedIds = ref([]) // 勾选的ID列表
const queryParamsRef = ref(null) // 查询表单实例
diff --git a/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
index cc8f386..9b2bcfe 100644
--- a/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
@@ -1,116 +1,137 @@
<template>
- <el-dialog v-model="visible" :title="titleEnum[dialogMode]" :close-on-click-modal="false" width="80%">
- <div class="bodyBox">
- <div class="leftMap ztzf-cesium" id="mapContainer"></div>
- <div class="rightInfo">
- <div v-if="readonly">
- <div>场景名称: {{ formData.sceneName }}</div>
- <div>指挥点位置: {{ formatLocation(formData) }}</div>
- <div>场景类型: {{ getDictLabel(formData.sceneType, dictObj.sceneType) }}</div>
- <div>防控负责人: {{ formData.defenseLeader }}</div>
- <div>防控负责人电话: {{ formData.leaderPhone }}</div>
- <div>设备模式: {{ getDictLabel(formData.deviceMode, dictObj.deviceMode) }}</div>
- <div>防控面积: {{ formatDefenseArea(formData.defenseArea) }}</div>
- <el-table ref="areaTableRef" :data="areaList" row-key="id">
- <el-table-column prop="areaName" label="区域名称" />
- <el-table-column prop="areaType" label="区域类型">
- <template v-slot="{ row }">
- {{ getDictLabel(row.areaType, dictObj.areaType) }}
- </template>
- </el-table-column>
- </el-table>
+ <el-dialog class="ztzf-page-map-view-dialog" v-model="visible" :show-close="false" :close-on-click-modal="false">
+ <div class="dialog-container">
+ <div class="left-container">
+ <div class="leftMap ztzf-cesium" id="mapContainer"></div>
+ </div>
+ <div class="right-container">
+ <div class="header">
+ {{ titleEnum[dialogMode] }}
</div>
- <el-form v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly" label-width="100px">
+
+ <div class="dialog-container" v-if="readonly">
+ <div class="detail-title">场景详情</div>
<el-row>
<el-col :span="24">
- <el-form-item label="场景名称" prop="sceneName">
- <el-input v-model="formData.sceneName" maxlength="50" placeholder="请输入" clearable />
- </el-form-item>
+ <div class="label">场景名称</div>
+ <div class="val">{{ formData.sceneName }}</div>
</el-col>
<el-col :span="24">
- <el-form-item label="指挥点位置" prop="longitude">
- <div>{{ formatLocation(formData) }}</div>
- </el-form-item>
+ <div class="label">指挥点位置</div>
+ <div class="val">{{ formatLocation(formData) }}</div>
</el-col>
<el-col :span="24">
- <el-form-item label="场景类型" prop="sceneType">
- <el-select v-model="formData.sceneType" placeholder="请选择" clearable>
- <el-option
- v-for="item in dictObj.sceneType"
- :key="item.dictKey"
- :label="item.dictValue"
- :value="item.dictKey"
- />
- </el-select>
- </el-form-item>
+ <div class="label">场景类型</div>
+ <div class="val">{{ getDictLabel(formData.sceneType, dictObj.sceneType) }}</div>
</el-col>
<el-col :span="24">
- <el-form-item label="防控负责人" prop="defenseLeader">
- <el-input v-model="formData.defenseLeader" maxlength="50" placeholder="请输入" clearable />
- </el-form-item>
+ <div class="label">负责人</div>
+ <div class="val">{{ formData.defenseLeader }}</div>
</el-col>
<el-col :span="24">
- <el-form-item label="防控负责人电话" prop="leaderPhone">
- <el-input v-model="formData.leaderPhone" maxlength="50" placeholder="请输入" clearable />
- </el-form-item>
+ <div class="label">负责人电话</div>
+ <div class="val">{{ formData.leaderPhone }}</div>
</el-col>
<el-col :span="24">
- <el-form-item label="设备模式" prop="deviceMode">
- <el-select v-model="formData.deviceMode" placeholder="请选择" clearable>
- <el-option
- v-for="item in dictObj.deviceMode"
- :key="item.dictKey"
- :label="item.dictValue"
- :value="item.dictKey"
- />
- </el-select>
- </el-form-item>
+ <div class="label">设备模式</div>
+ <div class="val">{{ getDictLabel(formData.deviceMode, dictObj.deviceMode) }}</div>
</el-col>
<el-col :span="24">
- <el-form-item label="防控面积" prop="defenseArea">
- <div>{{ formatDefenseArea(formData.defenseArea) }}</div>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <div>
- <div>
- 关联区域
- <el-input v-model="searchName" placeholder="请输入" clearable></el-input>
- </div>
-
- <el-form-item prop="areaDivideIds" label-width="0">
- <el-table
- ref="areaTableRef"
- :data="areaList.filter(item => item.areaName.includes(searchName))"
- row-key="id"
- @selection-change="handleAreaSelectionChange"
- >
- <el-table-column type="selection" width="55" :reserve-selection="true" />
- <el-table-column prop="areaName" label="区域名称" />
- <el-table-column prop="areaType" label="区域类型">
- <template v-slot="{ row }">
- {{ getDictLabel(row.areaType, dictObj.areaType) }}
- </template>
- </el-table-column>
- </el-table>
- </el-form-item>
- </div>
+ <div class="label">防控面积</div>
+ <div class="val">{{ formatDefenseArea(formData.defenseArea) }}</div>
</el-col>
</el-row>
+
+ <div class="detail-title">关联区域</div>
+ <div class="ztzf-table-container">
+ <div class="ztzf-table-content">
+ <el-table class="ztzf-data-cockpit-table" ref="areaTableRef" :data="areaList" row-key="id">
+ <el-table-column prop="areaName" label="区域名称" />
+ <el-table-column prop="areaType" label="区域类型">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.areaType, dictObj.areaType) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+ </div>
+
+ <el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly"
+ label-width="96px">
+ <el-form-item label="场景名称" prop="sceneName">
+ <el-input class="ztzf-data-cockpit-search-input" v-model="formData.sceneName" maxlength="50"
+ placeholder="请输入" clearable />
+ </el-form-item>
+ <el-form-item label="指挥点位置" prop="longitude">
+ <div class="val">{{ formatLocation(formData) }}</div>
+ </el-form-item>
+ <el-form-item label="场景类型" prop="sceneType">
+ <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.sceneType" placeholder="请选择" clearable>
+ <el-option v-for="item in dictObj.sceneType" :key="item.dictKey" :label="item.dictValue"
+ :value="item.dictKey" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="负责人" prop="defenseLeader">
+ <el-input class="ztzf-data-cockpit-search-input" v-model="formData.defenseLeader" maxlength="50"
+ placeholder="请输入" clearable />
+ </el-form-item>
+ <el-form-item label="负责人电话" prop="leaderPhone">
+ <el-input class="ztzf-data-cockpit-search-input" v-model="formData.leaderPhone" maxlength="50"
+ placeholder="请输入" clearable />
+ </el-form-item>
+ <el-form-item label="设备模式" prop="deviceMode">
+ <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.deviceMode" placeholder="请选择" clearable>
+ <el-option v-for="item in dictObj.deviceMode" :key="item.dictKey" :label="item.dictValue"
+ :value="item.dictKey" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="防控面积" prop="defenseArea">
+ <div class="val">{{ formatDefenseArea(formData.defenseArea) }}</div>
+ </el-form-item>
+
+ <div class="search-table-container">
+ <div class="search-box">
+ <div class="label">
+ 关联区域
+ </div>
+
+ <el-input class="ztzf-data-cockpit-search-input" v-model="searchName" placeholder="请输入"
+ clearable></el-input>
+ </div>
+
+ <el-form-item prop="areaDivideIds" label-width="0">
+ <el-table class="ztzf-data-cockpit-table" ref="areaTableRef"
+ :data="areaList.filter(item => item.areaName.includes(searchName))" row-key="id"
+ @selection-change="handleAreaSelectionChange">
+ <el-table-column type="selection" width="55" :reserve-selection="true" />
+ <el-table-column prop="areaName" label="区域名称" />
+ <el-table-column prop="areaType" label="区域类型">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.areaType, dictObj.areaType) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form-item>
+ </div>
</el-form>
+
+ <div class="footer">
+ <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting"
+ :disabled="submitting" @click="handleSubmit">
+ 确定
+ </el-button>
+ </div>
</div>
</div>
- <template #footer>
- <el-button @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
- <el-button v-if="!readonly" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit">
- 确定
- </el-button>
- </template>
</el-dialog>
</template>
<script setup>
-import { computed, inject, nextTick, onMounted, ref } from 'vue'
+import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { fwDefenseSceneDetailApi, fwDefenseSceneSubmitApi } from './sceneConfigApi'
import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
@@ -122,8 +143,8 @@
const initForm = () => ({
sceneName: '', // 场景名称
sceneType: '', // 场景类型
- defenseLeader: '', // 防控负责人
- leaderPhone: '', // 防控负责人电话
+ defenseLeader: '', // 负责人
+ leaderPhone: '', // 负责人电话
deviceMode: '', // 设备模式
areaDivideIds: '', // 关联区域ID
areaCount: 0, // 区域数量
@@ -159,12 +180,12 @@
}
// 关闭弹框
-function handleCancel() {
+function handleCancel () {
visible.value = false
}
// 提交新增/编辑
-async function handleSubmit() {
+async function handleSubmit () {
const isValid = await formRef.value?.validate().catch(() => false)
if (!isValid) return
submitting.value = true
@@ -183,21 +204,21 @@
}
// 加载详情
-async function loadDetail() {
+async function loadDetail () {
if (!formData.value.id) return
const res = await fwDefenseSceneDetailApi({ id: formData.value.id })
formData.value = res?.data?.data ?? initForm()
}
// 获取区域列表
-async function getAreaList() {
+async function getAreaList () {
if (areaList.value.length) return
const res = await fwAreaDivideListApi({ filterSelected: 1, sceneId: formData.value.id })
areaList.value = res?.data?.data ?? []
}
// 关联区域变更
-function handleAreaSelectionChange(rows) {
+function handleAreaSelectionChange (rows) {
selectedAreaRows.value = rows
formData.value.areaDivideIds = rows.map(item => item.id)
formData.value.areaCount = rows.length
@@ -205,7 +226,7 @@
}
// 渲染面
-function renderingSurface() {
+function renderingSurface () {
geometricSource && geometricSource.entities.removeAll()
selectedAreaRows.value.forEach(item => {
if (item.geom) {
@@ -227,13 +248,13 @@
watch(() => selectedAreaRows.value, renderingSurface)
-function calcDefenseArea(rows) {
+function calcDefenseArea (rows) {
const total = rows.reduce((sum, item) => sum + (Number(item.areaSize) || 0), 0)
return _.round(total, 2)
}
// 同步关联区域
-function syncAreaSelection() {
+function syncAreaSelection () {
if (!areaTableRef.value) return
areaTableRef.value.clearSelection()
const rows = []
@@ -250,20 +271,20 @@
formData.value.defenseArea = calcDefenseArea(rows)
}
-function formatDefenseArea(value) {
+function formatDefenseArea (value) {
if (value == null || value === '') return ''
return `${value}k㎡`
}
// 格式化指挥点位置
-function formatLocation(row) {
+function formatLocation (row) {
if (row?.longitude == null || row?.latitude == null) return ''
return `${_.round(row.longitude, 6)}, ${_.round(row.latitude, 6)}`
}
let geometricSource
// 初始化地图实例
-function initMap() {
+function initMap () {
const publicCesiumInstance = new PublicCesium({
dom: 'mapContainer',
flatMode: false,
@@ -279,7 +300,7 @@
viewer.dataSources.add(geometricSource)
}
-function LeftClickEvent(click) {
+function LeftClickEvent (click) {
const pos = click.position
const cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
if (!cartesian3) return
@@ -291,7 +312,7 @@
setMapPoint(formData.value.longitude, formData.value.latitude)
}
-function setMapPoint(longitude, latitude) {
+function setMapPoint (longitude, latitude) {
if (!viewer || longitude == null || latitude == null) return
if (!redPointEntity) {
redPointEntity = viewer.entities.add({
@@ -303,17 +324,17 @@
label: {
text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
font: '14px',
- fillColor: Cesium.Color.WHITE, // 文字颜色:白色
- backgroundColor: Cesium.Color.BLACK.withAlpha(0.45), //背景颜色
- backgroundPadding: new Cesium.Cartesian2(5, 5), // 水平/垂直内边距(像素)
- pixelOffset: new Cesium.Cartesian2(0, -20), // 可选:微调位置
- showBackground: true, // 确保背景显示(某些版本需要)
+ fillColor: Cesium.Color.WHITE,
+ backgroundColor: Cesium.Color.BLACK.withAlpha(0.45),
+ backgroundPadding: new Cesium.Cartesian2(5, 5),
+ pixelOffset: new Cesium.Cartesian2(0, -20),
+ showBackground: true,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
outlineWidth: 1,
outlineColor: Cesium.Color.WHITE,
- eyeOffset: new Cesium.Cartesian3(0, 0, -20), // 负值更靠近相机(显示在前)
+ eyeOffset: new Cesium.Cartesian3(0, 0, -20),
},
})
} else {
@@ -323,7 +344,7 @@
}
// 打开弹框
-async function open({ mode, row } = {}) {
+async function open ({ mode, row } = {}) {
dialogMode.value = mode || 'add'
formData.value = dialogMode.value === 'add' ? initForm() : row
selectedAreaRows.value = []
@@ -348,18 +369,4 @@
})
</script>
-<style scoped lang="scss">
-.bodyBox {
- display: flex;
- height: 600px;
- .leftMap {
- width: 70%;
- height: 100%;
- }
- .rightInfo {
- width: 30%;
- height: 100%;
- overflow: auto;
- }
-}
-</style>
+<style scoped lang="scss"></style>
diff --git a/applications/drone-command/src/views/areaManage/sceneConfig/index.vue b/applications/drone-command/src/views/areaManage/sceneConfig/index.vue
index bd99316..29cfdbb 100644
--- a/applications/drone-command/src/views/areaManage/sceneConfig/index.vue
+++ b/applications/drone-command/src/views/areaManage/sceneConfig/index.vue
@@ -55,7 +55,7 @@
<div class="ztzf-table-toolbar">
<el-button :icon="Plus" color="#284FE3" type="primary" @click="openForm('add')">新增</el-button>
- <el-button type="danger" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
+ <el-button :icon="Delete" color="#1A2652" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
</div>
<div class="ztzf-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
@@ -64,22 +64,22 @@
<el-table-column type="selection" width="46" />
<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
- <el-table-column prop="sceneName" show-overflow-tooltip width="130" label="场景名称" />
- <el-table-column prop="sceneType" show-overflow-tooltip width="120" label="场景类型">
+ <el-table-column prop="sceneName" show-overflow-tooltip label="场景名称" />
+ <el-table-column prop="sceneType" show-overflow-tooltip label="场景类型">
<template v-slot="{ row }">
{{ getDictLabel(row.sceneType, dictObj.sceneType) }}
</template>
</el-table-column>
- <el-table-column show-overflow-tooltip width="150" label="指挥点位置">
+ <el-table-column show-overflow-tooltip label="指挥点位置">
<template v-slot="{ row }">
{{ formatLocation(row) }}
</template>
</el-table-column>
- <el-table-column prop="defenseLeader" show-overflow-tooltip width="120" label="防控负责人" />
- <el-table-column prop="leaderPhone" show-overflow-tooltip width="140" label="防控负责人电话" />
- <el-table-column prop="areaCount" show-overflow-tooltip width="110" label="区域数量" />
- <el-table-column prop="counterDeviceCount" show-overflow-tooltip width="120" label="反制设备数量" />
- <el-table-column prop="detectDeviceCount" show-overflow-tooltip width="120" label="探测设备数量" />
+ <el-table-column prop="defenseLeader" show-overflow-tooltip label="防控负责人" />
+ <el-table-column prop="leaderPhone" show-overflow-tooltip label="防控负责人电话" />
+ <el-table-column prop="areaCount" show-overflow-tooltip label="区域数量" />
+ <el-table-column prop="counterDeviceCount" show-overflow-tooltip label="反制设备数量" />
+ <el-table-column prop="detectDeviceCount" show-overflow-tooltip label="探测设备数量" />
<el-table-column label="操作" class-name="operation-btns">
<template v-slot="{ row }">
<el-link @click="openForm('view', row)">查看</el-link>
@@ -107,7 +107,7 @@
</template>
<script setup>
-import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
+import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue'
import { nextTick, onMounted, provide, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fwDefenseScenePageApi, fwDefenseSceneRemoveApi } from './sceneConfigApi'
@@ -125,7 +125,7 @@
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const selectedIds = ref([]) // 勾选的ID列表
const queryParamsRef = ref(null) // 查询表单实例
diff --git a/applications/drone-command/src/views/basicManage/deviceScrap/index.vue b/applications/drone-command/src/views/basicManage/deviceScrap/index.vue
index 0032104..481235a 100644
--- a/applications/drone-command/src/views/basicManage/deviceScrap/index.vue
+++ b/applications/drone-command/src/views/basicManage/deviceScrap/index.vue
@@ -31,21 +31,21 @@
<el-table class="ztzf-data-cockpit-table" :data="list" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="46" />
<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
- <el-table-column prop="deviceName" show-overflow-tooltip width="130" label="设备名称" />
- <el-table-column prop="deviceType" show-overflow-tooltip width="130" label="设备类型">
+ <el-table-column prop="deviceName" show-overflow-tooltip label="设备名称" />
+ <el-table-column prop="deviceType" show-overflow-tooltip label="设备类型">
<template v-slot="{ row }">
{{ getDictLabel(row.deviceType, dictObj.deviceType) }}
</template>
</el-table-column>
- <el-table-column prop="deviceModel" show-overflow-tooltip width="98" label="型号" />
- <el-table-column prop="manufacturer" show-overflow-tooltip width="130" label="生产厂商" />
- <el-table-column prop="createTime" show-overflow-tooltip width="96" label="入库时间" />
- <el-table-column prop="source" show-overflow-tooltip width="112" label="来源" />
- <el-table-column prop="charger" show-overflow-tooltip width="100" label="负责人" />
- <el-table-column prop="scrapTime" show-overflow-tooltip width="96" label="报废时间" />
- <el-table-column prop="scrapReason" show-overflow-tooltip width="136" label="报废原因" />
- <el-table-column prop="disposeWay" show-overflow-tooltip width="116" label="处置方式" />
- <el-table-column prop="maintainReminder" show-overflow-tooltip width="116" label="维护提醒" />
+ <el-table-column prop="deviceModel" show-overflow-tooltip label="型号" />
+ <el-table-column prop="manufacturer" show-overflow-tooltip label="生产厂商" />
+ <el-table-column prop="createTime" show-overflow-tooltip label="入库时间" />
+ <el-table-column prop="source" show-overflow-tooltip label="来源" />
+ <el-table-column prop="charger" show-overflow-tooltip label="负责人" />
+ <el-table-column prop="scrapTime" show-overflow-tooltip label="报废时间" />
+ <el-table-column prop="scrapReason" show-overflow-tooltip label="报废原因" />
+ <el-table-column prop="disposeWay" show-overflow-tooltip label="处置方式" />
+ <el-table-column prop="maintainReminder" show-overflow-tooltip label="维护提醒" />
<el-table-column label="操作" class-name="operation-btns">
<template v-slot="{ row }">
<el-link @click="handleView(row)">查看</el-link>
@@ -84,7 +84,7 @@
})
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const selectedIds = ref([]) // 勾选的ID列表
const queryParamsRef = ref(null) // 查询表单实例
diff --git a/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue b/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue
index fc33051..63a0c3c 100644
--- a/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue
+++ b/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue
@@ -272,7 +272,7 @@
formData.value = initForm()
}
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
async function getList () {
loading.value = true
diff --git a/applications/drone-command/src/views/basicManage/deviceStock/index.vue b/applications/drone-command/src/views/basicManage/deviceStock/index.vue
index 0138975..25667c8 100644
--- a/applications/drone-command/src/views/basicManage/deviceStock/index.vue
+++ b/applications/drone-command/src/views/basicManage/deviceStock/index.vue
@@ -65,7 +65,7 @@
<div class="ztzf-table-toolbar">
<el-button :icon="Plus" color="#284FE3" type="primary" @click="handleAdd">新增</el-button>
- <el-button :icon="Download" type="primary" @click="exportFile" :loading="exportLoading">导出</el-button>
+ <el-button :icon="Download" color="#1A2652" type="primary" @click="exportFile" :loading="exportLoading">导出</el-button>
<!-- <el-button type="danger" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>-->
</div>
@@ -147,7 +147,7 @@
size: 10, // 每页大小
})
const searchParams = ref(initSearchParams()) // 查询参数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const total = ref(0) // 总数
const selectedIds = ref([]) // 勾选的设备ID列表
diff --git a/applications/drone-command/src/views/basicManage/maintainRecord/FormDiaLog.vue b/applications/drone-command/src/views/basicManage/maintainRecord/FormDiaLog.vue
index 66111e0..ec5044c 100644
--- a/applications/drone-command/src/views/basicManage/maintainRecord/FormDiaLog.vue
+++ b/applications/drone-command/src/views/basicManage/maintainRecord/FormDiaLog.vue
@@ -198,7 +198,7 @@
})
const searchParams = ref(initSearchParams())
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
async function getList() {
loading.value = true
diff --git a/applications/drone-command/src/views/basicManage/maintainRecord/index.vue b/applications/drone-command/src/views/basicManage/maintainRecord/index.vue
index 2aaa9f8..f51cfbc 100644
--- a/applications/drone-command/src/views/basicManage/maintainRecord/index.vue
+++ b/applications/drone-command/src/views/basicManage/maintainRecord/index.vue
@@ -28,7 +28,7 @@
<div class="ztzf-table-toolbar">
<el-button :icon="Plus" color="#284FE3" type="primary" @click="handleAdd">维护计划</el-button>
- <el-button type="danger" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
+ <el-button :icon="Delete" color="#1A2652" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
</div>
<div class="ztzf-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
@@ -36,17 +36,17 @@
<el-table class="ztzf-data-cockpit-table" :data="list" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="46" />
<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
- <el-table-column prop="deviceName" show-overflow-tooltip width="130" label="设备名称" />
- <el-table-column prop="deviceType" show-overflow-tooltip width="130" label="设备类型">
+ <el-table-column prop="deviceName" show-overflow-tooltip label="设备名称" />
+ <el-table-column prop="deviceType" show-overflow-tooltip label="设备类型">
<template v-slot="{ row }">
{{ getDictLabel(row.deviceType, dictObj.deviceType) }}
</template>
</el-table-column>
- <el-table-column prop="deviceModel" show-overflow-tooltip width="98" label="型号" />
- <el-table-column prop="manufacturer" show-overflow-tooltip width="130" label="生产厂商" />
- <el-table-column prop="charger" show-overflow-tooltip width="100" label="负责人" />
- <el-table-column prop="maintainReminder" show-overflow-tooltip width="116" label="维护提醒" />
- <el-table-column prop="planCycleType" show-overflow-tooltip width="160" label="维护计划">
+ <el-table-column prop="deviceModel" show-overflow-tooltip label="型号" />
+ <el-table-column prop="manufacturer" show-overflow-tooltip label="生产厂商" />
+ <el-table-column prop="charger" show-overflow-tooltip label="负责人" />
+ <el-table-column prop="maintainReminder" show-overflow-tooltip label="维护提醒" />
+ <el-table-column prop="planCycleType" show-overflow-tooltip label="维护计划">
<template v-slot="{ row }">
{{ getPlanCycleLabel(row) }}
</template>
@@ -73,7 +73,7 @@
</basic-container>
</template>
<script setup>
-import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
+import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue'
import { onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getDictionaryByCode } from '@/api/system/dictbiz'
@@ -95,7 +95,7 @@
})
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const selectedIds = ref([]) // 勾选的ID列表
const queryParamsRef = ref(null) // 查询表单实例
diff --git a/applications/drone-command/src/views/dataCenter/components/dataCenterMap.vue b/applications/drone-command/src/views/dataCenter/components/dataCenterMap.vue
index 3dd006b..9ba2f1f 100644
--- a/applications/drone-command/src/views/dataCenter/components/dataCenterMap.vue
+++ b/applications/drone-command/src/views/dataCenter/components/dataCenterMap.vue
@@ -233,7 +233,7 @@
};
// 地图接口
-const loading = ref(false);
+const loading = ref(true);
const getMapInfoAPIFun = async ids => {
try {
const res = await getMapInfoAPI(ids);
diff --git a/applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue b/applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue
index 3057b8c..f8e3dab 100644
--- a/applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue
@@ -108,7 +108,7 @@
})
const searchParams = ref(initSearchParams())
const queryParamsRef = ref(null)
-const loading = ref(false)
+const loading = ref(true)
const list = ref([])
const total = ref(0)
const areaTree = ref([
diff --git a/applications/drone-command/src/views/dataCockpit/components/EquipmentWarning.vue b/applications/drone-command/src/views/dataCockpit/components/EquipmentWarning.vue
index 4f2c8a0..ddbd81d 100644
--- a/applications/drone-command/src/views/dataCockpit/components/EquipmentWarning.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/EquipmentWarning.vue
@@ -33,7 +33,7 @@
import EmptyState from './EmptyState.vue'
const equipmentWarningData = ref([])
-const loading = ref(false)
+const loading = ref(true)
const minLoadingMs = 400
const statusMap = {
diff --git a/applications/drone-command/src/views/dataCockpit/components/HistoryWarning.vue b/applications/drone-command/src/views/dataCockpit/components/HistoryWarning.vue
index 01d5958..767359a 100644
--- a/applications/drone-command/src/views/dataCockpit/components/HistoryWarning.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/HistoryWarning.vue
@@ -44,7 +44,7 @@
import EmptyState from './EmptyState.vue'
const historyWarningData = ref([])
-const loading = ref(false)
+const loading = ref(true)
const minLoadingMs = 400
// 查询条件:keyword + dateRange(YYYY-MM-DD数组)
diff --git a/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue b/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
index 7210005..607b94d 100644
--- a/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
@@ -36,7 +36,7 @@
import { ElMessage } from 'element-plus'
const realWarningData = ref([])
-const loading = ref(false)
+const loading = ref(true)
const minLoadingMs = 400
const actionTextMap = {
diff --git a/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue b/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue
index 7a4cfcb..b014989 100644
--- a/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue
@@ -94,7 +94,7 @@
const historyVisible = ref(false)
const historyDevice = ref(null)
-const loading = ref(false)
+const loading = ref(true)
const minLoadingMs = 400
const onHistory = (item) => {
diff --git a/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/FormDiaLog.vue b/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/FormDiaLog.vue
index cef590e..3471537 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/FormDiaLog.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/FormDiaLog.vue
@@ -1,53 +1,73 @@
-<template>
- <el-dialog v-model="visible" :title="titleEnum[dialogMode]" :close-on-click-modal="false">
- <div v-if="readonly">
+<template>
+ <el-dialog
+ class="ztzf-page-view-dialog"
+ v-model="visible"
+ :title="titleEnum[dialogMode]"
+ :close-on-click-modal="false"
+ width="520px"
+ >
+ <div class="detail-container">
<el-row>
<el-col :span="12">
- <div>无人机名称: {{ formData.droneName }}</div>
+ <div class="label">无人机名称</div>
+ <div class="val">{{ formData.droneName || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>无人机类型: {{ formData.droneType }}</div>
+ <div class="label">无人机类型</div>
+ <div class="val">{{ formData.droneType || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>无人机设备编码: {{ formData.droneDeviceCode }}</div>
+ <div class="label">无人机设备编码</div>
+ <div class="val">{{ formData.droneDeviceCode || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>发现时间: {{ formData.findTime }}</div>
+ <div class="label">发现时间</div>
+ <div class="val">{{ formData.findTime || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>反制效果: {{ getDictLabel(formData.counterEffect, dictObj.counterEffect) }}</div>
+ <div class="label">反制效果</div>
+ <div class="val">{{ getDictLabel(formData.counterEffect, dictObj.counterEffect) || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>反制方式: {{ getCounterWayLabel(formData.counterWay) }}</div>
+ <div class="label">反制方式</div>
+ <div class="val">{{ getCounterWayLabel(formData.counterWay) || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>反制设备名称: {{ formData.deviceName }}</div>
+ <div class="label">反制设备名称</div>
+ <div class="val">{{ formData.deviceName || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>反制设备型号: {{ formData.deviceModel }}</div>
+ <div class="label">反制设备型号</div>
+ <div class="val">{{ formData.deviceModel || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>反制设备类型: {{ getDeviceTypeLabel(formData.deviceType) }}</div>
+ <div class="label">反制设备类型</div>
+ <div class="val">{{ getDeviceTypeLabel(formData.deviceType) || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>反制设备编码: {{ formData.deviceSn }}</div>
+ <div class="label">反制设备编码</div>
+ <div class="val">{{ formData.deviceSn || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>区域编码: {{ formData.areaCode }}</div>
+ <div class="label">区域编码</div>
+ <div class="val">{{ formData.areaCode || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>部署位置: {{ getDeployPosition() }}</div>
+ <div class="label">部署位置</div>
+ <div class="val">{{ getDeployPosition() || '-' }}</div>
</el-col>
<el-col :span="12">
- <div>覆盖范围: {{ formData.coverRadiusM ? `${formData.coverRadiusM}米` : '' }}</div>
+ <div class="label">覆盖范围</div>
+ <div class="val">{{ formData.coverRadiusM ? `${formData.coverRadiusM}米` : '-' }}</div>
</el-col>
<el-col :span="12">
- <div>工作模式: {{ getWorkModeLabel(formData.workMode) }}</div>
+ <div class="label">工作模式</div>
+ <div class="val">{{ getWorkModeLabel(formData.workMode) || '-' }}</div>
</el-col>
</el-row>
</div>
<template #footer>
- <el-button @click="handleCancel">关闭</el-button>
+ <el-button color="#2B2B4C" @click="handleCancel">关闭</el-button>
</template>
</el-dialog>
</template>
@@ -78,7 +98,6 @@
// 注入字典数据
const dictObj = inject('dictObj')
-// 注入反制效果选项
const formData = ref(initForm()) // 表单数据
const visible = defineModel() // 弹框显隐
@@ -94,7 +113,7 @@
// 获取反制方式标签
function getCounterWayLabel(value) {
const map = { '1': '信号干扰', '2': '诱导驱离' }
- return map[value] || value || '无'
+ return map[value] || value || '暂无'
}
// 获取设备类型标签
@@ -128,7 +147,9 @@
async function open({ mode, row } = {}) {
dialogMode.value = mode || 'view'
formData.value = row ? { ...row } : initForm()
- dialogMode.value === 'view' && (await loadDetail())
+ if (readonly.value) {
+ await loadDetail()
+ }
}
defineExpose({ open })
diff --git a/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/index.vue b/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/index.vue
index a8b596a..53a0e36 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/index.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/countermeasureEvaluation/index.vue
@@ -1,93 +1,114 @@
-<template>
+<template>
<basic-container>
- <el-form ref="queryParamsRef" :model="searchParams">
- <el-row :gutter="16">
- <el-col :span="4">
- <el-form-item label="设备名称" prop="deviceName">
- <el-input v-model="searchParams.deviceName" placeholder="请输入" clearable @clear="handleSearch" />
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <el-form-item label="设备类型" prop="deviceType">
- <el-select v-model="searchParams.deviceType" placeholder="请选择" clearable @change="handleSearch">
- <el-option
- v-for="item in dictObj.deviceType"
- :key="item.dictKey"
- :label="item.dictValue"
- :value="item.dictKey"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <el-form-item label="反制效果" prop="counterEffect">
- <el-select v-model="searchParams.counterEffect" placeholder="请选择" clearable @change="handleSearch">
- <el-option
- v-for="item in dictObj.counterEffect"
- :key="item.dictKey"
- :label="item.dictValue"
- :value="item.dictKey"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <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 ref="queryParamsRef" :model="searchParams" class="ztzf-page-history-search">
+ <el-form-item label="设备名称" prop="deviceName">
+ <el-input
+ class="ztzf-data-cockpit-search-input"
+ v-model="searchParams.deviceName"
+ placeholder="请输入"
+ clearable
+ @clear="handleSearch"
+ />
+ </el-form-item>
+
+ <el-form-item label="设备类型" prop="deviceType">
+ <el-select
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
+ v-model="searchParams.deviceType"
+ placeholder="请选择"
+ clearable
+ @change="handleSearch"
+ >
+ <el-option
+ v-for="item in dictObj.deviceType"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="反制效果" prop="counterEffect">
+ <el-select
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
+ v-model="searchParams.counterEffect"
+ placeholder="请选择"
+ clearable
+ @change="handleSearch"
+ >
+ <el-option
+ v-for="item in dictObj.counterEffect"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item class="history-search-actions">
+ <el-button :icon="RefreshRight" @click="resetForm"></el-button>
+ <el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button>
+ </el-form-item>
</el-form>
- <el-table v-loading="loading" :data="list">
- <el-table-column type="index" width="60" label="序号" />
- <el-table-column prop="droneName" label="无人机名称" />
- <el-table-column prop="droneType" label="无人机类型" />
- <el-table-column prop="droneDeviceCode" label="无人机设备编码" />
- <el-table-column prop="findTime" label="发现时间" />
- <el-table-column prop="counterEffect" label="反制效果">
- <template v-slot="{ row }">
- {{ getDictLabel(row.counterEffect, dictObj.counterEffect) }}
- </template>
- </el-table-column>
- <el-table-column prop="deviceName" label="反制设备名称" />
- <el-table-column prop="deviceModel" label="反制设备型号" />
- <el-table-column prop="deviceType" label="反制设备类型">
- <template v-slot="{ row }">
- {{ getDictLabel(row.deviceType, dictObj.deviceType) }}
- </template>
- </el-table-column>
- <el-table-column prop="deviceSn" label="反制设备编码" />
- <el-table-column prop="areaCode" label="场景" />
- <el-table-column prop="areaCode" label="区域" />
- <el-table-column label="部署位置">
- <template v-slot="{ row }">
- {{ row.deployLongitude && row.deployLatitude ? `${row.deployLongitude}, ${row.deployLatitude}` : '' }}
- </template>
- </el-table-column>
- <el-table-column prop="coverRadiusM" label="覆盖范围">
- <template v-slot="{ row }">
- {{ row.coverRadiusM ? `${row.coverRadiusM}米` : '' }}
- </template>
- </el-table-column>
- <el-table-column prop="workMode" label="工作模式">
- <template v-slot="{ row }">
- {{ getWorkModeLabel(row.workMode) }}
- </template>
- </el-table-column>
- <el-table-column label="操作" width="100">
- <template v-slot="{ row }">
- <el-link @click="openForm('view', row)" type="primary">查看</el-link>
- </template>
- </el-table-column>
- </el-table>
- <div>
- <el-pagination
- v-model:current-page="searchParams.current"
- v-model:page-size="searchParams.size"
- :total="total"
- @change="getList"
- />
+
+ <div class="ztzf-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
+ <div class="ztzf-table-content ztzf-table-content-bg">
+ <el-table class="ztzf-data-cockpit-table" :data="list">
+ <el-table-column type="index" width="64" show-overflow-tooltip label="序号" />
+ <el-table-column prop="droneName" show-overflow-tooltip width="130" label="无人机名称" />
+ <el-table-column prop="droneType" show-overflow-tooltip width="130" label="无人机类型" />
+ <el-table-column prop="droneDeviceCode" show-overflow-tooltip width="140" label="无人机设备编码" />
+ <el-table-column prop="findTime" show-overflow-tooltip width="140" label="发现时间" />
+ <el-table-column prop="counterEffect" show-overflow-tooltip width="120" label="反制效果">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.counterEffect, dictObj.counterEffect) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="deviceName" show-overflow-tooltip width="130" label="反制设备名称" />
+ <el-table-column prop="deviceModel" show-overflow-tooltip width="120" label="反制设备型号" />
+ <el-table-column prop="deviceType" show-overflow-tooltip width="120" label="反制设备类型">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.deviceType, dictObj.deviceType) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="deviceSn" show-overflow-tooltip width="140" label="反制设备编码" />
+ <el-table-column prop="areaCode" show-overflow-tooltip width="120" label="场景" />
+ <el-table-column prop="areaCode" show-overflow-tooltip width="120" label="区域" />
+ <el-table-column show-overflow-tooltip width="160" label="部署位置">
+ <template v-slot="{ row }">
+ {{ row.deployLongitude && row.deployLatitude ? `${row.deployLongitude}, ${row.deployLatitude}` : '' }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="coverRadiusM" show-overflow-tooltip width="120" label="覆盖范围">
+ <template v-slot="{ row }">
+ {{ row.coverRadiusM ? `${row.coverRadiusM}米` : '' }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="workMode" show-overflow-tooltip width="120" label="工作模式">
+ <template v-slot="{ row }">
+ {{ getWorkModeLabel(row.workMode) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="操作" class-name="operation-btns" width="100">
+ <template v-slot="{ row }">
+ <el-link @click="openForm('view', row)" type="primary">查看</el-link>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <div class="ztzf-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>
<FormDiaLog v-if="dialogVisible" v-model="dialogVisible" ref="dialogRef" />
@@ -95,6 +116,7 @@
</template>
<script setup>
+import { RefreshRight, Search } from '@element-plus/icons-vue'
import { onMounted, ref, nextTick, provide } from 'vue'
import { fwEffectEvalPageApi } from './countermeasureEvaluationApi'
import FormDiaLog from './FormDiaLog.vue'
@@ -112,17 +134,17 @@
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const queryParamsRef = ref(null) // 查询表单实例
const dialogRef = ref(null) // 弹框实例
-const dialogVisible = ref(null) // 弹框显隐
+const dialogVisible = ref(false) // 弹框显隐
const dictObj = ref({
deviceType: [], // 设备类型
counterEffect: [], // 反制效果
})
-// 注入字典和反制效果选项供FormDiaLog使用
+// 注入字典和反制效果选项供 FormDiaLog 使用
provide('dictObj', dictObj)
// 获取列表
diff --git a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
index 8e3c493..8d026e2 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
@@ -1,86 +1,101 @@
<template>
- <el-dialog
- v-model="visible"
- :title="dialogTitle"
- class="detection-range-dialog"
- destroy-on-close
- @closed="handleClosed"
- >
- <div class="dialog-body">
- <div class="dialog-map">
- <DeviceMapContainer
- :online-devices="mapDevices"
- :show-layer-control="false"
- container-id="detection-range-map"
- />
- <div class="map-toolbar">
- <button class="map-btn" type="button" aria-label="编辑范围"></button>
- <button class="map-btn" type="button" aria-label="定位设备"></button>
- </div>
- <div class="map-mini-controls">
- <button class="map-mini-btn" type="button" aria-label="放大">+</button>
- <button class="map-mini-btn" type="button" aria-label="缩小">-</button>
- </div>
+ <el-dialog v-model="visible" class="ztzf-page-map-view-dialog" :show-close="false" :close-on-click-modal="false"
+ destroy-on-close @closed="handleClosed">
+ <div class="dialog-container">
+ <div class="left-container">
+ <div class="leftMap ztzf-cesium" id="detectionRangeMap"></div>
</div>
- <div class="dialog-form">
- <div class="form-title">{{ formTitle }}</div>
- <el-form ref="formRef" :model="formData" :rules="rules" label-width="84px" :disabled="dialogReadonly">
+
+ <div class="right-container">
+ <div class="header">{{ formTitle }}</div>
+
+ <div class="dialog-container" v-if="dialogReadonly">
+ <el-row>
+ <el-col :span="24">
+ <div class="label">侦测设备</div>
+ <div class="val">{{ formData.deviceName || '-' }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">设备类型</div>
+ <div class="val">{{ getDeviceTypeLabel(formData.deviceType) }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">设备位置</div>
+ <div class="val">{{ formData.deployLocation || '-' }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">设备编号</div>
+ <div class="val">{{ formData.deviceSn || '-' }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">设备型号</div>
+ <div class="val">{{ formData.deviceModel || '-' }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">覆盖范围</div>
+ <div class="val">{{ formatRange(formData.effectiveRangeKm) }}</div>
+ </el-col>
+ </el-row>
+ </div>
+
+ <el-form v-else ref="formRef" class="dialog-form" :model="formData" :rules="rules" label-width="96px">
<el-form-item label="侦测设备" prop="deviceId">
- <el-select v-model="formData.deviceId" placeholder="请选择" clearable @change="handleDeviceChange">
- <el-option v-for="item in deviceOptions" :key="item.value" :label="item.label" :value="item.value" />
+ <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.deviceId" placeholder="请选择" clearable @change="handleDeviceChange">
+ <el-option v-for="item in deviceOptions" :key="item.value" :label="item.label"
+ :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="设备类型" prop="deviceType">
- <el-select v-model="formData.deviceType" placeholder="请选择" clearable>
- <el-option
- v-for="item in dictObj.deviceType"
- :key="item.dictKey"
- :label="item.dictValue"
- :value="item.dictKey"
- />
+ <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.deviceType" placeholder="请选择" clearable>
+ <el-option v-for="item in dictObj.deviceType" :key="item.dictKey" :label="item.dictValue"
+ :value="item.dictKey" />
</el-select>
</el-form-item>
<el-form-item label="设备位置" prop="deployLocation">
- <el-input v-model="formData.deployLocation" placeholder="请选择位置">
+ <el-input class="ztzf-data-cockpit-search-input" v-model="formData.deployLocation"
+ placeholder="请选择位置">
<template #suffix>
<span class="suffix-action">地图选点</span>
</template>
</el-input>
</el-form-item>
<el-form-item label="设备编号" prop="deviceSn">
- <el-input v-model="formData.deviceSn" placeholder="请输入" clearable />
+ <el-input class="ztzf-data-cockpit-search-input" v-model="formData.deviceSn" placeholder="请输入"
+ clearable />
</el-form-item>
<el-form-item label="设备型号" prop="deviceModel">
- <el-input v-model="formData.deviceModel" placeholder="请输入" clearable />
+ <el-input class="ztzf-data-cockpit-search-input" v-model="formData.deviceModel"
+ placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="覆盖范围" prop="effectiveRangeKm">
- <el-input-number
- v-model="formData.effectiveRangeKm"
- :min="0"
- :precision="2"
- :controls="false"
- placeholder="请输入"
- />
+ <el-input-number class="ztzf-data-cockpit-search-input" v-model="formData.effectiveRangeKm" :min="0" :precision="2" :controls="false"
+ placeholder="请输入" />
</el-form-item>
</el-form>
+ <div class="footer">
+ <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ <el-button color="#284FE3" v-if="!dialogReadonly" type="primary" :loading="submitting"
+ :disabled="submitting" @click="handleSubmit">
+ 保存
+ </el-button>
+ </div>
</div>
</div>
- <template v-if="!dialogReadonly" #footer>
- <el-button @click="handleCancel">取消</el-button>
- <el-button type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit">保存</el-button>
- </template>
</el-dialog>
</template>
<script setup>
-import { computed, inject, ref } from 'vue'
+import { computed, inject, nextTick, ref } from 'vue'
import { ElMessage } from 'element-plus'
-import DeviceMapContainer from '@/components/map-container/device-map-container.vue'
import {
detectionRangeDetailApi,
detectionRangePageApi,
detectionRangeSubmitApi,
} from '@/api/detectionCountermeasure/detectionRange'
+import { PublicCesium } from '@/utils/cesium/publicCesium'
+import * as Cesium from 'cesium'
const initForm = () => ({
deviceId: '', // 侦测设备ID
@@ -99,17 +114,10 @@
const visible = ref(false) // 弹框显隐
const dialogMode = ref('add') // 弹框模式
const submitting = ref(false) // 提交中
-const mapDevices = ref([])
const deviceOptions = ref([])
const dialogReadonly = computed(() => dialogMode.value === 'view')
-const dialogTitle = computed(() => {
- if (dialogMode.value === 'edit') {
- return '编辑'
- } else if (dialogMode.value === 'view') {
- return '查看'
- }
- return '新增'
-})
+let viewer
+let redPointEntity
const formTitle = computed(() => {
if (dialogMode.value === 'edit') {
return '编辑侦测范围'
@@ -123,10 +131,11 @@
const rules = {
deviceId: [{ required: true, message: '请选择侦测设备', trigger: ['blur', 'change'] }],
deviceType: [{ required: true, message: '请选择设备类型', trigger: ['blur', 'change'] }],
+ deployLocation: [{ required: true, message: '请选择设备位置', trigger: ['blur', 'change'] }],
effectiveRangeKm: [{ required: true, message: '请输入覆盖范围', trigger: ['blur', 'change'] }],
}
-function handleDeviceChange(value) {
+function handleDeviceChange (value) {
const target = deviceOptions.value.find(item => item.value === value)
const raw = target?.raw
if (!raw) return
@@ -139,13 +148,24 @@
raw.deployLocation ?? raw.address ?? raw.location ?? raw.deployAddress ?? formData.value.deployLocation
}
+function getDeviceTypeLabel (value) {
+ const list = dictObj?.deviceType ?? []
+ const target = list.find(item => item.dictKey === value)
+ return target?.dictValue ?? value ?? '-'
+}
+
+function formatRange (value) {
+ if (value == null || value === '') return '-'
+ return `${value}km`
+}
+
// 关闭弹框
-function handleCancel() {
+function handleCancel () {
visible.value = false
}
// 提交新增/编辑
-async function handleSubmit() {
+async function handleSubmit () {
const isValid = await formRef.value?.validate().catch(() => false)
if (!isValid) return
submitting.value = true
@@ -166,13 +186,13 @@
}
// 加载详情
-async function loadDetail(id) {
+async function loadDetail (id) {
if (!id) return null
const res = await detectionRangeDetailApi({ id })
return res?.data?.data ?? null
}
-async function loadDeviceOptions() {
+async function loadDeviceOptions () {
const res = await detectionRangePageApi({
current: 1,
size: 200,
@@ -187,14 +207,75 @@
}
// 关闭后重置
-function handleClosed() {
+function handleClosed () {
formData.value = initForm()
+ viewer = null
+ redPointEntity = null
+}
+
+function setMapPoint (longitude, latitude) {
+ if (!viewer || longitude == null || latitude == null) return
+ if (!redPointEntity) {
+ redPointEntity = viewer.entities.add({
+ position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
+ point: {
+ color: Cesium.Color.RED,
+ pixelSize: 10,
+ },
+ label: {
+ text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
+ font: '14px',
+ fillColor: Cesium.Color.WHITE,
+ backgroundColor: Cesium.Color.BLACK.withAlpha(0.45),
+ backgroundPadding: new Cesium.Cartesian2(5, 5),
+ pixelOffset: new Cesium.Cartesian2(0, -20),
+ showBackground: true,
+ style: Cesium.LabelStyle.FILL_AND_OUTLINE,
+ horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
+ verticalOrigin: Cesium.VerticalOrigin.CENTER,
+ outlineWidth: 1,
+ outlineColor: Cesium.Color.WHITE,
+ eyeOffset: new Cesium.Cartesian3(0, 0, -20),
+ },
+ })
+ } else {
+ redPointEntity.position = Cesium.Cartesian3.fromDegrees(longitude, latitude)
+ redPointEntity.label.text = `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
+ }
+}
+
+function initMap () {
+ if (viewer) return
+ const publicCesiumInstance = new PublicCesium({
+ dom: 'detectionRangeMap',
+ flatMode: false,
+ terrain: true,
+ layerMode: 4,
+ boundary: false,
+ })
+ if (!dialogReadonly.value) {
+ publicCesiumInstance.addLeftClickEvent(null, handleMapClick)
+ }
+ viewer = publicCesiumInstance.getViewer()
+}
+
+function handleMapClick (click) {
+ const pos = click.position
+ const cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
+ if (!cartesian3) return
+ const carto = Cesium.Cartographic.fromCartesian(cartesian3)
+ const longitude = Number(Cesium.Math.toDegrees(carto.longitude).toFixed(6))
+ const latitude = Number(Cesium.Math.toDegrees(carto.latitude).toFixed(6))
+ formData.value.deployLocation = `${longitude}, ${latitude}`
+ setMapPoint(longitude, latitude)
}
// 打开弹框
-async function open({ mode, row } = {}) {
+async function open ({ mode, row } = {}) {
dialogMode.value = mode || 'add'
visible.value = true
+ await nextTick()
+ initMap()
if (dialogMode.value === 'add') {
await loadDeviceOptions()
formData.value = initForm()
@@ -212,114 +293,16 @@
if (currentId && !deviceOptions.value.some(item => item.value === currentId)) {
deviceOptions.value.unshift({ label: currentName || '-', value: currentId })
}
+ const location = formData.value.deployLocation
+ if (location) {
+ const [longitude, latitude] = location.split(',').map(item => Number(item.trim()))
+ if (!Number.isNaN(longitude) && !Number.isNaN(latitude)) {
+ setMapPoint(longitude, latitude)
+ }
+ }
}
defineExpose({ open })
</script>
-<style scoped lang="scss">
-.detection-range-dialog {
- :deep(.el-dialog) {
- width: 1100px;
- background: #071833;
- border-radius: 8px;
- }
-
- :deep(.el-dialog__header) {
- border-bottom: 1px solid rgba(70, 110, 180, 0.35);
- }
-}
-
-.dialog-body {
- display: flex;
- gap: 16px;
- min-height: 560px;
-}
-
-.dialog-map {
- position: relative;
- flex: 1;
- min-height: 520px;
- border-radius: 8px;
- overflow: hidden;
- border: 1px solid rgba(70, 110, 180, 0.35);
- background: #031024;
-}
-
-.map-toolbar {
- position: absolute;
- top: 16px;
- right: 16px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- z-index: 2;
-}
-
-.map-btn {
- width: 34px;
- height: 34px;
- border-radius: 6px;
- border: 1px solid rgba(80, 120, 180, 0.6);
- background: rgba(7, 18, 44, 0.9);
- cursor: pointer;
-}
-
-.map-mini-controls {
- position: absolute;
- left: 16px;
- bottom: 16px;
- display: flex;
- flex-direction: column;
- gap: 6px;
- z-index: 2;
-}
-
-.map-mini-btn {
- width: 28px;
- height: 28px;
- border-radius: 4px;
- border: 1px solid rgba(80, 120, 180, 0.6);
- background: rgba(7, 18, 44, 0.9);
- color: #cfe3ff;
- cursor: pointer;
-}
-
-.dialog-form {
- width: 300px;
- padding: 12px 12px 0;
- border-radius: 8px;
- background: linear-gradient(180deg, rgba(10, 28, 58, 0.95), rgba(6, 16, 34, 0.95));
- border: 1px solid rgba(70, 110, 180, 0.35);
- color: #d7e7ff;
-}
-
-.form-title {
- font-size: 14px;
- font-weight: 600;
- margin-bottom: 12px;
- letter-spacing: 1px;
-}
-
-:deep(.el-form-item__label) {
- color: rgba(210, 230, 255, 0.85);
-}
-
-:deep(.el-input__wrapper),
-:deep(.el-select__wrapper),
-:deep(.el-input-number .el-input__wrapper) {
- background: rgba(7, 20, 44, 0.85);
- border: 1px solid rgba(80, 120, 190, 0.45);
- box-shadow: none;
-}
-
-:deep(.el-input__inner) {
- color: #d5e6ff;
-}
-
-.suffix-action {
- font-size: 12px;
- color: #7fb6ff;
- cursor: pointer;
-}
-</style>
+<style scoped lang="scss"></style>
diff --git a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue
index 23bf370..f6bbaab 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue
@@ -1,4 +1,4 @@
-<template>
+<template>
<basic-container>
<el-form ref="queryParamsRef" :model="searchParams" class="ztzf-page-history-search">
<el-form-item label="名称" prop="deviceName">
@@ -43,24 +43,24 @@
<div class="ztzf-table-content ztzf-table-content-bg">
<el-table class="ztzf-data-cockpit-table" :data="list">
<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
- <el-table-column prop="deviceName" show-overflow-tooltip width="140" label="设备名称" />
- <el-table-column prop="deviceType" show-overflow-tooltip width="120" label="设备类型">
+ <el-table-column prop="deviceName" show-overflow-tooltip label="设备名称" />
+ <el-table-column prop="deviceType" show-overflow-tooltip label="设备类型">
<template v-slot="{ row }">
{{ getDictLabel(row.deviceType, dictObj.deviceType) }}
</template>
</el-table-column>
- <el-table-column show-overflow-tooltip width="160" label="部署位置">
+ <el-table-column show-overflow-tooltip label="部署位置">
<template v-slot="{ row }">
{{ formatDeployLocation(row) }}
</template>
</el-table-column>
- <el-table-column prop="deviceModel" show-overflow-tooltip width="110" label="型号" />
- <el-table-column show-overflow-tooltip width="120" label="覆盖范围">
+ <el-table-column prop="deviceModel" show-overflow-tooltip label="型号" />
+ <el-table-column show-overflow-tooltip label="覆盖范围">
<template v-slot="{ row }">
{{ formatRange(row) }}
</template>
</el-table-column>
- <el-table-column show-overflow-tooltip width="150" label="设备编码">
+ <el-table-column show-overflow-tooltip label="设备编码">
<template v-slot="{ row }">
{{ formatDeviceCode(row) }}
</template>
@@ -107,7 +107,7 @@
size: 10, // 每页大小
})
const searchParams = ref(initSearchParams()) // 查询参数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const total = ref(0) // 总数
const queryParamsRef = ref(null) // 查询表单实例
@@ -160,7 +160,12 @@
// 删除
async function handleDelete(row) {
- await ElMessageBox.confirm('确认删除该条数据吗?', '提示', { type: 'warning' })
+ await ElMessageBox.confirm('确认删除该条数据吗?', '提示', {
+ type: 'warning',
+ customClass: 'ztzf-page-view-message-box',
+ confirmButtonClass: 'ztzf-message-box-confirm',
+ cancelButtonClass: 'ztzf-message-box-cancel',
+ })
await detectionRangeSubmitApi({ id: row.id, effectiveRangeKm: 0 })
ElMessage.success('删除成功')
getList()
diff --git a/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/FormDiaLog.vue b/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/FormDiaLog.vue
index 4b0dec4..aaff6ae 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/FormDiaLog.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/FormDiaLog.vue
@@ -1,67 +1,120 @@
-<template>
- <el-dialog v-model="visible" :title="titleEnum[dialogMode]" :close-on-click-modal="false">
- <div v-if="readonly">
+<template>
+ <el-dialog
+ class="ztzf-page-view-dialog"
+ v-model="visible"
+ :title="titleEnum[dialogMode]"
+ :close-on-click-modal="false"
+ @closed="handleClosed"
+ destroy-on-close
+ >
+ <div v-if="readonly" class="detail-container">
+ <div class="detail-title">配置详情</div>
<el-row>
<el-col :span="12">
- <div>起始扇区: {{ formData.startSector }}</div>
+ <div class="label">起始扇区</div>
+ <div class="val">{{ formData.startSector }}</div>
</el-col>
<el-col :span="12">
- <div>终止扇区: {{ formData.endSector }}</div>
+ <div class="label">终止扇区</div>
+ <div class="val">{{ formData.endSector }}</div>
</el-col>
<el-col :span="12">
- <div>搜索模式: {{ formData.searchMode }}</div>
+ <div class="label">搜索模式</div>
+ <div class="val">{{ formData.searchMode }}</div>
</el-col>
<el-col :span="12">
- <div>雷达频点: {{ formData.radarFreq }}</div>
+ <div class="label">雷达频点</div>
+ <div class="val">{{ formData.radarFreq }}</div>
</el-col>
<el-col :span="12">
- <div>伺服模式: {{ formData.servoMode }}</div>
+ <div class="label">伺服模式</div>
+ <div class="val">{{ formData.servoMode }}</div>
</el-col>
<el-col :span="12">
- <div>伺服数据模式: {{ formData.servoDataMode }}</div>
+ <div class="label">伺服数据模式</div>
+ <div class="val">{{ formData.servoDataMode }}</div>
</el-col>
<el-col :span="12">
- <div>轴选择: {{ formData.axisSelect }}</div>
+ <div class="label">轴选择</div>
+ <div class="val">{{ formData.axisSelect }}</div>
</el-col>
<el-col :span="12">
- <div>伺服转速度: {{ formData.servoTurnSpeed }}</div>
+ <div class="label">伺服转速度</div>
+ <div class="val">{{ formData.servoTurnSpeed }}</div>
</el-col>
</el-row>
</div>
- <el-form v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly" label-width="120px">
+
+ <el-form v-else ref="formRef" :model="formData" :rules="rules" label-width="120px" class="dialog-form">
<el-row>
<el-col :span="12">
<el-form-item label="起始扇区" prop="startSector">
- <el-input v-model="formData.startSector" maxlength="50" placeholder="请输入" clearable />
+ <el-input
+ class="ztzf-data-cockpit-search-input"
+ v-model="formData.startSector"
+ maxlength="50"
+ placeholder="请输入"
+ clearable
+ />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="终止扇区" prop="endSector">
- <el-input v-model="formData.endSector" maxlength="50" placeholder="请输入" clearable />
+ <el-input
+ class="ztzf-data-cockpit-search-input"
+ v-model="formData.endSector"
+ maxlength="50"
+ placeholder="请输入"
+ clearable
+ />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="搜索模式" prop="searchMode">
- <el-select v-model="formData.searchMode" placeholder="请选择" clearable>
+ <el-select
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.searchMode"
+ placeholder="请选择"
+ clearable
+ >
<el-option v-for="item in searchModeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="雷达频点" prop="radarFreq">
- <el-input v-model="formData.radarFreq" maxlength="50" placeholder="请输入" clearable />
+ <el-input
+ class="ztzf-data-cockpit-search-input"
+ v-model="formData.radarFreq"
+ maxlength="50"
+ placeholder="请输入"
+ clearable
+ />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="伺服模式" prop="servoMode">
- <el-select v-model="formData.servoMode" placeholder="请选择" clearable>
+ <el-select
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.servoMode"
+ placeholder="请选择"
+ clearable
+ >
<el-option v-for="item in servoModeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="伺服数据模式" prop="servoDataMode">
- <el-select v-model="formData.servoDataMode" placeholder="请选择" clearable>
+ <el-select
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.servoDataMode"
+ placeholder="请选择"
+ clearable
+ >
<el-option
v-for="item in servoDataModeOptions"
:key="item.value"
@@ -73,22 +126,41 @@
</el-col>
<el-col :span="12">
<el-form-item label="轴选择" prop="axisSelect">
- <el-select v-model="formData.axisSelect" placeholder="请选择" clearable>
+ <el-select
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.axisSelect"
+ placeholder="请选择"
+ clearable
+ >
<el-option v-for="item in axisSelectOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="伺服转速度" prop="servoTurnSpeed">
- <el-input v-model="formData.servoTurnSpeed" maxlength="50" placeholder="请输入" clearable />
+ <el-input
+ class="ztzf-data-cockpit-search-input"
+ v-model="formData.servoTurnSpeed"
+ maxlength="50"
+ placeholder="请输入"
+ clearable
+ />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
- <el-button @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
- <el-button v-if="!readonly" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit">
- 确定
+ <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button
+ color="#284FE3"
+ v-if="!readonly"
+ type="primary"
+ :loading="submitting"
+ :disabled="submitting"
+ @click="handleSubmit"
+ >
+ 保存
</el-button>
</template>
</el-dialog>
@@ -97,7 +169,7 @@
<script setup>
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
-import { fwDeviceConfigDetailApi, fwDeviceConfigSubmitApi } from './deviceAppConfigApi'
+import { fwDeviceConfigSubmitApi } from './deviceAppConfigApi'
import { fieldRules } from '@ztzf/utils'
const configKeys = [
@@ -144,7 +216,7 @@
{ label: '扇区', value: '扇区' },
{ label: '定位', value: '定位' },
]
-const servoDataModeOptions = [{ label: '编码器', value: '编码器' }]
+const servoDataModeOptions = [{ label: '编码模式', value: '编码模式' }]
const axisSelectOptions = [{ label: '方位', value: '方位' }]
const rules = {
@@ -171,6 +243,10 @@
visible.value = false
}
+function handleClosed() {
+ formData.value = initForm()
+}
+
// 提交更新
async function handleSubmit() {
const isValid = await formRef.value?.validate().catch(() => false)
diff --git a/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue b/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue
index 2a7ed1a..0783d2a 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue
@@ -1,59 +1,74 @@
-<template>
+<template>
<basic-container>
- <el-form ref="queryParamsRef" :model="searchParams">
- <el-row :gutter="16">
- <el-col :span="4">
- <el-form-item label="设备名称" prop="deviceName">
- <el-input v-model="searchParams.deviceName" placeholder="请输入" clearable @clear="handleSearch" />
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <el-form-item label="设备类型" prop="deviceType">
- <el-select v-model="searchParams.deviceType" placeholder="请选择" clearable @change="handleSearch">
- <el-option
- v-for="item in dictObj.deviceType"
- :key="item.dictKey"
- :label="item.dictValue"
- :value="item.dictKey"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <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 ref="queryParamsRef" :model="searchParams" class="ztzf-page-history-search">
+ <el-form-item label="设备名称" prop="deviceName">
+ <el-input
+ class="ztzf-data-cockpit-search-input"
+ v-model="searchParams.deviceName"
+ placeholder="请输入"
+ clearable
+ @clear="handleSearch"
+ />
+ </el-form-item>
+
+ <el-form-item label="设备类型" prop="deviceType">
+ <el-select
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
+ v-model="searchParams.deviceType"
+ placeholder="请选择"
+ clearable
+ @change="handleSearch"
+ >
+ <el-option
+ v-for="item in dictObj.deviceType"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey"
+ />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item class="history-search-actions">
+ <el-button :icon="RefreshRight" @click="resetForm"></el-button>
+ <el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button>
+ </el-form-item>
</el-form>
- <el-table v-loading="loading" :data="list">
- <el-table-column type="index" width="60" label="序号" />
- <el-table-column prop="deviceType" label="设备类型">
- <template v-slot="{ row }">
- {{ getDictLabel(row.deviceType, dictObj.deviceType) }}
- </template>
- </el-table-column>
- <el-table-column prop="deviceModel" label="设备型号" />
- <el-table-column prop="deviceName" label="设备名称" />
- <el-table-column prop="deviceSn" label="设备编码" />
- <el-table-column prop="deployLocation" label="部署位置" />
- <el-table-column prop="manufacturer" label="厂商" />
- <el-table-column prop="contactPhone" label="联系方式" />
- <el-table-column prop="sceneName" label="所属场景" />
- <el-table-column label="操作">
- <template v-slot="{ row }">
- <el-link @click="openForm('edit', row)" type="warning">配置接口</el-link>
- </template>
- </el-table-column>
- </el-table>
- <div>
- <el-pagination
- v-model:current-page="searchParams.current"
- v-model:page-size="searchParams.size"
- :total="total"
- @change="getList"
- />
+
+ <div class="ztzf-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
+ <div class="ztzf-table-content ztzf-table-content-bg">
+ <el-table class="ztzf-data-cockpit-table" :data="list">
+ <el-table-column type="index" width="64" show-overflow-tooltip label="序号" />
+ <el-table-column prop="deviceType" show-overflow-tooltip label="设备类型">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.deviceType, dictObj.deviceType) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="deviceModel" show-overflow-tooltip label="设备型号" />
+ <el-table-column prop="deviceName" show-overflow-tooltip label="设备名称" />
+ <el-table-column prop="deviceSn" show-overflow-tooltip label="设备编码" />
+ <el-table-column prop="deployLocation" show-overflow-tooltip label="部署位置" />
+ <el-table-column prop="manufacturer" show-overflow-tooltip label="厂商" />
+ <el-table-column prop="contactPhone" show-overflow-tooltip label="联系方式" />
+ <el-table-column prop="sceneName" show-overflow-tooltip label="所属场景" />
+ <el-table-column label="操作" class-name="operation-btns">
+ <template v-slot="{ row }">
+ <el-link @click="openForm('edit', row)" type="warning">配置接口</el-link>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <div class="ztzf-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>
<FormDiaLog v-if="dialogVisible" v-model="dialogVisible" ref="dialogRef" @success="getList" />
@@ -61,6 +76,7 @@
</template>
<script setup>
+import { RefreshRight, Search } from '@element-plus/icons-vue'
import { nextTick, onMounted, provide, ref } from 'vue'
import FormDiaLog from './FormDiaLog.vue'
import { getDictionaryByCode } from '@/api/system/dictbiz'
@@ -77,7 +93,7 @@
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const queryParamsRef = ref(null) // 查询表单实例
const dialogRef = ref(null) // 弹框实例
diff --git a/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue b/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue
index 07012ef..9ec734f 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/FormDiaLog.vue
@@ -1,56 +1,101 @@
-<template>
- <el-dialog v-model="visible" :title="titleEnum[dialogMode]">
- <div v-if="readonly">
+<template>
+ <el-dialog
+ class="ztzf-page-view-dialog"
+ v-model="visible"
+ :title="titleEnum[dialogMode]"
+ destroy-on-close
+ :close-on-click-modal="false"
+ @closed="handleClosed"
+ >
+ <div v-if="readonly" class="detail-container">
+ <div class="detail-title">调度详情</div>
<el-row>
<el-col :span="12">
- <div>设备名称: {{ formData.deviceName }}</div>
+ <div class="label">设备名称</div>
+ <div class="val">{{ formData.deviceName }}</div>
</el-col>
<el-col :span="12">
- <div>设备编码: {{ formData.deviceSn }}</div>
+ <div class="label">设备编码</div>
+ <div class="val">{{ formData.deviceSn }}</div>
</el-col>
<el-col :span="12">
- <div>场景: {{ formData.defenseSceneName }}</div>
+ <div class="label">场景</div>
+ <div class="val">{{ formData.defenseSceneName }}</div>
</el-col>
<el-col :span="12">
- <div>区域: {{ formData.areaDivideName }}</div>
+ <div class="label">区域</div>
+ <div class="val">{{ formData.areaDivideName }}</div>
</el-col>
<el-col :span="12">
- <div>调度人员: {{ formData.dispatchUser }}</div>
+ <div class="label">调度人员</div>
+ <div class="val">{{ formData.dispatchUser }}</div>
</el-col>
<el-col :span="12">
- <div>调度位置: {{ formData.longitude }}, {{ formData.latitude }}</div>
+ <div class="label">调度位置</div>
+ <div class="val">{{ formData.longitude }}, {{ formData.latitude }}</div>
</el-col>
<el-col :span="12">
- <div>调度日期: {{ formData.dispatchStartTime }} ~ {{ formData.dispatchEndTime }}</div>
+ <div class="label">调度日期</div>
+ <div class="val">{{ formData.dispatchStartTime }} ~ {{ formData.dispatchEndTime }}</div>
</el-col>
<el-col :span="24">
- <div>调度内容: {{ formData.dispatchContent }}</div>
+ <div class="label">调度内容</div>
+ <div class="val">{{ formData.dispatchContent }}</div>
</el-col>
<el-col :span="24">
- <div>调度结果: {{ formData.dispatchResult }}</div>
+ <div class="label">调度结果</div>
+ <div class="val">{{ formData.dispatchResult }}</div>
</el-col>
<el-col :span="24">
- <div>处理措施: {{ formData.disposeMeasure }}</div>
+ <div class="label">处理措施</div>
+ <div class="val">{{ formData.disposeMeasure }}</div>
</el-col>
</el-row>
</div>
- <el-form v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly" label-width="100px">
+ <el-form
+ v-else
+ ref="formRef"
+ :model="formData"
+ :rules="rules"
+ :disabled="readonly"
+ label-width="100px"
+ class="dialog-form"
+ >
<el-row>
<el-col :span="12">
<el-form-item label="设备" prop="deviceId">
- <el-select v-model="formData.deviceId" placeholder="请选择" clearable @change="handleDeviceChange">
+ <el-select
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.deviceId"
+ placeholder="请选择"
+ clearable
+ @change="handleDeviceChange"
+ >
<el-option v-for="item in deviceList" :key="item.id" :label="item.deviceName" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备编码" prop="deviceSn">
- <el-input v-model="formData.deviceSn" disabled placeholder="选择设备后自动带出" />
+ <el-input
+ class="ztzf-data-cockpit-search-input"
+ v-model="formData.deviceSn"
+ disabled
+ placeholder="选择设备后自动带出"
+ />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="场景" prop="defenseSceneId">
- <el-select v-model="formData.defenseSceneId" placeholder="请选择" clearable @change="handleSceneChange">
+ <el-select
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
+ v-model="formData.defenseSceneId"
+ placeholder="请选择"
+ clearable
+ @change="handleSceneChange"
+ >
<el-option v-for="item in sceneList" :key="item.id" :label="item.sceneName" :value="item.id" />
</el-select>
</el-form-item>
@@ -58,6 +103,8 @@
<el-col :span="12">
<el-form-item label="区域" prop="areaDivideId">
<el-select
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
v-model="formData.areaDivideId"
placeholder="请先选择场景"
clearable
@@ -70,12 +117,20 @@
</el-col>
<el-col :span="12">
<el-form-item label="调度人员" prop="dispatchUser">
- <el-input v-model="formData.dispatchUser" maxlength="50" placeholder="请输入" clearable />
+ <el-input
+ class="ztzf-data-cockpit-search-input"
+ v-model="formData.dispatchUser"
+ maxlength="50"
+ placeholder="请输入"
+ clearable
+ />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="调度日期" prop="dispatchDateRange">
<el-date-picker
+ class="ztzf-data-cockpit-date-picker"
+ popper-class="ztzf-data-cockpit-date-picker-popper"
v-model="formData.dispatchDateRange"
type="datetimerange"
range-separator="至"
@@ -87,7 +142,7 @@
</el-col>
<el-col :span="24">
<el-form-item label="调度位置" prop="longitude">
-<!-- <el-button @click="selectLocation" style="width: 100%">-->
+ <!-- <el-button @click="selectLocation" style="width: 100%">-->
<el-button style="width: 100%">
{{
formData.longitude && formData.latitude ? `${formData.longitude}, ${formData.latitude}` : '选择区域后自动带出'
@@ -98,6 +153,7 @@
<el-col :span="24">
<el-form-item label="调度内容" prop="dispatchContent">
<el-input
+ class="ztzf-data-cockpit-search-input"
v-model="formData.dispatchContent"
type="textarea"
:rows="3"
@@ -110,6 +166,7 @@
<el-col :span="24">
<el-form-item label="调度结果" prop="dispatchResult">
<el-input
+ class="ztzf-data-cockpit-search-input"
v-model="formData.dispatchResult"
type="textarea"
:rows="3"
@@ -122,6 +179,7 @@
<el-col :span="24">
<el-form-item label="处理措施" prop="disposeMeasure">
<el-input
+ class="ztzf-data-cockpit-search-input"
v-model="formData.disposeMeasure"
type="textarea"
:rows="3"
@@ -135,9 +193,16 @@
</el-form>
<template #footer>
- <el-button @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
- <el-button v-if="!readonly" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit">
- 确定
+ <el-button color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button
+ color="#284FE3"
+ v-if="!readonly"
+ type="primary"
+ :loading="submitting"
+ :disabled="submitting"
+ @click="handleSubmit"
+ >
+ 保存
</el-button>
</template>
@@ -152,7 +217,7 @@
</template>
<script setup>
-import { computed, nextTick, ref, inject } from 'vue'
+import { computed, nextTick, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { fwTaskScheduleDetailApi, fwTaskScheduleSubmitApi } from './taskScheduleApi'
import { fwAreaDivideListApi } from '@/views/areaManage/partition/partitionApi'
@@ -249,6 +314,10 @@
visible.value = false
}
+function handleClosed() {
+ formData.value = initForm()
+}
+
// 提交新增/编辑
async function handleSubmit() {
const isValid = await formRef.value?.validate().catch(() => false)
diff --git a/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/index.vue b/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/index.vue
index 5cc07be..002545b 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/index.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/taskSchedule/index.vue
@@ -1,9 +1,10 @@
-<template>
+<template>
<basic-container>
<el-form ref="queryParamsRef" :model="searchParams" class="ztzf-page-history-search">
<el-form-item label="设备名称" prop="deviceId">
<el-select
- class="ztzf-data-cockpit-search-input"
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
v-model="searchParams.deviceId"
placeholder="请选择"
clearable
@@ -13,9 +14,11 @@
<el-option v-for="item in deviceList" :key="item.id" :label="item.deviceName" :value="item.id" />
</el-select>
</el-form-item>
+
<el-form-item label="设备类型" prop="deviceType">
<el-select
- class="ztzf-data-cockpit-search-input"
+ class="ztzf-data-cockpit-select"
+ popper-class="ztzf-data-cockpit-select-popper"
v-model="searchParams.deviceType"
placeholder="请选择"
clearable
@@ -30,6 +33,7 @@
/>
</el-select>
</el-form-item>
+
<el-form-item label="设备编号" prop="deviceSn">
<el-input
class="ztzf-data-cockpit-search-input"
@@ -48,7 +52,7 @@
<div class="ztzf-table-toolbar">
<el-button :icon="Plus" color="#284FE3" type="primary" @click="openForm('add')">新增</el-button>
- <el-button type="danger" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
+ <el-button :icon="Delete" color="#1A2652" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
</div>
<div class="ztzf-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
@@ -59,17 +63,17 @@
<el-table-column prop="deviceName" show-overflow-tooltip width="140" label="设备名称" />
<el-table-column prop="deviceSn" show-overflow-tooltip width="140" label="设备编码" />
<!-- <el-table-column prop="deviceStatus" show-overflow-tooltip width="100" label="设备状态">
- <template v-slot="{ row }">
- {{ getDictLabel(row.deviceStatus, dictObj.deviceStatus) }}
- </template>
- </el-table-column>-->
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.deviceStatus, dictObj.deviceStatus) }}
+ </template>
+ </el-table-column>-->
<el-table-column prop="areaName" show-overflow-tooltip width="120" label="区域" />
<el-table-column prop="defenseZoneName" show-overflow-tooltip width="120" label="场景" />
- <el-table-column show-overflow-tooltip width="200" label="调度位置">
+ <el-table-column show-overflow-tooltip label="调度位置">
<template v-slot="{ row }">{{ row.longitude }}, {{ row.latitude }}</template>
</el-table-column>
<el-table-column prop="dispatchUser" show-overflow-tooltip width="100" label="调度人员" />
- <el-table-column show-overflow-tooltip width="180" label="调度日期">
+ <el-table-column show-overflow-tooltip label="调度日期">
<template v-slot="{ row }">
{{ formatDispatchDate(row) }}
</template>
@@ -110,7 +114,7 @@
</template>
<script setup>
-import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
+import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue'
import { nextTick, onMounted, ref, provide } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fwTaskSchedulePageApi, fwTaskScheduleRemoveApi } from './taskScheduleApi'
@@ -130,7 +134,7 @@
})
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const selectedIds = ref([]) // 勾选的ID列表
const queryParamsRef = ref(null) // 查询表单实例
diff --git a/applications/drone-command/src/views/job/components/JobRelatedEvents.vue b/applications/drone-command/src/views/job/components/JobRelatedEvents.vue
index 9e03ac7..a2d31f5 100644
--- a/applications/drone-command/src/views/job/components/JobRelatedEvents.vue
+++ b/applications/drone-command/src/views/job/components/JobRelatedEvents.vue
@@ -144,7 +144,7 @@
params.value.ai_types = val
handleSearch()
}
-const loading = ref(false)
+const loading = ref(true)
const list = ref()
const params = ref({
way_line_job_info_id: null,
diff --git a/applications/drone-command/src/views/job/components/TaskIntermediateContent/AddTask.vue b/applications/drone-command/src/views/job/components/TaskIntermediateContent/AddTask.vue
index 78b7ca1..fbff465 100644
--- a/applications/drone-command/src/views/job/components/TaskIntermediateContent/AddTask.vue
+++ b/applications/drone-command/src/views/job/components/TaskIntermediateContent/AddTask.vue
@@ -151,7 +151,7 @@
})
const isShowAddTask = defineModel('show')
-const loading = ref(false)
+const loading = ref(true)
// 禁用当天之前的日期
const disabledDate = time => {
diff --git a/applications/drone-command/src/views/job/components/executeNestDetails.vue b/applications/drone-command/src/views/job/components/executeNestDetails.vue
index 03a9a5f..3ce91b2 100644
--- a/applications/drone-command/src/views/job/components/executeNestDetails.vue
+++ b/applications/drone-command/src/views/job/components/executeNestDetails.vue
@@ -79,7 +79,7 @@
const deviseName = ref(null)
const devisejobInfoId = ref('')
const devisebatchNo = ref(null)
-const loading = ref(false)
+const loading = ref(true)
const isShowContinuingToFly = code => {
return ContinuingToFly.find(i => i.code === code)
}
diff --git a/applications/drone-command/src/views/layerManagement/components/leftList.vue b/applications/drone-command/src/views/layerManagement/components/leftList.vue
index b200cf5..6efffd5 100644
--- a/applications/drone-command/src/views/layerManagement/components/leftList.vue
+++ b/applications/drone-command/src/views/layerManagement/components/leftList.vue
@@ -123,7 +123,7 @@
dkbh: '',
patchesName: '',
});
-const loading = ref(false);
+const loading = ref(true);
const treeData = ref([]);
const treeAllData = ref([]);
// 存储过滤后显示的节点ID(返回true的节点)
diff --git a/applications/drone-command/src/views/layerManagement/components/rightEdit.vue b/applications/drone-command/src/views/layerManagement/components/rightEdit.vue
index b640471..059d7e8 100644
--- a/applications/drone-command/src/views/layerManagement/components/rightEdit.vue
+++ b/applications/drone-command/src/views/layerManagement/components/rightEdit.vue
@@ -156,7 +156,7 @@
geo_data: '',
});
-const loading = ref(false);
+const loading = ref(true);
const detailData = ref(null);
const fenceArea = ref(0);
detailData.value = layerParams.value.editDetailData;
diff --git a/applications/drone-command/src/views/recordManage/alarmRecords/index.vue b/applications/drone-command/src/views/recordManage/alarmRecords/index.vue
index ab7b175..5939a81 100644
--- a/applications/drone-command/src/views/recordManage/alarmRecords/index.vue
+++ b/applications/drone-command/src/views/recordManage/alarmRecords/index.vue
@@ -56,7 +56,7 @@
</el-form>
<div class="ztzf-table-toolbar">
- <el-button :icon="Download" type="primary" @click="exportFile" :loading="exportLoading">导出</el-button>
+ <el-button :icon="Download" color="#1A2652" type="primary" @click="exportFile" :loading="exportLoading">导出</el-button>
</div>
<div class="ztzf-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
@@ -64,18 +64,18 @@
<el-table class="ztzf-data-cockpit-table" :data="list">
<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
<el-table-column prop="droneName" show-overflow-tooltip width="140" label="无人机名称" />
- <el-table-column prop="droneSerialNo" show-overflow-tooltip width="160" label="无人机序列号" />
- <el-table-column prop="alarmTime" show-overflow-tooltip width="160" label="告警时间" />
- <el-table-column prop="areaName" show-overflow-tooltip width="120" label="区域" />
- <el-table-column prop="triggerReason" show-overflow-tooltip width="160" label="触发原因" />
- <el-table-column prop="deviceName" show-overflow-tooltip width="160" label="侦测反制设备" />
- <el-table-column prop="droneType" show-overflow-tooltip width="120" label="设备类型">
+ <el-table-column prop="droneSerialNo" show-overflow-tooltip label="无人机序列号" />
+ <el-table-column prop="alarmTime" show-overflow-tooltip label="告警时间" />
+ <el-table-column prop="areaName" show-overflow-tooltip label="区域" />
+ <el-table-column prop="triggerReason" show-overflow-tooltip label="触发原因" />
+ <el-table-column prop="deviceName" show-overflow-tooltip label="侦测反制设备" />
+ <el-table-column prop="droneType" show-overflow-tooltip label="设备类型">
<template v-slot="{ row }">
{{ getDeviceTypeName(row.droneType) }}
</template>
</el-table-column>
- <el-table-column prop="deviceSn" show-overflow-tooltip width="140" label="设备编码" />
- <el-table-column show-overflow-tooltip width="120" label="设备状态">
+ <el-table-column prop="deviceSn" show-overflow-tooltip label="设备编码" />
+ <el-table-column show-overflow-tooltip label="设备状态">
<template v-slot="{ row }">
{{ getDictLabel(row.deviceStatus, dictObj.deviceStatus) }}
</template>
@@ -116,7 +116,7 @@
})
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const queryParamsRef = ref(null) // 查询表单实例
const dialogRef = ref(null) // 弹框实例
diff --git a/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue b/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
index 4e5f403..cc0bd5c 100644
--- a/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
+++ b/applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
@@ -1,32 +1,59 @@
-<template>
- <el-dialog v-model="visible" :title="dialogTitle" @closed="handleClosed" destroy-on-close>
- <div class="bodyBox">
- <div class="leftMap ztzf-cesium" id="leftMapContainer"></div>
- <div class="rightInfo">
- <div>
- <div>无人机名称:{{ formData.droneName }}</div>
- <div>序列号: {{ formData.serialNo }}</div>
- <div>告警时间: {{ formData.alarmTime }}</div>
- <div>触发原因: {{ formData.triggerType }}</div>
- <div>停留时长: {{ formData.stayDuration }}</div>
+<template>
+ <el-dialog class="ztzf-page-map-view-dialog" v-model="visible" :show-close="false"
+ :close-on-click-modal="false" @closed="handleClosed" destroy-on-close>
+ <div class="dialog-container">
+ <div class="left-container">
+ <div class="leftMap ztzf-cesium" id="leftMapContainer"></div>
+ </div>
+ <div class="right-container">
+ <div class="header">{{ dialogTitle }}</div>
+ <div class="dialog-container">
+ <div class="detail-title">轨迹信息</div>
+ <el-row>
+ <el-col :span="24">
+ <div class="label">无人机名称</div>
+ <div class="val">{{ formData.droneName }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">序列号</div>
+ <div class="val">{{ formData.serialNo }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">告警时间</div>
+ <div class="val">{{ formData.alarmTime }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">触发原因</div>
+ <div class="val">{{ formData.triggerType }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">停留时长</div>
+ <div class="val">{{ formData.stayDuration }}</div>
+ </el-col>
+ </el-row>
+ <div class="detail-title">轨迹点</div>
+ <div class="ztzf-table-container">
+ <div class="ztzf-table-content">
+ <el-table class="ztzf-data-cockpit-table" :data="list">
+ <el-table-column type="index" width="60" label="序号" />
+ <el-table-column label="经纬度">
+ <template #default="{ row }">{{ row.longitude }},{{ row.latitude }}</template>
+ </el-table-column>
+ <el-table-column prop="createTime" label="时间" />
+ </el-table>
+ </div>
+ </div>
</div>
- <el-table :data="list">
- <el-table-column type="index" width="60" label="序号" />
- <el-table-column label="经纬度">
- <template #default="{ row }">{{ row.longitude }},{{ row.latitude }}</template>
- </el-table-column>
- <el-table-column prop="createTime" label="时间" />
- </el-table>
+ <div class="footer">
+ <el-button color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+ </div>
</div>
</div>
- <template #footer>
- <el-button @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
- </template>
</el-dialog>
</template>
<script setup>
-import { computed, ref } from 'vue'
+import { computed, nextTick, ref } from 'vue'
import { fwDroneFlightRecordDetailApi } from '@/views/recordManage/historyTracks/fwDroneFlightRecord'
import { fwDroneFlightRecordDetailPageApi } from '@/views/recordManage/historyTracks/flyTrajectory'
import { PublicCesium } from '@/utils/cesium/publicCesium'
@@ -57,12 +84,12 @@
})
// 关闭弹框
-function handleCancel() {
+function handleCancel () {
visible.value = false
}
// 加载详情
-async function loadDetail() {
+async function loadDetail () {
if (!formData.value.id) return
const res = await fwDroneFlightRecordDetailApi({ id: formData.value.id })
const res1 = await fwDroneFlightRecordDetailPageApi({ flightRecordId: formData.value.id })
@@ -72,11 +99,11 @@
}
// 角度转c3
-function degreesToC3({ longitude, latitude, height = 0 }) {
+function degreesToC3 ({ longitude, latitude, height = 0 }) {
return Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
}
// 渲染航线
-function drawFlightPath() {
+function drawFlightPath () {
const positions = list.value.map(p => degreesToC3(p))
droneLineEntity && geometricSource.entities.remove(droneLineEntity)
const color = Cesium.Color.fromCssColorString('#00D690')
@@ -92,12 +119,17 @@
}
// 关闭后重置
-function handleClosed() {
+function handleClosed () {
formData.value = initForm()
+ list.value = []
+ viewer = null
+ geometricSource = null
+ droneLineEntity = null
}
let viewer, geometricSource, droneLineEntity
-function initMap() {
+function initMap () {
+ if (viewer) return
const publicCesiumInstance = new PublicCesium({
dom: 'leftMapContainer',
flatMode: false,
@@ -111,12 +143,11 @@
}
// 打开弹框
-async function open({ mode, row } = {}) {
+async function open ({ mode, row } = {}) {
dialogMode.value = mode || 'add'
visible.value = true
- await nextTick(() => {
- initMap()
- })
+ await nextTick()
+ initMap()
if (dialogMode.value === 'add') {
formData.value = initForm()
} else {
@@ -127,16 +158,5 @@
defineExpose({ open })
</script>
-<style scoped lang="scss">
-.bodyBox {
- display: flex;
- .rightInfo {
- width: 50%;
- height: 500px;
- }
- .leftMap {
- width: 50%;
- height: 500px;
- }
-}
-</style>
+
+<style scoped lang="scss"></style>
diff --git a/applications/drone-command/src/views/recordManage/historyTracks/index.vue b/applications/drone-command/src/views/recordManage/historyTracks/index.vue
index e7a13fe..ae58333 100644
--- a/applications/drone-command/src/views/recordManage/historyTracks/index.vue
+++ b/applications/drone-command/src/views/recordManage/historyTracks/index.vue
@@ -39,7 +39,7 @@
</el-form>
<div class="ztzf-table-toolbar">
- <el-button :icon="Download" type="primary" @click="exportFile" :loading="exportLoading">导出</el-button>
+ <el-button :icon="Download" color="#1A2652" type="primary" @click="exportFile" :loading="exportLoading">导出</el-button>
</div>
<div class="ztzf-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
@@ -47,11 +47,11 @@
<el-table class="ztzf-data-cockpit-table" :data="list">
<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
<el-table-column prop="droneName" show-overflow-tooltip width="140" label="无人机名称" />
- <el-table-column prop="serialNo" show-overflow-tooltip width="160" label="序列号" />
- <el-table-column prop="alarmTime" show-overflow-tooltip width="160" label="告警时间" />
- <el-table-column prop="areaName" show-overflow-tooltip width="120" label="区域" />
- <el-table-column prop="triggerType" show-overflow-tooltip width="160" label="触发原因" />
- <el-table-column show-overflow-tooltip width="140" label="停留时长">
+ <el-table-column prop="serialNo" show-overflow-tooltip label="序列号" />
+ <el-table-column prop="alarmTime" show-overflow-tooltip label="告警时间" />
+ <el-table-column prop="areaName" show-overflow-tooltip label="区域" />
+ <el-table-column prop="triggerType" show-overflow-tooltip label="触发原因" />
+ <el-table-column show-overflow-tooltip label="停留时长">
<template v-slot="{ row }">
{{ formatStayDuration(row.stayDuration) }}
</template>
@@ -95,7 +95,7 @@
})
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
-const loading = ref(false) // 列表加载中
+const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const queryParamsRef = ref(null) // 查询表单实例
const dateRange = ref([])
diff --git a/applications/drone-command/src/views/resource/components/spotDetails.vue b/applications/drone-command/src/views/resource/components/spotDetails.vue
index 6b346d1..ad71420 100644
--- a/applications/drone-command/src/views/resource/components/spotDetails.vue
+++ b/applications/drone-command/src/views/resource/components/spotDetails.vue
@@ -168,7 +168,7 @@
const viewInstance = shallowRef(null);
const homeViewer = shallowRef(null);
let tbJwdList = [];
-const loading = ref(false);
+const loading = ref(true);
const tableLoading = ref(true);
const tableData = ref([]);
const AlltableData = ref([]);
diff --git a/applications/drone-command/src/views/tickets/ticket.vue b/applications/drone-command/src/views/tickets/ticket.vue
index a5bd6ed..4b6ef13 100644
--- a/applications/drone-command/src/views/tickets/ticket.vue
+++ b/applications/drone-command/src/views/tickets/ticket.vue
@@ -509,7 +509,7 @@
const detailVisible = ref(false)
const currentDetail = ref({})
const departmentUsers = ref({})
-const loading = ref(false)
+const loading = ref(true)
const globalCounts = ref({})
const mapLoaded = ref(false)
const isFetching = ref(false)
diff --git a/applications/drone-command/src/views/tickets/ticketComponent/DispatchDialog.vue b/applications/drone-command/src/views/tickets/ticketComponent/DispatchDialog.vue
index 11046da..1ee5389 100644
--- a/applications/drone-command/src/views/tickets/ticketComponent/DispatchDialog.vue
+++ b/applications/drone-command/src/views/tickets/ticketComponent/DispatchDialog.vue
@@ -88,7 +88,7 @@
});
const emit = defineEmits(['update:modelValue', 'dispatch-success', 'dispatch-error']);
const formRef = ref(null); // 表单引用
-const loading = ref(false);
+const loading = ref(true);
const form = ref({
department: '',
handler: ''
diff --git a/applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue b/applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue
index 4a38bfe..ab07b87 100644
--- a/applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue
+++ b/applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue
@@ -1,53 +1,55 @@
<template>
- <div class="call-container">
- <div class="content-wrapper">
-<!-- <div class="input-group">-->
-<!-- <div class="input-item">-->
-<!-- <span class="input-label">我的 userId:</span>-->
-<!-- <input v-model="uid" class="input-field" />-->
-<!-- </div>-->
-<!-- -->
-<!-- <div class="input-item">-->
-<!-- <span class="input-label">对方 userId:</span>-->
-<!-- <input v-model="peerUid" class="input-field" />-->
-<!-- </div>-->
-<!-- </div>-->
-
- <div class="button-group" v-if="false">
- <button @click="connectWS" :disabled="connected" class="btn btn-connect">连接WS</button>
- </div>
- <!-- 头像-->
- <div class="contactAvatar">
- <img :src="defaultAvatar" alt=""></img>
- </div>
- <div class="status-group">
-<!-- <strong class="status-text">状态:<span :style="{color: getStateColor(state)}">{{ state }}</span></strong>-->
- <strong v-if="state==='in-call'" class="status-text">通话时长:{{ durationText }}</strong>
- </div>
- <div class="button-group">
- <button v-if="false" @click="requestCall" :disabled="!connected || state!=='idle'" class="btn btn-call">呼叫</button>
-
- <button v-if="state==='ringing'" @click="acceptCall" class="btn btn-accept">接听</button>
- <button v-if="state==='ringing'" @click="rejectCall" class="btn btn-reject">拒绝</button>
-
- <button v-if="state==='calling' || state==='in-call'" @click="hangup" class="btn btn-hangup">挂断</button>
- </div>
-
+ <div class="call-container" :class="{'incoming-call': state === 'ringing'} ">
+ <div class="content-wrapper">
+ <!-- <div class="input-group">
+ <div class="input-item">
+ <span class="input-label">我的 userId:</span>
+ <input v-model="uid" class="input-field" />
+ </div>
- </div>
-
-<!-- <div class="tip-message">-->
-<!-- <small>提示:若 getUserMedia 不可用,请用 https 或 localhost 访问页面。</small>-->
-<!-- </div>-->
-
-<!-- <div class="log-container">-->
-<!-- <div class="log-box">-->
-<!-- <div class="log-title">日志信息:</div>-->
-<!-- <pre class="log-content">{{ logText }}</pre>-->
-<!-- </div>-->
-<!-- </div>-->
-
- <audio ref="remoteAudio" autoplay class="hidden-audio"></audio>
+ <div class="input-item">
+ <span class="input-label">对方 userId:</span>
+ <input v-model="peerUid" class="input-field" />
+ </div>
+ </div>-->
+
+ <div class="button-group" v-if="false">
+ <button @click="connectWS" :disabled="connected" class="btn btn-connect">连接WS</button>
+ </div>
+ <!-- 头像-->
+ <div class="contactAvatar">
+ <img :src="defaultAvatar" alt=""></img>
+ </div>
+ <div class="status-group">
+ <strong v-if="state==='ringing'" class="status-text incoming-status">来电中...</strong>
+ <strong v-if="state==='calling'" class="status-text calling-status">呼叫中...</strong>
+ <!-- <strong class="status-text">状态:<span :style="{color: getStateColor(state)}">{{ state }}</span></strong>-->
+ <strong v-if="state==='in-call'" class="status-text">通话时长:{{ durationText }}</strong>
+ </div>
+ <div class="button-group">
+ <button v-if="state === 'idle' && !receiveParameters" @click="requestCall" :disabled="!connected" class="btn btn-call">呼叫</button>
+
+ <button v-if="state==='ringing'" @click="acceptCall" class="btn btn-accept incoming-accept">接听</button>
+ <button v-if="state==='ringing'" @click="rejectCall" class="btn btn-reject incoming-reject">拒绝</button>
+
+ <button v-if="state==='calling' || state==='in-call'" @click="hangup" class="btn btn-hangup">挂断</button>
+ </div>
+
+
+ </div>
+
+ <!-- <div class="tip-message">
+ <small>提示:若 getUserMedia 不可用,请用 https 或 localhost 访问页面。</small>
+ </div>-->
+
+ <!-- <div class="log-container">
+ <div class="log-box">
+ <div class="log-title">日志信息:</div>
+ <pre class="log-content">{{ logText }}</pre>
+ </div>
+ </div>-->
+
+ <audio ref="remoteAudio" autoplay class="hidden-audio"></audio>
</div>
</template>
@@ -58,25 +60,69 @@
/** ✅ 你的 WS 地址前缀(后面拼 userId) */
const WS_BASE = 'wss://wrj.shuixiongit.com/ws/chat?userId='
- // 解析URL参数
- const parseUrlParams = () => {
- const url = new URL(window.location.href)
- const paramsStr = url.searchParams.get('params')
- if (paramsStr) {
- try {
- const params = JSON.parse(decodeURIComponent(paramsStr))
- if (params.peerUid) {
- peerUid.value = String(params.peerUid)
- log('🔗 从URL参数获取peerUid:', params.peerUid)
- }
- } catch (e) {
- log('❌ 解析URL参数失败:', String(e))
- }
- }
- }
+// 解析URL参数
+const parseUrlParams = () => {
+ // 1. 优先从route.query.voiceparams获取参数
+ if (route.query && route.query.voiceparams) {
+ console.log('从route.query.voiceparams获取参数:', route.query.voiceparams);
+ try {
+ // 解码并解析voiceparams参数
+ const params = JSON.parse(decodeURIComponent(route.query.voiceparams));
+ console.log('解析后的voiceparams:', params);
+
+ if (params.peerUid) {
+ peerUid.value = String(params.peerUid);
+ log('🔗 从voiceparams获取peerUid:', params.peerUid);
+ }
+
+ // 如果是来电类型,直接触发来电状态
+ if (params.type === 'incoming') {
+ log('📞 voiceparams检测到来电,直接进入来电状态');
+ incomingFrom = String(params.from);
+ peerUid.value = incomingFrom;
+ acceptedByMe = false;
+ offeredByPeer = false;
+
+ state.value = 'ringing';
+ log('📞 收到来电 call,来自', incomingFrom);
+
+ // 开始来电提示(铃声和震动)
+ playRingtone();
+ startVibration();
+ }
+ } catch (e) {
+ log('❌ 解析voiceparams参数失败:', String(e));
+ }
+ }
+
+ // 2. 同时保留原有的URL参数解析逻辑,作为备选
+ const url = new URL(window.location.href);
+ const paramsStr = url.searchParams.get('params');
+ if (paramsStr) {
+ try {
+ const params = JSON.parse(decodeURIComponent(paramsStr));
+ if (params.peerUid) {
+ peerUid.value = String(params.peerUid);
+ log('🔗 从URL参数获取peerUid:', params.peerUid);
+ }
+ } catch (e) {
+ log('❌ 解析URL参数失败:', String(e));
+ }
+ }
+}
- const uid = ref('3')
- const peerUid = ref('2')
+// 从URL参数或本地存储获取用户ID
+const getUserIdFromStorage = () => {
+ const userId = localStorage.getItem('userId') || sessionStorage.getItem('userId')
+ if (userId) {
+ log('🔗 从本地存储获取userId:', userId)
+ return userId
+ }
+ return '3' // 默认值
+}
+
+const uid = ref('3')
+const peerUid = ref('2')
const connected = ref(false)
const state = ref('idle') // idle | calling | ringing | in-call
@@ -113,59 +159,130 @@
const m = String(Math.floor(seconds.value / 60)).padStart(2, '0')
const s = String(seconds.value % 60).padStart(2, '0')
return `${m}:${s}`
- })
- function startTimer() {
+})
+function startTimer() {
stopTimer()
seconds.value = 0
timer = setInterval(() => seconds.value++, 1000)
- }
- function stopTimer() {
+}
+function stopTimer() {
if (timer) clearInterval(timer)
timer = null
- }
-
- // 心跳
- let pingTimer = null
- function startPing() {
+}
+
+// 心跳
+let pingTimer = null
+function startPing() {
stopPing()
// 每 25 秒发一次 ping(你后端有 PING/PONG)
pingTimer = setInterval(() => {
- if (connected.value) send('ping', 'system', null)
+ if (connected.value) send('ping', 'system', null)
}, 25000)
- }
- function stopPing() {
+}
+
+// 来电提示相关
+let ringtoneAudio = null
+let vibrationTimer = null
+
+// 播放来电铃声
+function playRingtone() {
+ // 创建音频对象(使用默认的系统提示音)
+ ringtoneAudio = new Audio('data:audio/wav;base64,UklGRigAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQAAAAAAA==');
+ ringtoneAudio.loop = true;
+ ringtoneAudio.volume = 0.5;
+
+ try {
+ ringtoneAudio.play();
+ log('🔊 开始播放来电铃声')
+ } catch (e) {
+ log('❌ 播放来电铃声失败:', String(e))
+ }
+}
+
+// 停止来电铃声
+function stopRingtone() {
+ if (ringtoneAudio) {
+ try {
+ ringtoneAudio.pause();
+ ringtoneAudio.currentTime = 0;
+ ringtoneAudio = null;
+ log('🔇 停止播放来电铃声')
+ } catch (e) {
+ log('❌ 停止来电铃声失败:', String(e))
+ }
+ }
+}
+
+// 开始震动
+function startVibration() {
+ // 检查浏览器是否支持震动 API
+ if ('vibrate' in navigator) {
+ // 震动模式:震动1秒,暂停0.5秒,重复
+ const vibrationPattern = [1000, 500];
+
+ try {
+ navigator.vibrate(vibrationPattern);
+ vibrationTimer = setInterval(() => {
+ navigator.vibrate(vibrationPattern);
+ }, 1500); // 每1.5秒重复一次震动模式
+ log('📳 开始震动提示')
+ } catch (e) {
+ log('❌ 震动失败:', String(e))
+ }
+ } else {
+ log('ℹ️ 当前浏览器不支持震动功能')
+ }
+}
+
+// 停止震动
+function stopVibration() {
+ if (vibrationTimer) {
+ clearInterval(vibrationTimer);
+ vibrationTimer = null;
+ }
+
+ if ('vibrate' in navigator) {
+ try {
+ navigator.vibrate(0); // 停止震动
+ log('📳 停止震动提示')
+ } catch (e) {
+ log('❌ 停止震动失败:', String(e))
+ }
+ }
+}
+function stopPing() {
if (pingTimer) clearInterval(pingTimer)
pingTimer = null
- }
-
- function log(...args) {
+}
+
+function log(...args) {
const line = args.map(v => (typeof v === 'string' ? v : JSON.stringify(v))).join(' ')
logText.value += `[${new Date().toLocaleTimeString()}] ${line}\n`
console.log(...args)
- }
-
- /**
- * ✅ 严格按后端 ChatMessage 字段发:
- * { type, from, to, payload, timestamp }
- * 注意:后端会用 session 的 userId 覆盖 from,但我们带上也没坏处
- */
- function send(type, to, payload) {
+}
+
+/**
+ * ✅ 严格按后端 ChatMessage 字段发:
+ * { type, from, to, payload, timestamp }
+ * 注意:后端会用 session 的 userId 覆盖 from,但我们带上也没坏处
+ */
+function send(type, to, payload) {
if (!type) return
if (!ws || ws.readyState !== WebSocket.OPEN) return
-
+
const msg = {
- type, // "call/accept/busy/offer/answer/candidate/hangup/ping"
- from: String(uid.value),
- to: String(to),
- payload: payload ?? null,
- timestamp: Date.now(),
+ type, // "call/accept/busy/offer/answer/candidate/hangup/ping"
+ from: String(uid.value),
+ to: String(to),
+ payload: payload ?? null,
+ timestamp: Date.now(),
}
ws.send(JSON.stringify(msg))
- }
-
- /* ---------------- WebRTC ---------------- */
-
- function createPC() {
+}
+
+/* ---------------- WebRTC ---------------- */
+
+function createPC() {
pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
})
@@ -245,6 +362,8 @@
/* ---------------- 信令处理 ---------------- */
async function onSignal(msg) {
+console.log('1cesss',msg);
+
if (!msg) return
const t = (msg.type || '').toString()
@@ -270,6 +389,11 @@
state.value = 'ringing'
log('📞 收到来电 call,来自', incomingFrom)
+
+ // 开始来电提示(铃声和震动)
+ playRingtone()
+ startVibration()
+
return
}
@@ -285,382 +409,518 @@
cleanup(false)
return
}
-
+
if (t === 'offer') {
- // 被叫收到 offer:只有点击接听后才真正应答(你要求“先点接听”)
- offeredByPeer = true
- incomingFrom = String(msg.from)
- peerUid.value = incomingFrom
-
- if (!acceptedByMe) {
- state.value = 'ringing'
- log('📩 收到 offer(但未接听),已等待用户点击接听')
- // 暂存 offer 到一个变量
- lastOfferSdp = msg.payload?.sdp
+ // 被叫收到 offer:只有点击接听后才真正应答(你要求“先点接听”)
+ offeredByPeer = true
+ incomingFrom = String(msg.from)
+ peerUid.value = incomingFrom
+
+ if (!acceptedByMe) {
+ state.value = 'ringing'
+ log('📩 收到 offer(但未接听),已等待用户点击接听')
+ // 暂存 offer 到一个变量
+ lastOfferSdp = msg.payload?.sdp
+ return
+ }
+
+ // 已接听:直接 answer
+ await answerOffer(msg.payload?.sdp, incomingFrom)
return
- }
-
- // 已接听:直接 answer
- await answerOffer(msg.payload?.sdp, incomingFrom)
- return
}
-
+
if (t === 'answer') {
- log('✅ 收到 answer,通话建立')
- if (!pc) createPC()
- await pc.setRemoteDescription(new RTCSessionDescription(msg.payload?.sdp))
- await flushCandidates()
-
- state.value = 'in-call'
- startTimer()
- return
+ log('✅ 收到 answer,通话建立')
+ if (!pc) createPC()
+ await pc.setRemoteDescription(new RTCSessionDescription(msg.payload?.sdp))
+ await flushCandidates()
+
+ state.value = 'in-call'
+ startTimer()
+ return
}
-
+
if (t === 'candidate') {
- const c = msg.payload?.candidate
- if (!c) return
- if (pc && pc.remoteDescription) {
- await pc.addIceCandidate(new RTCIceCandidate(c))
- } else {
- pendingCandidates.push(c)
- }
- return
+ const c = msg.payload?.candidate
+ if (!c) return
+ if (pc && pc.remoteDescription) {
+ await pc.addIceCandidate(new RTCIceCandidate(c))
+ } else {
+ pendingCandidates.push(c)
+ }
+ return
}
-
+
if (t === 'hangup') {
- log('📴 对方挂断')
- cleanup(false)
- return
+ log('📴 对方挂断')
+ cleanup(false)
+ return
}
-
+
if (t === 'pong') {
- // 心跳响应
- // log('pong')
- return
- }
- }
-
- // 暂存 offer(用于“先点接听再 answer”)
- let lastOfferSdp = null
-
- /* ---------------- 业务操作 ---------------- */
-
- function connectWS() {
- if (ws) ws.close()
-
- ws = new WebSocket(WS_BASE + encodeURIComponent(uid.value))
-
- ws.onopen = () => {
- connected.value = true
- log('WS 已连接 userId=', uid.value)
- startPing()
- }
-
- ws.onclose = () => {
- connected.value = false
- log('WS 已关闭')
- stopPing()
- }
-
- ws.onerror = (e) => log('WS error', e)
-
- ws.onmessage = (e) => {
- const msg = JSON.parse(e.data)
- log('收到信令', msg)
- onSignal(msg)
- }
- }
-
- function requestCall() {
- state.value = 'calling'
- incomingFrom = null
- acceptedByMe = false
- offeredByPeer = false
- lastOfferSdp = null
-
- send('call', peerUid.value, null)
- log('➡️ 发起呼叫 call 给', peerUid.value)
- }
-
- function acceptCall() {
- if (!incomingFrom) return
- acceptedByMe = true
-
- // 先告诉对方我接听了(后端会把双方置忙)
- send('accept', incomingFrom, null)
- log('✅ 已点击接听,发送 accept 给', incomingFrom)
-
- state.value = 'calling'
-
- // 如果 offer 已经提前到达(我们暂存了),立刻 answer
- if (lastOfferSdp) {
- answerOffer(lastOfferSdp, incomingFrom)
- lastOfferSdp = null
- }
- }
-
- function rejectCall() {
- if (!incomingFrom) return
-
- // 你后端没有 reject,用 busy 表示拒绝/不可接听
- send('busy', incomingFrom, null)
- log('❌ 已拒绝来电,发送 busy 给', incomingFrom)
-
- incomingFrom = null
- acceptedByMe = false
- offeredByPeer = false
- lastOfferSdp = null
- state.value = 'idle'
-
- // 返回上一页(uni-app的语音通话列表页面)
- if (window.uni) {
- window.uni.navigateBack()
- } else {
- log('❌ uni对象不存在,无法返回上一页')
- }
- }
-
- async function startOffer() {
- if (!pc) createPC()
-
- // ✅ 不会重复 addTrack
- await ensureLocalTracksAdded()
-
- const offer = await pc.createOffer()
- await pc.setLocalDescription(offer)
-
- send('offer', peerUid.value, { sdp: pc.localDescription })
- log('➡️ 已发送 offer 给', peerUid.value)
- }
-
- async function answerOffer(offerSdp, from) {
- if (!offerSdp) {
- log('❌ offer sdp 为空,无法接听')
- return
- }
- if (!pc) createPC()
-
- await pc.setRemoteDescription(new RTCSessionDescription(offerSdp))
- await flushCandidates()
-
- await ensureLocalTracksAdded()
-
- const answer = await pc.createAnswer()
- await pc.setLocalDescription(answer)
-
- send('answer', from, { sdp: pc.localDescription })
- log('⬅️ 已发送 answer 给', from)
-
- state.value = 'in-call'
- startTimer()
- }
-
- function hangup() {
- cleanup(true)
- }
-
- /* ---------------- 清理 ---------------- */
-
- function cleanup(sendToPeer) {
- try {
- if (sendToPeer) send('hangup', peerUid.value, null)
- } catch {}
-
- stopTimer()
-
- if (pc) {
- pc.getSenders().forEach(s => s.track && s.track.stop())
- pc.close()
- }
- if (localStream) {
- localStream.getTracks().forEach(t => t.stop())
- }
-
- pc = null
- localStream = null
- pendingCandidates.length = 0
-
- incomingFrom = null
- acceptedByMe = false
- offeredByPeer = false
- lastOfferSdp = null
-
- state.value = 'idle'
-
- // 返回上一页(uni-app的语音通话列表页面)
- if (window.uni) {
- window.uni.navigateBack()
- } else {
- log('❌ uni对象不存在,无法返回上一页')
+ // 心跳响应
+ // log('pong')
+ return
}
}
+// 暂存 offer(用于“先点接听再 answer”)
+let lastOfferSdp = null
+
+/* ---------------- 业务操作 ---------------- */
+
+function connectWS() {
+ // 确保获取最新的用户ID
+ uid.value = getUserIdFromStorage()
+
+ if (ws) ws.close()
+
+ ws = new WebSocket(WS_BASE + encodeURIComponent(uid.value))
+
+ ws.onopen = () => {
+ connected.value = true
+ log('WS 已连接 userId=', uid.value)
+ startPing()
+ }
+
+ ws.onclose = () => {
+ connected.value = false
+ log('WS 已关闭')
+ stopPing()
+ }
+
+ ws.onerror = (e) => log('WS error', e)
+
+ ws.onmessage = (e) => {
+ const msg = JSON.parse(e.data)
+ log('收到信令', msg)
+ onSignal(msg)
+ }
+}
+
+function requestCall() {
+ state.value = 'calling'
+ incomingFrom = null
+ acceptedByMe = false
+ offeredByPeer = false
+ lastOfferSdp = null
+
+ send('call', peerUid.value, null)
+ log('➡️ 发起呼叫 call 给', peerUid.value)
+}
+
+function acceptCall() {
+ if (!incomingFrom) return
+ acceptedByMe = true
+
+ // 停止来电提示
+ stopRingtone()
+ stopVibration()
+
+ // 先告诉对方我接听了(后端会把双方置忙)
+ send('accept', incomingFrom, null)
+ log('✅ 已点击接听,发送 accept 给', incomingFrom)
+
+ state.value = 'calling'
+
+ // 如果 offer 已经提前到达(我们暂存了),立刻 answer
+ if (lastOfferSdp) {
+ answerOffer(lastOfferSdp, incomingFrom)
+ lastOfferSdp = null
+ }
+}
+
+function rejectCall() {
+ if (!incomingFrom) return
+
+ // 停止来电提示
+ stopRingtone()
+ stopVibration()
+
+ // 你后端没有 reject,用 busy 表示拒绝/不可接听
+ send('busy', incomingFrom, null)
+ log('❌ 已拒绝来电,发送 busy 给', incomingFrom)
+
+ incomingFrom = null
+ acceptedByMe = false
+ offeredByPeer = false
+ lastOfferSdp = null
+ state.value = 'idle'
+
+ // 返回上一页(uni-app的语音通话列表页面)
+ if (window.uni) {
+ window.uni.navigateBack()
+ } else {
+ log('❌ uni对象不存在,无法返回上一页')
+ }
+}
+
+async function startOffer() {
+ if (!pc) createPC()
+
+ // ✅ 不会重复 addTrack
+ await ensureLocalTracksAdded()
+
+ const offer = await pc.createOffer()
+ await pc.setLocalDescription(offer)
+
+ send('offer', peerUid.value, { sdp: pc.localDescription })
+ log('➡️ 已发送 offer 给', peerUid.value)
+}
+
+async function answerOffer(offerSdp, from) {
+ if (!offerSdp) {
+ log('❌ offer sdp 为空,无法接听')
+ return
+ }
+ if (!pc) createPC()
+
+ await pc.setRemoteDescription(new RTCSessionDescription(offerSdp))
+ await flushCandidates()
+
+ await ensureLocalTracksAdded()
+
+ const answer = await pc.createAnswer()
+ await pc.setLocalDescription(answer)
+
+ send('answer', from, { sdp: pc.localDescription })
+ log('⬅️ 已发送 answer 给', from)
+
+ state.value = 'in-call'
+ startTimer()
+}
+
+function hangup() {
+ cleanup(true)
+}
+
+/* ---------------- 清理 ---------------- */
+
+function cleanup(sendToPeer) {
+ try {
+ if (sendToPeer) send('hangup', peerUid.value, null)
+ } catch {}
+
+ stopTimer()
+ // 停止来电提示
+ stopRingtone()
+ stopVibration()
+
+ if (pc) {
+ pc.getSenders().forEach(s => s.track && s.track.stop())
+ pc.close()
+ }
+ if (localStream) {
+ localStream.getTracks().forEach(t => t.stop())
+ }
+
+ pc = null
+ localStream = null
+ pendingCandidates.length = 0
+
+ incomingFrom = null
+ acceptedByMe = false
+ offeredByPeer = false
+ lastOfferSdp = null
+
+ state.value = 'idle'
+
+ // 返回上一页(uni-app的语音通话列表页面)
+ if (window.uni) {
+ window.uni.navigateBack()
+ } else {
+ log('❌ uni对象不存在,无法返回上一页')
+ }
+}
+
+// 定义receiveParameters变量,用于模板中判断是否显示呼叫按钮
+const receiveParameters = ref(route.query.contact || null);
+
onMounted(() => {
- const receiveParameters = route.query.contact
- console.log('接收参数', receiveParameters)
+ console.log('收拾收拾',route.query.voiceparams);
+ console.log('接收参数', receiveParameters.value)
+
+ // 解析并使用contact参数
+ if (receiveParameters.value) {
+ try {
+ const contact = JSON.parse(decodeURIComponent(receiveParameters.value))
+ if (contact.friendId) {
+ peerUid.value = contact.friendId
+ log('🔗 从contact参数获取peerUid:', contact.friendId)
+ log('👤 联系人名称:', contact.friendNickName || '未知联系人')
+ }
+ } catch (e) {
+ log('❌ 解析contact参数失败:', String(e))
+ }
+ }
+
// 解析URL参数
parseUrlParams()
// 自动连接WebSocket
connectWS()
-})
+});
+
+// 监听连接状态,当connected变为true且有receiveParameters时自动呼叫
+watch(() => connected.value, (newVal) => {
+ if (newVal && state.value === 'idle' && receiveParameters.value) {
+ log('📞 连接成功且有contact参数,自动发起呼叫');
+ requestCall();
+ }
+});
// 监听连接状态,连接成功后自动呼叫
-watch(() => connected.value, (newVal) => {
- if (newVal && state.value === 'idle') {
- requestCall()
- }
+// watch(() => connected.value, (newVal) => {
+// if (newVal && state.value === 'idle') {
+// requestCall()
+// }
+// })
+
+onBeforeUnmount(() => {
+ cleanup(false)
+ stopPing()
+ if (ws) ws.close()
})
+</script>
+<style scoped lang="scss">
+.call-container {
+ padding: 20px;
+ min-height: 100vh;
+ font-family: Arial, sans-serif;
+ background-color: #f5f5f5;
+ transition: all 0.3s ease;
+}
- onBeforeUnmount(() => {
- cleanup(false)
- stopPing()
- if (ws) ws.close()
- })
- </script>
- <style scoped lang="scss">
- .call-container {
- padding:100px 20px 20px 20px;
- min-height: 100vh;
- font-family: Arial, sans-serif;
- }
+/* 来电时的全屏样式 */
+.call-container.incoming-call {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 99999;
+ background-color: #ffffff;
+ padding: 0;
+}
- .content-wrapper {
- display: flex;
- flex-direction: column;
- gap: 15px;
- max-width: 600px;
- margin: 0 auto;
- }
+.content-wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ max-width: 600px;
+ margin: 0 auto;
+ height: 100vh;
+ justify-content: center;
+ align-items: center;
+}
- .input-group {
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
+.input-group {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
- .input-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
+.input-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
- .input-label {
- font-weight: bold;
- color: #333;
- }
+.input-label {
+ font-weight: bold;
+ color: #333;
+}
- .input-field {
- width: 120px;
- padding: 8px;
- border: 1px solid #ddd;
- border-radius: 4px;
- font-size: 14px;
- }
+.input-field {
+ width: 120px;
+ padding: 8px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-size: 14px;
+}
- .button-group {
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
- justify-content: center;
- }
+.button-group {
+ display: flex;
+ gap: 50px;
+ flex-wrap: wrap;
+ justify-content: center;
+ margin-top: 80px;
+}
- .btn {
- padding: 10px 20px;
- color: white;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-size: 14px;
- transition: background-color 0.3s;
+.btn {
+ padding: 15px 30px;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 16px;
+ font-weight: bold;
+ transition: all 0.3s ease;
+ min-width: 120px;
- &:disabled {
- opacity: 0.6;
- cursor: not-allowed;
- }
+ &:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
}
+}
- .btn-connect {
- background-color: #4CAF50;
- }
+/* 来电时的接听和拒绝按钮样式 */
+.btn.incoming-accept, .btn.incoming-reject {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ padding: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
+}
- .btn-call {
- background-color: #2196F3;
- }
+.btn.incoming-accept {
+ background-color: #4CAF50;
+ transform: scale(1.1);
+}
- .btn-accept {
- background-color: #4CAF50;
- }
+.btn.incoming-reject {
+ background-color: #f44336;
+}
- .btn-reject {
- background-color: #f44336;
- }
+.btn.incoming-accept:active {
+ transform: scale(0.95);
+}
- .btn-hangup {
- background-color: #9E9E9E;
- }
+.btn.incoming-reject:active {
+ transform: scale(0.95);
+}
- .status-group {
- display: flex;
- flex-direction: column;
- gap: 5px;
- }
+.btn-connect {
+ background-color: #4CAF50;
+}
- .status-text {
- font-size: 16px;
- color: #333;
- text-align: center;
- margin-bottom: 50px;
- }
+.btn-call {
+ background-color: #2196F3;
+}
- .tip-message {
- margin-top: 15px;
- color: #666;
- text-align: center;
- }
+.btn-accept {
+ background-color: #4CAF50;
+}
- .log-container {
- margin-top: 20px;
- max-width: 600px;
- margin-left: auto;
- margin-right: auto;
- }
+.btn-reject {
+ background-color: #f44336;
+}
- .log-box {
- background-color: white;
- border: 1px solid #ddd;
- border-radius: 8px;
- padding: 10px;
- }
+.btn-hangup {
+ background-color: #9E9E9E;
+ padding: 15px 40px;
+ min-width: 150px;
+}
- .log-title {
- font-weight: bold;
- color: #333;
- margin-bottom: 8px;
- }
+.status-group {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ align-items: center;
+}
- .log-content {
- max-height: 200px;
- overflow: auto;
- background-color: #fafafa;
- padding: 10px;
- border-radius: 4px;
- font-size: 12px;
- line-height: 1.4;
- }
+.status-text {
+ font-size: 18px;
+ color: #333;
+ text-align: center;
+ margin-bottom: 20px;
+}
- .hidden-audio {
- display: none;
+/* 来电状态样式 */
+.status-text.incoming-status {
+ font-size: 24px;
+ color: #333;
+ font-weight: bold;
+ animation: pulse 1.5s infinite;
+}
+
+.status-text.calling-status {
+ font-size: 20px;
+ color: #666;
+}
+
+/* 呼吸灯动画 */
+@keyframes pulse {
+ 0% {
+ opacity: 1;
+ transform: scale(1);
}
- .contactAvatar {
- width: 172rpx;
- height: 172rpx;
- display: flex;
- justify-content: center;
- align-items: center;
- margin-bottom: 200px;
- img{
- width: 172rpx;
- height: 172rpx;
- }
+ 50% {
+ opacity: 0.7;
+ transform: scale(1.05);
}
- </style>
\ No newline at end of file
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+.tip-message {
+ margin-top: 15px;
+ color: #666;
+ text-align: center;
+}
+
+.log-container {
+ margin-top: 20px;
+ max-width: 600px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.log-box {
+ background-color: white;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ padding: 10px;
+}
+
+.log-title {
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 8px;
+}
+
+.log-content {
+ max-height: 200px;
+ overflow: auto;
+ background-color: #fafafa;
+ padding: 10px;
+ border-radius: 4px;
+ font-size: 12px;
+ line-height: 1.4;
+}
+
+.hidden-audio {
+ display: none;
+}
+
+/* 头像样式 */
+.contactAvatar {
+ width: 180px;
+ height: 180px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 40px;
+ border-radius: 50%;
+ background-color: #f0f0f0;
+ overflow: hidden;
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+}
+
+/* 来电时的头像放大效果 */
+.call-container.incoming-call .contactAvatar {
+ width: 220px;
+ height: 220px;
+ margin-bottom: 60px;
+}
+</style>
\ No newline at end of file
diff --git a/uniapps/work-app/src/hooks/useGlobalWS.js b/uniapps/work-app/src/hooks/useGlobalWS.js
index 83d6a3e..9f18e0c 100644
--- a/uniapps/work-app/src/hooks/useGlobalWS.js
+++ b/uniapps/work-app/src/hooks/useGlobalWS.js
@@ -1,104 +1,167 @@
-import {useUserStore} from "@/store/index.js";
-import {getEnvObj} from "@/utils/index.js";
-// import {enterRoom} from "@/utils/voiceCallByTX/index.js";
+import { useUserStore } from "@/store/index.js";
+import { ref, computed, watch } from "vue";
import useAppStore from "../store/modules/app/index.js";
-
-let socketTask = null
+import websocketService from "@/utils/websocket.js";
export function useGlobalWS() {
const userStore = useUserStore();
const appStore = useAppStore();
- const userId = computed(() => userStore?.userInfo?.user_id)
- const access_token = computed(() => userStore?.userInfo?.access_token)
- const {VITE_APP_WS_API_URL} = getEnvObj()
+ // 设置默认用户ID为3
+ const defaultUserId = '3';
+ // WebSocket基础URL
+ const WS_BASE = 'wss://wrj.shuixiongit.com/ws/chat?userId=';
// 消息处理
- function messageHandler(payload) {
- switch (payload.biz_code) {
- case 'JOB_ISREFRESH':
- appStore.setJobUpdateKeyAdd()
- break
- case 'DEVICE_ISREFRESH':
- appStore.setDeviceUpdateKeyAdd()
- break
- case 'DOWNLOAD_PROGRESS':
- break
- case 'LOGOUT_USER':
- userStore.setUserInfo(null)
- uni.reLaunch({
- url: '/pages/login/index'
- })
- break
- case 'VoiceCall':
- // enterRoom(payload, userId.value)
- break
- default:
- break;
- }
- }
+ function messageHandler(payload) {
+ console.log('🌐 全局WebSocket收到消息111111111111111111111:', payload)
+ // 先尝试直接处理消息(适用于mobile-web-view的voiceCallDetail页面的消息格式)
+ const t = (payload.type || '').toString()
+ const bizCode = payload.biz_code || ''
- // 关闭ws
- function closeWS() {
- socketTask?.close({
- success: () => {
- console.log('ws关闭连接');
- },
- })
- }
+ console.log('📋 消息分析:type=' + t + ', biz_code=' + bizCode)
- // 初始化ws
- function initWS() {
- // 关闭,再连接ws
- closeWS()
- if (!access_token.value) return
- const url = VITE_APP_WS_API_URL
- + `?x-auth-token=${encodeURI(access_token?.value)}`
- + `&model_type=3&workspace-id=${userId.value}`
- // 创建连接
- socketTask = uni.connectSocket({
- url: url,
- success: () => {
- console.log('ws连接成功');
- },
- fail: (err) => {
- console.error('ws连接失败:', err);
- }
- });
- // 消息监听
- socketTask.onMessage((result) => {
- messageHandler(JSON.parse(result.data))
- })
- //==================================
- // 监听连接打开
- socketTask.onOpen((res) => {
- console.log('✅ WebSocket连接已建立')
- // reconnectAttempts = 0 // 连接成功后重置重连次数
- // 可以在这里发送心跳或订阅消息
- // startHeartbeat()
- })
- // 监听连接关闭
- socketTask.onClose((res) => {
- console.log(`WebSocket连接关闭,代码: ${res.code}, 原因: ${res.reason}`)
+ // 处理语音通话请求
+ if (t === 'call') {
+ console.log('📞 全局收到来电 call,来自', payload.from)
+ // 构建来电参数
+ const callParams = {
+ peerUid: payload.from,
+ from: payload.from,
+ type: 'incoming'
+ };
- // 根据不同的关闭代码处理
- if (res.code === 1000) { // 正常关闭
- console.log('连接正常关闭')
- } else if (res.code === 1006) { // 异常关闭
- console.log('连接异常关闭,尝试重连...')
- } else if (res.code === 1011) { // 服务器内部错误
- console.log('服务器内部错误(1011),延迟重连...')
- } else {
- console.log('其他原因关闭,尝试重连...')
+ // 转义参数以便在URL中传递
+ const encodedParams = encodeURIComponent(JSON.stringify(callParams));
+
+ // 跳转到mobile-web-view应用中的语音通话页面
+ const voiceCallUrl = `http://localhost:9527/work-app/#/subPackages/voiceCallDetail/index?voiceparams=${encodedParams}`;
+
+ console.log('🔗 准备导航到:', voiceCallUrl)
+
+ try {
+ // 优先使用uni-app的导航API(适用于uni-app环境)
+ if (typeof uni !== 'undefined' && uni.navigateTo) {
+ console.log('使用uni-app导航API跳转到语音通话页面');
+
+ // 检查是否是跳转到应用内页面
+ if (voiceCallUrl.includes('/subPackages/voiceCallDetail/')) {
+ // 应用内页面跳转
+ uni.navigateTo({
+ url: voiceCallUrl.replace('http://localhost:9527/work-app/#', ''),
+ success: () => {
+ console.log('✅ 应用内导航成功!');
+ },
+ fail: (err) => {
+ console.error('❌ 应用内导航失败:', err);
+ // 如果应用内导航失败,尝试使用外部跳转
+ fallbackToExternalNavigation();
+ }
+ });
+ } else {
+ // 外部页面跳转
+ fallbackToExternalNavigation();
+ }
+ }
+ // 否则尝试使用window.location跳转(适用于Web环境)
+ else if (typeof window !== 'undefined' && window.location) {
+ console.log('使用window.location跳转到语音通话页面');
+ window.location.href = voiceCallUrl;
+ } else {
+ console.error('无法跳转到语音通话页面:当前环境不支持导航');
+ }
+ } catch (error) {
+ console.error('跳转到语音通话页面失败:', error);
+ // 尝试使用外部跳转作为最后的补救措施
+ fallbackToExternalNavigation();
}
- })
- // 监听错误
- socketTask.onError((err) => {
- console.error('WebSocket发生错误:', err)
- })
- }
+ // 外部页面跳转的回调函数
+ function fallbackToExternalNavigation() {
+ console.log('尝试使用外部导航跳转到语音通话页面');
+ try {
+ // 使用uni-app的外部链接跳转API
+ if (typeof uni !== 'undefined' && uni.navigateToWebview) {
+ // 这是一个示例,具体API可能需要根据uni-app版本和配置调整
+ uni.navigateToWebview({
+ url: voiceCallUrl,
+ success: () => {
+ console.log('✅ 外部Webview导航成功!');
+ },
+ fail: (err) => {
+ console.error('❌ 外部Webview导航失败:', err);
+ // 如果还是失败,尝试使用浏览器打开
+ if (typeof plus !== 'undefined' && plus.runtime) {
+ plus.runtime.openURL(voiceCallUrl);
+ } else {
+ console.error('❌ 所有导航方式都失败了');
+ }
+ }
+ });
+ }
+ // 或者使用plus.runtime.openURL(适用于App环境)
+ else if (typeof plus !== 'undefined' && plus.runtime) {
+ plus.runtime.openURL(voiceCallUrl);
+ console.log('尝试使用plus.runtime.openURL打开外部链接');
+ } else {
+ console.error('无法跳转到外部页面:当前环境不支持外部导航');
+ }
+ } catch (fallbackError) {
+ console.error('外部导航尝试失败:', fallbackError);
+ }
+ }
+ return
+ }
+ // 然后处理biz_code格式的消息
+ switch (bizCode) {
+ case 'JOB_ISREFRESH':
+ appStore.setJobUpdateKeyAdd()
+ break
+ case 'DEVICE_ISREFRESH':
+ appStore.setDeviceUpdateKeyAdd()
+ break
+ case 'DOWNLOAD_PROGRESS':
+ break
+ case 'LOGOUT_USER':
+ userStore.setUserInfo(null)
+ uni.reLaunch({
+ url: '/pages/login/index'
+ })
+ break
+ case 'VoiceCall':
+ // enterRoom(payload, userId.value)
+ break
+ default:
+ // 记录未处理的消息
+ console.log('未处理的WebSocket消息:', payload)
+ break;
+ }
+ }
- // watch(access_token, initWS, {immediate: true})
+ // 初始化WebSocket连接
+ function initWS() {
+ try {
+ // 使用默认用户ID初始化WebSocket连接
+ websocketService.init(defaultUserId, WS_BASE);
+ console.log('🌐 全局WebSocket初始化成功,默认用户ID:', defaultUserId);
+
+ // 设置全局消息处理回调
+ websocketService.setOnMessageCallback(messageHandler);
+
+ console.log('✅ 全局WebSocket消息处理器已设置');
+ } catch (error) {
+ console.error('❌ 全局WebSocket初始化失败:', error);
+ }
+ }
+
+ // 监听用户信息变化,初始化WebSocket
+ watch(
+ () => userStore.userInfo,
+ (newUserInfo) => {
+ console.log('👤 用户信息变化,尝试初始化WebSocket');
+ initWS();
+ },
+ { immediate: true }
+ )
}
diff --git a/uniapps/work-app/src/pages/login/index.vue b/uniapps/work-app/src/pages/login/index.vue
index 618cd64..4dc8802 100644
--- a/uniapps/work-app/src/pages/login/index.vue
+++ b/uniapps/work-app/src/pages/login/index.vue
@@ -45,6 +45,7 @@
import md5 from "js-md5";
import { loginByUsername } from "@/api/user/index.js";
import { useUserStore } from "@/store/index.js";
+import websocketService from "@/utils/websocket.js";
import { HOME_PATH, LOGIN_PATH, removeQueryString } from "@/router";
@@ -134,7 +135,59 @@
userInfo.key,
userInfo.code
);
+ console.log('登录成功,用户信息:', res.data.data);
userStore.setUserInfo(res.data.data);
+
+
+ // 初始化WebSocket连接
+ try {
+ const wsUrl = 'wss://wrj.shuixiongit.com/ws/chat?userId=';
+ // 设置默认用户ID为3
+ const defaultUserId = '3';
+ // 使用默认用户ID初始化WebSocket连接
+ websocketService.init(defaultUserId, wsUrl);
+ console.log('登录成功,WebSocket已初始化,默认用户ID:', defaultUserId);
+
+ // 设置来电消息回调
+ websocketService.setOnMessageCallback((message) => {
+ if (message.type === 'call') {
+ console.log('收到来电,准备跳转到语音通话页面');
+ // 构建来电参数
+ const callParams = {
+ peerUid: message.from,
+ from: message.from,
+ type: 'incoming'
+ };
+
+ // 转义参数以便在URL中传递
+ const encodedParams = encodeURIComponent(JSON.stringify(callParams));
+
+ try {
+ // 优先使用uni-app的导航API(适用于同应用内跳转)
+ if (typeof uni !== 'undefined' && uni.navigateTo) {
+ console.log('使用uni-app导航API跳转到语音通话页面');
+ uni.navigateTo({
+ url: `/subPackages/voiceCallDetail/index?params=${encodedParams}`
+ });
+ }
+ // 否则使用window.location跳转(适用于Web环境)
+ else if (typeof window !== 'undefined' && window.location) {
+ console.log('使用window.location跳转到语音通话页面');
+ // 注意:实际部署时需要使用正确的URL
+ const voiceCallUrl = `http://localhost:8080/appPages/voiceCallDetail/index.html?params=${encodedParams}`;
+ window.location.href = voiceCallUrl;
+ } else {
+ console.error('无法跳转到语音通话页面:当前环境不支持导航');
+ }
+ } catch (error) {
+ console.error('跳转到语音通话页面失败:', error);
+ }
+ }
+ });
+ } catch (error) {
+ console.error('WebSocket初始化失败:', error);
+ }
+
uni.reLaunch({
url: "/pages/work/index",
});
diff --git a/uniapps/work-app/src/subPackages/voiceCallDetail/index.vue b/uniapps/work-app/src/subPackages/voiceCallDetail/index.vue
index 2c688c3..32fde49 100644
--- a/uniapps/work-app/src/subPackages/voiceCallDetail/index.vue
+++ b/uniapps/work-app/src/subPackages/voiceCallDetail/index.vue
@@ -21,7 +21,7 @@
// 构建viewUrl,将contact参数拼接到URL中
const contactParam = contact ? encodeURIComponent(JSON.stringify(contact)) : '';
// viewUrl.value = `https://192.168.1.157:5176/mobile-web-view/#/webViewWrapper/voiceCallDetail?params=${encodeURIComponent(userParams)}&contact=${contactParam}`;
- viewUrl.value = getWebViewUrl("/voiceCallDetail", { contact: options.contact });
+ viewUrl.value = getWebViewUrl("/voiceCallDetail", { contact: options.contact ,voiceparams: options.voiceparams});
});
function onPostMessage(data) {}
// #ifdef APP-PLUS
diff --git a/uniapps/work-app/src/utils/common/index.js b/uniapps/work-app/src/utils/common/index.js
index a2bb1d8..605ed86 100644
--- a/uniapps/work-app/src/utils/common/index.js
+++ b/uniapps/work-app/src/utils/common/index.js
@@ -90,4 +90,4 @@
export function getAssetsImage (targetUrl) {
const url = getEnvObj().VITE_APP_ASSETS_URL
return `${url}${targetUrl}`
-}
\ No newline at end of file
+}
diff --git a/uniapps/work-app/src/utils/websocket.js b/uniapps/work-app/src/utils/websocket.js
new file mode 100644
index 0000000..9706abd
--- /dev/null
+++ b/uniapps/work-app/src/utils/websocket.js
@@ -0,0 +1,188 @@
+// WebSocket服务管理
+class WebSocketService {
+ constructor() {
+ this.socketTask = null
+ this.connected = false
+ this.url = ''
+ this.userId = ''
+ this.onMessageCallback = null
+ this.onOpenCallback = null
+ this.onCloseCallback = null
+ this.onErrorCallback = null
+ this.pingTimer = null
+ }
+
+ // 初始化WebSocket连接
+ init(userId, url) {
+ if (!url) {
+ console.error('WebSocket初始化失败:缺少url')
+ return
+ }
+
+ this.userId = userId || ''
+ this.url = url + encodeURIComponent(this.userId)
+ this.connect()
+ }
+
+ // 建立WebSocket连接
+ connect() {
+ try {
+ // 关闭现有连接
+ this.close()
+
+ // 使用uni-app的WebSocket API
+ this.socketTask = uni.connectSocket({
+ url: this.url,
+ success: () => {
+ console.log('WebSocket连接已请求')
+ },
+ fail: (error) => {
+ console.error('WebSocket连接请求失败:', error)
+ if (this.onErrorCallback) {
+ this.onErrorCallback(error)
+ }
+ }
+ })
+
+ // 监听连接打开
+ this.socketTask.onOpen(() => {
+ this.connected = true
+ console.log('WebSocket已连接,userId:', this.userId)
+ this.startPing()
+ if (this.onOpenCallback) {
+ this.onOpenCallback()
+ }
+ })
+
+ // 监听连接关闭
+ this.socketTask.onClose(() => {
+ this.connected = false
+ console.log('WebSocket已关闭')
+ this.stopPing()
+ if (this.onCloseCallback) {
+ this.onCloseCallback()
+ }
+ })
+
+ // 监听连接错误
+ this.socketTask.onError((error) => {
+ console.error('WebSocket错误:', error)
+ this.connected = false
+ this.stopPing()
+ if (this.onErrorCallback) {
+ this.onErrorCallback(error)
+ }
+ })
+
+ // 监听消息
+ this.socketTask.onMessage((res) => {
+ try {
+ const message = JSON.parse(res.data)
+ console.log('收到WebSocket消息:', message)
+ if (this.onMessageCallback) {
+ this.onMessageCallback(message)
+ }
+ } catch (error) {
+ console.error('解析WebSocket消息失败:', error)
+ }
+ })
+ } catch (error) {
+ console.error('WebSocket连接失败:', error)
+ if (this.onErrorCallback) {
+ this.onErrorCallback(error)
+ }
+ }
+ }
+
+ // 发送消息
+ send(type, to, payload) {
+ if (!type || !this.socketTask || !this.connected) {
+ console.error('WebSocket发送失败:连接未建立')
+ return
+ }
+
+ const message = {
+ type,
+ from: String(this.userId),
+ to: String(to),
+ payload: payload || null,
+ timestamp: Date.now()
+ }
+
+ this.socketTask.send({
+ data: JSON.stringify(message),
+ success: () => {
+ console.log('WebSocket消息发送成功:', message)
+ },
+ fail: (error) => {
+ console.error('WebSocket消息发送失败:', error)
+ }
+ })
+ }
+
+ // 开始心跳检测
+ startPing() {
+ this.stopPing()
+ // 每25秒发送一次心跳
+ this.pingTimer = setInterval(() => {
+ if (this.connected) {
+ this.send('ping', 'system')
+ }
+ }, 25000)
+ }
+
+ // 停止心跳检测
+ stopPing() {
+ if (this.pingTimer) {
+ clearInterval(this.pingTimer)
+ this.pingTimer = null
+ }
+ }
+
+ // 关闭连接
+ close() {
+ if (this.socketTask) {
+ this.socketTask.close({
+ code: 1000,
+ reason: '正常关闭',
+ success: () => {
+ console.log('WebSocket已关闭')
+ },
+ fail: (error) => {
+ console.error('WebSocket关闭失败:', error)
+ }
+ })
+ this.socketTask = null
+ }
+ this.stopPing()
+ this.connected = false
+ }
+
+ // 设置消息回调
+ setOnMessageCallback(callback) {
+ this.onMessageCallback = callback
+ }
+
+ // 设置连接打开回调
+ setOnOpenCallback(callback) {
+ this.onOpenCallback = callback
+ }
+
+ // 设置连接关闭回调
+ setOnCloseCallback(callback) {
+ this.onCloseCallback = callback
+ }
+
+ // 设置错误回调
+ setOnErrorCallback(callback) {
+ this.onErrorCallback = callback
+ }
+
+ // 获取连接状态
+ getConnected() {
+ return this.connected
+ }
+}
+
+// 导出单例实例
+export default new WebSocketService()
--
Gitblit v1.9.3