吉安感知网项目-前端
shuishen
2026-01-14 094fdb2719e1985538e9fade52f602a79d1d4230
feat:通用列表页面级样式调整
14 files modified
1 files renamed
2 files added
2602 ■■■■■ changed files
applications/drone-command/env/.env.development 4 ●●●● patch | view | raw | blame | history
applications/drone-command/src/api/detectionCountermeasure/detectionRange/index.js 55 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/components/basic-container/main.vue 19 ●●●● patch | view | raw | blame | history
applications/drone-command/src/components/map-container/device-map-container.vue 178 ●●●● patch | view | raw | blame | history
applications/drone-command/src/styles/common/cockpit.scss 488 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/api.txt 329 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/index.vue 161 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/precinctInfo/index.vue 93 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/sceneConfig/index.vue 153 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/deviceScrap/index.vue 154 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/deviceStock/index.vue 206 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/maintainRecord/index.vue 167 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue 32 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/index.vue 8 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/detectionCountermeasure/components/DetectionRangeDialog.vue 348 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/detectionCountermeasure/detectionRange.vue 192 ●●●●● patch | view | raw | blame | history
packages/utils/date/index.js 15 ●●●●● patch | view | raw | blame | history
applications/drone-command/env/.env.development
@@ -2,7 +2,7 @@
 # @Author       : yuan
 # @Date         : 2026-01-07 14:58:30
 # @LastEditors  : yuan
 # @LastEditTime : 2026-01-08 15:14:52
 # @LastEditTime : 2026-01-13 16:28:33
 # @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.33
VITE_APP_URL= http://192.168.1.168
#新大屏地址
VITE_APP_DASHBOARD_URL = 'https://wrj.shuixiongit.com/command-center-dashboard/'
applications/drone-command/src/api/detectionCountermeasure/detectionRange/index.js
New file
@@ -0,0 +1,55 @@
import request from '@/axios'
// 查
export const detectionRangePageApi = params => {
    return request({
        url: `/drone-fw/device/fwDevice/page`,
        method: 'get',
        params,
    })
}
// 增
export const detectionRangeAddApi = data => {
    return request({
        url: `/drone-fw/device/fwDevice/submit`,
        method: 'post',
        data,
    })
}
// 改
export const detectionRangeEditApi = data => {
    return request({
        url: `/drone-fw/device/fwDevice/submit`,
        method: 'post',
        data,
    })
}
// 新增或修改
export const detectionRangeSubmitApi = data => {
    return request({
        url: `/drone-fw/device/fwDevice/submit`,
        method: 'post',
        data,
    })
}
// 删
export const detectionRangeRemoveApi = params => {
    return request({
        url: `/drone-fw/device/fwDevice/remove`,
        method: 'post',
        params,
    })
}
// 详情
export const detectionRangeDetailApi = params => {
    return request({
        url: `/drone-fw/device/fwDevice/detail`,
        method: 'get',
        params,
    })
}
applications/drone-command/src/components/basic-container/main.vue
@@ -2,7 +2,7 @@
 * @Author       : yuan
 * @Date         : 2025-06-14 15:19:16
 * @LastEditors  : yuan
 * @LastEditTime : 2026-01-07 16:26:20
 * @LastEditTime : 2026-01-14 10:10:35
 * @FilePath     : \applications\drone-command\src\components\basic-container\main.vue
 * @Description  : 
 * Copyright 2025 OBKoro1, All Rights Reserved. 
@@ -50,25 +50,26 @@
  height: 0;
  flex: 1;
  padding: 30px;
  padding: 6px 30px;
  // box-sizing: border-box;
  // height: 100%;
  .basic-container__card {
    display: flex;
    flex-direction: column;
    height: 100% !important;
    overflow: hidden;
    background: transparent;
    .el-card__body {
    ::v-deep(.el-card__body) {
      padding: 0 !important;
      height: 0 !important;
      flex: 1 !important;
      display: flex;
      flex-direction: column;
      height: 100% !important;
      overflow: hidden;
      box-sizing: border-box;
      &>div {
        height: 0;
        flex: 1;
      }
    }
  }
applications/drone-command/src/components/map-container/device-map-container.vue
File was renamed from applications/drone-command/src/views/dataCockpit/components/MapContainer.vue
@@ -1,16 +1,22 @@
<template>
    <div class="ztzf-cesium map-container" id="cesium"></div>
    <div class="layer-control-root" :class="{ collapsed: props.leftCollapsed }">
    <div class="ztzf-cesium map-container" :id="props.containerId"></div>
    <div v-if="props.showLayerControl" class="layer-control-root" :class="{ collapsed: props.leftCollapsed }">
        <div class="layer-control-wrap" ref="layerWrapRef">
            <div class="layer-control" @click="toggleLayerPanel">
                <img :src="layerControlIcon" alt="图层控制">
                <img :src="layerControlIcon" alt="图层控制" />
            </div>
            <div v-if="showLayerPanel" class="layer-panel">
                <div class="panel-title">图层管理</div>
                <div class="panel-content">
                    <el-tree :data="layerTree" show-checkbox default-expand-all node-key="key" :props="layerTreeProps"
                        :default-checked-keys="defaultCheckedKeys" />
                    <el-tree
                        :data="layerTree"
                        show-checkbox
                        default-expand-all
                        node-key="key"
                        :props="layerTreeProps"
                        :default-checked-keys="defaultCheckedKeys"
                    />
                </div>
            </div>
        </div>
@@ -26,12 +32,20 @@
const props = defineProps({
    onlineDevices: {
        type: Array,
        default: () => []
        default: () => [],
    },
    leftCollapsed: {
        type: Boolean,
        default: false
    }
        default: false,
    },
    containerId: {
        type: String,
        default: 'device-map-container',
    },
    showLayerControl: {
        type: Boolean,
        default: true,
    },
})
let viewInstance = null
@@ -41,7 +55,7 @@
const layerWrapRef = ref(null)
const layerTreeProps = {
    label: 'label',
    children: 'children'
    children: 'children',
}
const defaultCheckedKeys = ['global', 'city-base']
const layerTree = ref([
@@ -50,8 +64,8 @@
        label: '地理信息图层',
        children: [
            { key: 'global', label: '全球地形' },
            { key: 'admin', label: '行政区划' }
        ]
            { key: 'admin', label: '行政区划' },
        ],
    },
    {
        key: 'city',
@@ -60,20 +74,20 @@
            { key: 'city-base', label: '皖山白模' },
            { key: 'city-grid', label: '皖山白模光栅网格' },
            { key: 'city-tilt', label: '皖山倾斜摄影' },
            { key: 'city-tilt-grid', label: '皖山倾斜摄影网格' }
        ]
            { key: 'city-tilt-grid', label: '皖山倾斜摄影网格' },
        ],
    },
    {
        key: 'sky',
        label: '空域要素图层',
        children: [
            { key: 'airspace', label: '空域边界' },
            { key: 'route', label: '飞行航路' }
        ]
    }
            { key: 'route', label: '飞行航路' },
        ],
    },
])
const getDevicePosition = (item) => {
const getDevicePosition = item => {
    const longitudeRaw = item.longitude ?? item.lng ?? item.lon
    const latitudeRaw = item.latitude ?? item.lat
    if (longitudeRaw == null || latitudeRaw == null) return null
@@ -85,7 +99,7 @@
const clearDeviceEntities = () => {
    if (!viewer) return
    deviceEntityIds.forEach((id) => {
    deviceEntityIds.forEach(id => {
        viewer.entities.removeById(id)
    })
    deviceEntityIds.clear()
@@ -94,7 +108,7 @@
const RING_STYLES = [
    { inner: 0, outer: 5000, gradient: ['#FF361C', '#360B00'] },
    { inner: 5000, outer: 8000, gradient: ['#FFC609', '#583300'] },
    { inner: 8000, outer: 10000, gradient: ['#2AEDBF', '#012B11'] }
    { inner: 8000, outer: 10000, gradient: ['#2AEDBF', '#012B11'] },
]
const MATERIAL_TYPE = 'RadialGradientMaterial'
@@ -108,7 +122,7 @@
            type: MATERIAL_TYPE,
            uniforms: {
                color1: new Cesium.Color(1.0, 1.0, 1.0, 1.0),
                color2: new Cesium.Color(0.0, 0.0, 0.0, 1.0)
                color2: new Cesium.Color(0.0, 0.0, 0.0, 1.0),
            },
            source: `
                czm_material czm_getMaterial(czm_materialInput materialInput) {
@@ -120,45 +134,10 @@
                    material.alpha = color.a;
                    return material;
                }
            `
            `,
        },
        translucent: () => true
        translucent: () => true,
    })
}
class RadialGradientMaterialProperty {
    constructor(color1, color2) {
        this._definitionChanged = new Cesium.Event()
        this.color1 = color1
        this.color2 = color2
    }
    get isConstant () {
        return true
    }
    get definitionChanged () {
        return this._definitionChanged
    }
    getType () {
        return MATERIAL_TYPE
    }
    getValue (time, result) {
        const target = result || {}
        target.color1 = this.color1
        target.color2 = this.color2
        return target
    }
    equals (other) {
        return (
            other instanceof RadialGradientMaterialProperty &&
            Cesium.Color.equals(this.color1, other.color1) &&
            Cesium.Color.equals(this.color2, other.color2)
        )
    }
}
const buildCirclePositions = (center, radiusMeters, steps = 64) => {
@@ -194,17 +173,49 @@
            id: entityId,
            polygon: {
                hierarchy: new Cesium.PolygonHierarchy(outerPositions, holes),
                material
            }
                material,
            },
        })
    })
}
const renderDeviceEntities = (devices) => {
    console.log(devices, 'devices')
class RadialGradientMaterialProperty {
    constructor(color1, color2) {
        this._definitionChanged = new Cesium.Event()
        this.color1 = color1
        this.color2 = color2
    }
    get isConstant() {
        return true
    }
    get definitionChanged() {
        return this._definitionChanged
    }
    getType() {
        return MATERIAL_TYPE
    }
    getValue(time, result) {
        const target = result || {}
        target.color1 = this.color1
        target.color2 = this.color2
        return target
    }
    equals(other) {
        return (
            other instanceof RadialGradientMaterialProperty &&
            Cesium.Color.equals(this.color1, other.color1) &&
            Cesium.Color.equals(this.color2, other.color2)
        )
    }
}
const renderDeviceEntities = devices => {
    if (!viewer) return
    clearDeviceEntities()
    devices.forEach((item, index) => {
        const position = getDevicePosition(item)
@@ -218,15 +229,15 @@
            billboard: {
                image: equipmentIcon,
                width: 40.34,
                height: 40.34
            }
                height: 40.34,
            },
        })
    })
}
watch(
    () => props.onlineDevices,
    (devices) => {
    devices => {
        renderDeviceEntities(devices || [])
    },
    { deep: true }
@@ -234,16 +245,17 @@
watch(
    () => props.leftCollapsed,
    (isCollapsed) => {
    isCollapsed => {
        if (isCollapsed) showLayerPanel.value = false
    }
)
const toggleLayerPanel = () => {
    if (!props.showLayerControl) return
    showLayerPanel.value = !showLayerPanel.value
}
const handleClickOutside = (event) => {
const handleClickOutside = event => {
    if (!showLayerPanel.value) return
    const target = event.target
    if (layerWrapRef.value?.contains(target)) return
@@ -253,11 +265,11 @@
onMounted(() => {
    document.addEventListener('click', handleClickOutside)
    viewInstance = new PublicCesium({
        dom: 'cesium',
        dom: props.containerId,
        flatMode: false,
        terrain: false,
        layerMode: 4,
        contour: false
        contour: false,
    })
    viewer = viewInstance.getViewer()
@@ -331,17 +343,14 @@
    .panel-title {
        padding: 0 16px;
        line-height: 42px;
        font-family: Open Sans, Open Sans;
        font-family: 'Open Sans', Open Sans;
        font-weight: 400;
        font-size: 12px;
        color: #FFFFFF;
        color: #ffffff;
        text-align: left;
        font-style: normal;
        text-transform: none;
        border-bottom: 1px solid rgba(70,70,100,0.5);
        border-bottom: 1px solid rgba(70, 70, 100, 0.5);
        box-sizing: border-box;
    }
@@ -354,7 +363,7 @@
    ::v-deep(.el-tree) {
        background: transparent;
        color: #C3C3DD;
        color: #c3c3dd;
        font-size: 12px;
        .el-tree-node {
@@ -365,7 +374,7 @@
                align-items: center;
                height: 40px !important;
                line-height: 40px !important;
                border-bottom: 1px solid rgba(70,70,100,0.5);
                border-bottom: 1px solid rgba(70, 70, 100, 0.5);
                box-sizing: border-box;
            }
@@ -375,14 +384,11 @@
                }
            }
        }
    }
}
.layer-panel :deep(.el-tree-node__label) {
    color: #C3C3DD;
    color: #c3c3dd;
}
.layer-panel :deep(.el-tree-node__expand-icon) {
@@ -412,20 +418,20 @@
.layer-panel :deep(.el-tree-node.is-current > .el-tree-node__content) {
    background: transparent !important;
    color: #FFFFFF;
    color: #ffffff;
}
.layer-panel :deep(.el-tree-node.is-current > .el-tree-node__content .el-tree-node__label) {
    color: #FFFFFF;
    color: #ffffff;
}
.layer-panel :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
    background-color: #023AFF;
    border-color: #023AFF;
    background-color: #023aff;
    border-color: #023aff;
}
.layer-panel :deep(.el-checkbox__inner) {
    background-color: transparent;
    border-color: #A1A3D4;
    border-color: #a1a3d4;
}
</style>
applications/drone-command/src/styles/common/cockpit.scss
@@ -449,212 +449,382 @@
        }
    }
    .history-search {
        .el-form-item__label {
            font-family: Source Han Sans CN, Source Han Sans CN;
            font-weight: 500;
            font-size: 14px;
            color: #C5C9D6;
            font-style: normal;
            text-transform: none;
        }
    .el-dialog__body {
        height: 0;
        flex: 1;
        display: flex;
        flex-direction: column;
    }
}
        .ztzf-data-cockpit-search-input {
            .el-input__wrapper {
                background: #2E2E48;
            }
.ztzf-dialog-history-search {
    .el-form-item__label {
        font-family: Source Han Sans CN, Source Han Sans CN;
        font-weight: 500;
        font-size: 14px;
        color: #C5C9D6;
        font-style: normal;
        text-transform: none;
    }
            &.is-disabled {
                .el-input__wrapper {
                    background-color: #2E2E48;
                }
            }
        }
        .ztzf-data-cockpit-date-picker {
    .ztzf-data-cockpit-search-input {
        .el-input__wrapper {
            background: #2E2E48;
        }
        .ztzf-data-cockpit-date-picker-popper {
            background: #2E2E48 !important;
        &.is-disabled {
            .el-input__wrapper {
                background-color: #2E2E48;
            }
        }
    }
            .el-picker-panel {
                .el-picker-panel__body-wrapper {
                    .el-picker-panel__body {
                        background: #2E2E48 !important;
    .ztzf-data-cockpit-date-picker {
        background: #2E2E48;
    }
                        .el-date-picker__time-header {
    .ztzf-data-cockpit-date-picker-popper {
        background: #2E2E48 !important;
                            .el-date-picker__editor-wrap {
                                .el-time-panel {
        .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;
                                    // 将 time-picker-popper 的样式移到这里
                                    .el-time-panel__header {
                                        background: #2E2E48 !important;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            .el-popper__arrow {
                &::before {
                    background: #2E2E48 !important;
                    border: 1px solid #2E2E48 !important;
                    box-sizing: border-box;
                }
            }
        }
        .history-search-actions {
            .el-button {
                width: 96px;
                background: transparent !important;
                border-radius: 4px 4px 4px 4px;
                border: 1px solid rgba(255, 255, 255, 0.3);
                .el-icon {
                    font-size: 16px;
                    color: #fff;
                }
        .el-popper__arrow {
            &::before {
                background: #2E2E48 !important;
                border: 1px solid #2E2E48 !important;
                box-sizing: border-box;
            }
        }
    }
    .el-dialog__body {
    .history-search-actions {
        .el-button {
            width: 96px;
            background: transparent !important;
            border-radius: 4px 4px 4px 4px;
            border: 1px solid rgba(255, 255, 255, 0.3);
            .el-icon {
                font-size: 16px;
                color: #fff;
            }
        }
    }
}
.ztzf-page-history-search {
    .el-form-item__label {
        font-family: Source Han Sans CN, Source Han Sans CN;
        font-weight: 500;
        font-size: 14px;
        color: #C5C9D6;
        font-style: normal;
        text-transform: none;
    }
    .ztzf-data-cockpit-select {
        .el-select__wrapper {
            background: #1C1C31;
        }
    }
    .ztzf-data-cockpit-search-input {
        .el-input__wrapper {
            background: #1C1C31;
        }
        &.is-disabled {
            .el-input__wrapper {
                background-color: #1C1C31;
            }
        }
    }
    .ztzf-data-cockpit-date-picker {
        background: #1C1C31;
    }
    .ztzf-data-cockpit-date-picker-popper {
        background: #1C1C31 !important;
        .el-picker-panel {
            .el-picker-panel__body-wrapper {
                .el-picker-panel__body {
                    background: #1C1C31 !important;
                    .el-date-picker__time-header {
                        .el-date-picker__editor-wrap {
                            .el-time-panel {
                                background: #1C1C31 !important;
                                // 将 time-picker-popper 的样式移到这里
                                .el-time-panel__header {
                                    background: #1C1C31 !important;
                                }
                            }
                        }
                    }
                }
            }
        }
        .el-popper__arrow {
            &::before {
                background: #1C1C31 !important;
                border: 1px solid #1C1C31 !important;
                box-sizing: border-box;
            }
        }
    }
    .history-search-actions {
        .el-button {
            width: 96px;
            background: transparent !important;
            border-radius: 4px 4px 4px 4px;
            border: 1px solid rgba(255, 255, 255, 0.3);
            .el-icon {
                font-size: 16px;
                color: #fff;
            }
        }
        .search-btn {
            background: #1C1C31 !important;
            border: 1px solid rgba(227, 227, 236, 0.3) !important;
        }
    }
}
.ztzf-dialog-history-search,
.ztzf-page-history-search {
    display: flex;
    .el-form-item {
        margin-left: 30px;
        margin-bottom: 0;
        &:first-child {
            margin-left: 0;
        }
    }
    .el-form-item__label {
        margin-right: 12px;
        height: 40px;
        line-height: 40px;
    }
    .el-input {
        width: 200px;
        height: 40px;
    }
    .el-select {
        .el-select__wrapper {
            width: 200px;
            height: 40px;
        }
    }
    .el-date-editor {
        width: 240px;
        height: 40px;
    }
    .el-button {
        width: 96px;
        height: 40px;
    }
}
.ztzf-table-toolbar {
    margin-top: 30px;
    .el-button {
        min-width: 88px;
    }
}
.ztzf-table-container {
    margin-top: 20px;
    height: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
    .ztzf-table-content {
        height: 0;
        flex: 1;
        display: flex;
        flex-direction: column;
        .table-container {
        .ztzf-data-cockpit-table {
            height: 0;
            flex: 1;
            display: flex;
            flex-direction: column;
            background: transparent !important;
            .table-content {
            .el-table--border .el-table__inner-wrapper:after,
            .el-table--border:after,
            .el-table--border:before,
            .el-table__inner-wrapper:before {
                height: 0;
                flex: 1;
                display: flex;
                flex-direction: column;
                .ztzf-data-cockpit-table {
                    height: 0;
                    flex: 1;
                    background: transparent !important;
                    .el-table--border .el-table__inner-wrapper:after, .el-table--border:after, .el-table--border:before, .el-table__inner-wrapper:before {
                        height: 0;
                    }
                    tr {
                        background: transparent !important;
                    }
                    th.el-table__cell {
                        border-bottom: 1px solid #323241;
                        background: transparent !important;
                        .cell {
                            font-family: Source Han Sans CN, Source Han Sans CN;
                            font-weight: 500;
                            font-size: 14px;
                            color: #FFFFFF;
                            line-height: 22px;
                            font-style: normal;
                            text-transform: none;
                        }
                    }
                    td.el-table__cell {
                        border-bottom: 1px solid #323241;
                        background: transparent !important;
                        .cell {
                            font-family: Source Han Sans CN, Source Han Sans CN;
                            font-weight: 400;
                            font-size: 14px;
                            color: #9E9EBA;
                            line-height: 18px;
                            font-style: normal;
                            text-transform: none;
                        }
                    }
                }
            }
            .table-pagination {
                padding-top: 30px;
                padding-bottom: 10px;
                display: flex;
                justify-content: flex-end;
            tr {
                background: transparent !important;
            }
                .el-pagination__total {
            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: 400;
                    font-weight: 500;
                    font-size: 14px;
                    color: #9E9EBA;
                    color: #FFFFFF;
                    line-height: 22px;
                    font-style: normal;
                    text-transform: none;
                }
            }
                .btn-prev,
                .btn-next {
                    background: transparent !important;
            td.el-table__cell {
                padding: 9px 0;
                height: 56px;
                border-bottom: 1px solid #323241;
                background: transparent !important;
                    .el-icon {
                        color: #C9CDD4;
                    }
                .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;
                }
            }
                .el-pager {
                    li {
                        margin-left: 8px;
                        font-family: Nunito Sans, Nunito Sans;
                        font-weight: 600;
                        font-size: 14px;
                        color: #9E9EBA;
                        font-style: normal;
                        text-transform: none;
                        background: transparent;
            td.operation-btns .cell {
                display: flex;
                flex-wrap: nowrap;
                gap: 12px;
                /* 按钮间距(推荐) */
                white-space: nowrap;
                /* 双保险 */
                        &:first-child {
                            margin-left: 0;
                        }
                a {
                    min-width: 28px;
                    color: #6A89FF;
                        &.is-active {
                            font-family: Nunito Sans, Nunito Sans;
                            font-weight: 600;
                            font-size: 14px;
                            color: #477EFF;
                            font-style: normal;
                            text-transform: none;
                            background: rgba(71, 126, 255, 0.16);
                        }
                    }
                }
                .el-select {
                    .el-select__wrapper {
                        .el-select__placeholder {
                            font-family: Source Han Sans CN, Source Han Sans CN;
                            font-weight: 400;
                            font-size: 14px;
                            color: #9E9EBA;
                            font-style: normal;
                            text-transform: none;
                        }
                        background: #1C1C31;
                        border: none;
                        box-shadow: none;
                    &:last-child {
                        margin-right: 0;
                    }
                }
            }
        }
    }
    .ztzf-table-content-bg {
        background: #1A1A2A;
    }
}
.ztzf-table-pagination {
    padding-top: 30px;
    padding-bottom: 10px;
    display: flex;
    justify-content: flex-end;
    .el-pagination__total {
        font-family: Source Han Sans CN, Source Han Sans CN;
        font-weight: 400;
        font-size: 14px;
        color: #9E9EBA;
        font-style: normal;
        text-transform: none;
    }
    .btn-prev,
    .btn-next {
        background: transparent !important;
        .el-icon {
            color: #C9CDD4;
        }
    }
    .el-pager {
        li {
            margin-left: 8px;
            font-family: Nunito Sans, Nunito Sans;
            font-weight: 600;
            font-size: 14px;
            color: #9E9EBA;
            font-style: normal;
            text-transform: none;
            background: transparent;
            &:first-child {
                margin-left: 0;
            }
            &.is-active {
                font-family: Nunito Sans, Nunito Sans;
                font-weight: 600;
                font-size: 14px;
                color: #477EFF;
                font-style: normal;
                text-transform: none;
                background: rgba(71, 126, 255, 0.16);
            }
        }
    }
    .el-select {
        .el-select__wrapper {
            .el-select__placeholder {
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: 400;
                font-size: 14px;
                color: #9E9EBA;
                font-style: normal;
                text-transform: none;
            }
            background: #1C1C31;
            border: none;
            box-shadow: none;
        }
    }
}
applications/drone-command/src/views/api.txt
@@ -1,9 +1,9 @@
## 历史告警-实时告警
## 分页
**接口地址**:`/cockpit/dataCockpit/alarmLog`
**接口地址**:`/device/fwDevice/page`
**请求方式**:`GET`
@@ -15,7 +15,7 @@
**响应数据类型**:`*/*`
**接口描述**:<p>传入fwDroneAlarmRecord</p>
**接口描述**:<p>传入fwDevice</p>
@@ -27,31 +27,12 @@
| 参数名称 | 参数说明 | 请求类型    | 是否必须 | 数据类型 | schema |
| -------- | -------- | ----- | -------- | -------- | ------ |
|alarmTime|告警时间|query|false|string(date-time)||
|alarmType|告警类型:1.实时告警/2.历史告警|query|false|string||
|areaCode|区域编码|query|false|string||
|areaDivideId|区域id ja_fw_area_divide|query|false|string||
|areaName|区域名称|query|false|string||
|counterWay|反制方式:1.信号干扰/2.诱导驱离/无|query|false|string||
|belongDept|所属部门|query|false|integer(int64)||
|current|当前页|query|false|integer(int32)||
|defenseSceneId|场景id ja_fw_defense_scene.id|query|false|string||
|deviceId|反无设备ID(关联ja_fw_device.id)|query|false|integer(int64)||
|deviceType|设备类型|query|false|string||
|droneName|无人机名称|query|false|string||
|droneSerialNo|无人机序列号|query|false|string||
|droneType|无人机类型(1.微型机/2.植保机...)|query|false|string||
|endTime|结束时间|query|false|string||
|flightHeightM|飞行高度(m)|query|false|integer(int32)||
|flightSpeedMs|飞行速度(m/s)|query|false|number||
|flightStatus|飞行状态:1.侦测中/2.反制中|query|false|string||
|id|主键id|query|false|integer(int64)||
|latitude|当前纬度|query|false|number(double)||
|longitude|当前经度|query|false|number(double)||
|signalFreqMhz|信号频段(MHz)|query|false|integer(int32)||
|deviceName|设备名称|query|false|string||
|deviceType|设备类型(察打一体/便捷侦测箱/反制枪)|query|false|string||
|effectiveRangeKmIsNotNull|有效范围是否为空:0-不处理 1-不为空 2-为空|query|false|integer(int32)||
|size|每页的数量|query|false|integer(int32)||
|startTime|开始时间|query|false|string||
|stayDuration|停留时长 (秒) |query|false|string||
|triggerReason|触发原因|query|false|string||
**响应状态**:
@@ -59,7 +40,7 @@
| 状态码 | 说明 | schema |
| -------- | -------- | ----- | 
|200|OK|R«IPage«FwDroneAlarmRecordVO»»|
|200|OK|R«IPage«FwDeviceVO»»|
|401|Unauthorized||
|403|Forbidden||
|404|Not Found||
@@ -71,31 +52,32 @@
| 参数名称 | 参数说明 | 类型 | schema |
| -------- | -------- | ----- |----- | 
|code|状态码|integer(int32)|integer(int32)|
|data|承载数据|IPage«FwDroneAlarmRecordVO»|IPage«FwDroneAlarmRecordVO»|
|data|承载数据|IPage«FwDeviceVO»|IPage«FwDeviceVO»|
|&emsp;&emsp;current||integer(int64)||
|&emsp;&emsp;pages||integer(int64)||
|&emsp;&emsp;records||array|FwDroneAlarmRecordVO|
|&emsp;&emsp;&emsp;&emsp;alarmTime|告警时间|string||
|&emsp;&emsp;&emsp;&emsp;alarmType|告警类型:1.实时告警/2.历史告警|string||
|&emsp;&emsp;&emsp;&emsp;areaCode|区域编码|string||
|&emsp;&emsp;&emsp;&emsp;areaName|区域名称|string||
|&emsp;&emsp;&emsp;&emsp;counterWay|反制方式:1.信号干扰/2.诱导驱离/3.无|string||
|&emsp;&emsp;&emsp;&emsp;deviceId|反无设备ID(关联ja_fw_device.id)|integer||
|&emsp;&emsp;&emsp;&emsp;deviceModel|设备型号|string||
|&emsp;&emsp;records||array|FwDeviceVO|
|&emsp;&emsp;&emsp;&emsp;azimuth|方位角 0-360°|integer||
|&emsp;&emsp;&emsp;&emsp;batteryPct|电量百分比 0-100|integer||
|&emsp;&emsp;&emsp;&emsp;belongDept|所属部门id|integer||
|&emsp;&emsp;&emsp;&emsp;belongDeptName|所属部门名称|string||
|&emsp;&emsp;&emsp;&emsp;charger|负责人|string||
|&emsp;&emsp;&emsp;&emsp;contactPhone|联系电话|string||
|&emsp;&emsp;&emsp;&emsp;createTime|创建时间|string||
|&emsp;&emsp;&emsp;&emsp;detectTargetCnt|侦测目标数量|integer||
|&emsp;&emsp;&emsp;&emsp;deviceAtt|设备属性(无线电/光电/雷达)|string||
|&emsp;&emsp;&emsp;&emsp;deviceModel|型号|string||
|&emsp;&emsp;&emsp;&emsp;deviceName|设备名称|string||
|&emsp;&emsp;&emsp;&emsp;deviceType|设备类型(1.便捷侦测箱/2.反制枪/3.察打一体)|string||
|&emsp;&emsp;&emsp;&emsp;droneName|无人机名称|string||
|&emsp;&emsp;&emsp;&emsp;droneSerialNo|无人机序列号|string||
|&emsp;&emsp;&emsp;&emsp;droneType|无人机类型(1.微型机/2.植保机...)|string||
|&emsp;&emsp;&emsp;&emsp;flightHeightM|飞行高度(m)|integer||
|&emsp;&emsp;&emsp;&emsp;flightSpeedMs|飞行速度(m/s)|number||
|&emsp;&emsp;&emsp;&emsp;flightStatus|飞行状态:1.侦测中/2.反制中|string||
|&emsp;&emsp;&emsp;&emsp;id|主键id|integer||
|&emsp;&emsp;&emsp;&emsp;latitude|当前纬度|number||
|&emsp;&emsp;&emsp;&emsp;longitude|当前经度|number||
|&emsp;&emsp;&emsp;&emsp;signalFreqMhz|信号频段(MHz)|integer||
|&emsp;&emsp;&emsp;&emsp;stayDuration|停留时长(秒)|string||
|&emsp;&emsp;&emsp;&emsp;triggerReason|触发原因|string||
|&emsp;&emsp;&emsp;&emsp;deviceSpecification|规格|string||
|&emsp;&emsp;&emsp;&emsp;deviceType|设备类型(察打一体/便捷侦测箱/反制枪)|string||
|&emsp;&emsp;&emsp;&emsp;effectiveRangeKm|有效范围 km|number||
|&emsp;&emsp;&emsp;&emsp;elevation|俯仰角 -90-+90°|integer||
|&emsp;&emsp;&emsp;&emsp;id|主键|integer||
|&emsp;&emsp;&emsp;&emsp;manufacturer|生产厂商|string||
|&emsp;&emsp;&emsp;&emsp;purpose|用途|string||
|&emsp;&emsp;&emsp;&emsp;remark|备注|string||
|&emsp;&emsp;&emsp;&emsp;source|来源|string||
|&emsp;&emsp;&emsp;&emsp;status|运行状态(0:在线/1:离线/2:故障/3:报废)|integer||
|&emsp;&emsp;&emsp;&emsp;workMode|工作模式:1.侦测中/2.信号干扰中/3.诱导驱离中/4.待机|string||
|&emsp;&emsp;size||integer(int64)||
|&emsp;&emsp;total||integer(int64)||
|msg|返回消息|string||
@@ -111,27 +93,28 @@
        "pages": 0,
        "records": [
            {
                "alarmTime": "",
                "alarmType": "",
                "areaCode": "",
                "areaName": "",
                "counterWay": "",
                "deviceId": 0,
                "azimuth": 0,
                "batteryPct": 0,
                "belongDept": 0,
                "belongDeptName": "",
                "charger": "",
                "contactPhone": "",
                "createTime": "",
                "detectTargetCnt": 0,
                "deviceAtt": "",
                "deviceModel": "",
                "deviceName": "",
                "deviceSpecification": "",
                "deviceType": "",
                "droneName": "",
                "droneSerialNo": "",
                "droneType": "",
                "flightHeightM": 0,
                "flightSpeedMs": 0,
                "flightStatus": "",
                "effectiveRangeKm": 0,
                "elevation": 0,
                "id": 0,
                "latitude": 0,
                "longitude": 0,
                "signalFreqMhz": 0,
                "stayDuration": "",
                "triggerReason": ""
                "manufacturer": "",
                "purpose": "",
                "remark": "",
                "source": "",
                "status": 0,
                "workMode": ""
            }
        ],
        "size": 0,
@@ -140,4 +123,218 @@
    "msg": "",
    "success": true
}
```
## 详情
**接口地址**:`/device/fwDevice/detail`
**请求方式**:`GET`
**请求数据类型**:`application/x-www-form-urlencoded`
**响应数据类型**:`*/*`
**接口描述**:<p>传入fwDevice</p>
**请求参数**:
**请求参数**:
| 参数名称 | 参数说明 | 请求类型    | 是否必须 | 数据类型 | schema |
| -------- | -------- | ----- | -------- | -------- | ------ |
|id|主键|query|true|integer(int64)||
**响应状态**:
| 状态码 | 说明 | schema |
| -------- | -------- | ----- |
|200|OK|R«FwDeviceVO»|
|401|Unauthorized||
|403|Forbidden||
|404|Not Found||
**响应参数**:
| 参数名称 | 参数说明 | 类型 | schema |
| -------- | -------- | ----- |----- |
|code|状态码|integer(int32)|integer(int32)|
|data|承载数据|FwDeviceVO|FwDeviceVO|
|&emsp;&emsp;azimuth|方位角 0-360°|integer(int32)||
|&emsp;&emsp;batteryPct|电量百分比 0-100|integer(int32)||
|&emsp;&emsp;belongDept|所属部门id|integer(int64)||
|&emsp;&emsp;belongDeptName|所属部门名称|string||
|&emsp;&emsp;charger|负责人|string||
|&emsp;&emsp;contactPhone|联系电话|string||
|&emsp;&emsp;createTime|创建时间|string(date-time)||
|&emsp;&emsp;detectTargetCnt|侦测目标数量|integer(int32)||
|&emsp;&emsp;deviceAtt|设备属性(无线电/光电/雷达)|string||
|&emsp;&emsp;deviceModel|型号|string||
|&emsp;&emsp;deviceName|设备名称|string||
|&emsp;&emsp;deviceSpecification|规格|string||
|&emsp;&emsp;deviceType|设备类型(察打一体/便捷侦测箱/反制枪)|string||
|&emsp;&emsp;effectiveRangeKm|有效范围 km|number||
|&emsp;&emsp;elevation|俯仰角 -90-+90°|integer(int32)||
|&emsp;&emsp;id|主键|integer(int64)||
|&emsp;&emsp;manufacturer|生产厂商|string||
|&emsp;&emsp;purpose|用途|string||
|&emsp;&emsp;remark|备注|string||
|&emsp;&emsp;source|来源|string||
|&emsp;&emsp;status|运行状态(0:在线/1:离线/2:故障/3:报废)|integer(int32)||
|&emsp;&emsp;workMode|工作模式:1.侦测中/2.信号干扰中/3.诱导驱离中/4.待机|string||
|msg|返回消息|string||
|success|是否成功|boolean||
**响应示例**:
```javascript
{
    "code": 0,
    "data": {
        "azimuth": 0,
        "batteryPct": 0,
        "belongDept": 0,
        "belongDeptName": "",
        "charger": "",
        "contactPhone": "",
        "createTime": "",
        "detectTargetCnt": 0,
        "deviceAtt": "",
        "deviceModel": "",
        "deviceName": "",
        "deviceSpecification": "",
        "deviceType": "",
        "effectiveRangeKm": 0,
        "elevation": 0,
        "id": 0,
        "manufacturer": "",
        "purpose": "",
        "remark": "",
        "source": "",
        "status": 0,
        "workMode": ""
    },
    "msg": "",
    "success": true
}
```
## 新增或修改
**接口地址**:`/device/fwDevice/submit`
**请求方式**:`POST`
**请求数据类型**:`application/json`
**响应数据类型**:`*/*`
**接口描述**:<p>传入fwDevice</p>
**请求示例**:
```javascript
{
  "belongDept": 0,
  "charger": "",
  "contactPhone": "",
  "deviceAtt": "",
  "deviceModel": "",
  "deviceName": "",
  "deviceSpecification": "",
  "deviceStatusList": [],
  "deviceType": "",
  "effectiveRangeKm": 0,
  "effectiveRangeKmIsNotNull": 0,
  "id": 0,
  "manufacturer": "",
  "purpose": "",
  "source": ""
}
```
**请求参数**:
**请求参数**:
| 参数名称 | 参数说明 | 请求类型    | 是否必须 | 数据类型 | schema |
| -------- | -------- | ----- | -------- | -------- | ------ |
|fwDevice|fwDevice|body|true|FwDeviceDTO|FwDeviceDTO|
|&emsp;&emsp;belongDept|所属部门||false|integer(int64)||
|&emsp;&emsp;charger|负责人||false|string||
|&emsp;&emsp;contactPhone|联系电话||false|string||
|&emsp;&emsp;deviceAtt|设备属性(无线电/光电/雷达)||false|string||
|&emsp;&emsp;deviceModel|型号||false|string||
|&emsp;&emsp;deviceName|设备名称||true|string||
|&emsp;&emsp;deviceSpecification|规格||false|string||
|&emsp;&emsp;deviceStatusList|设备状态,多个状态用逗号隔开||false|array|string|
|&emsp;&emsp;deviceType|设备类型(察打一体/便捷侦测箱/反制枪)||false|string||
|&emsp;&emsp;effectiveRangeKm|有效范围 km||false|number||
|&emsp;&emsp;effectiveRangeKmIsNotNull|是否有范围 0不过滤 1有 2没有||false|integer(int32)||
|&emsp;&emsp;id|主键||false|integer(int64)||
|&emsp;&emsp;manufacturer|生产厂商||false|string||
|&emsp;&emsp;purpose|用途||false|string||
|&emsp;&emsp;source|来源||false|string||
**响应状态**:
| 状态码 | 说明 | schema |
| -------- | -------- | ----- |
|200|OK|R|
|201|Created||
|401|Unauthorized||
|403|Forbidden||
|404|Not Found||
**响应参数**:
| 参数名称 | 参数说明 | 类型 | schema |
| -------- | -------- | ----- |----- |
|code|状态码|integer(int32)|integer(int32)|
|data|承载数据|object||
|msg|返回消息|string||
|success|是否成功|boolean||
**响应示例**:
```javascript
{
    "code": 0,
    "data": {},
    "msg": "",
    "success": true
}
```
applications/drone-command/src/views/areaManage/partition/index.vue
@@ -1,79 +1,75 @@
<template>
    <basic-container>
        <el-form ref="queryParamsRef" :model="searchParams">
            <el-row :gutter="16">
                <el-col :span="4">
                    <el-form-item label="区域名称" prop="areaName">
                        <el-input v-model="searchParams.areaName" placeholder="请输入" clearable @clear="handleSearch" />
                    </el-form-item>
                </el-col>
                <el-col :span="4">
                    <el-form-item label="区域类型" prop="areaType">
                        <el-select v-model="searchParams.areaType" placeholder="请选择" clearable @change="handleSearch">
                            <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="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="areaName">
                <el-input class="ztzf-data-cockpit-search-input" v-model="searchParams.areaName" placeholder="请输入"
                    clearable @clear="handleSearch" />
            </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="searchParams.areaType" placeholder="请选择" clearable @change="handleSearch">
                    <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 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>
        <div>
            <el-button type="primary" @click="openForm('add')">新增</el-button>
        <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>
        </div>
        <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55" />
            <el-table-column type="index" width="60" label="序号" />
            <el-table-column prop="areaName" label="区域名称" />
            <el-table-column label="区域位置">
                <template v-slot="{ row }">
                    {{ formatLocation(row) }}
                </template>
            </el-table-column>
            <el-table-column prop="areaSize" label="区域面积(k㎡)" />
            <el-table-column prop="areaType" label="区域类型">
                <template v-slot="{ row }">
                    {{ getDictLabel(row.areaType, dictObj.areaType) }}
                </template>
            </el-table-column>
            <el-table-column prop="triggerCondition" label="触发条件" />
            <el-table-column prop="responseMechanism" label="响应机制" />
            <el-table-column prop="controlLevel" label="管控级别">
                <template v-slot="{ row }">
                    {{ getDictLabel(row.controlLevel, dictObj.controlLevel) }}
                </template>
            </el-table-column>
            <el-table-column prop="policeStationName" label="关联派出所" />
            <el-table-column label="可飞行时间段">
                <template v-slot="{ row }">
                    {{ formatFlyDate(row) }}
                </template>
            </el-table-column>
            <el-table-column label="操作">
                <template v-slot="{ row }">
                    <el-link @click="openForm('view', row)" type="primary">查看</el-link>
                    <el-link @click="openForm('edit', row)" type="warning">编辑</el-link>
                    <el-link @click="handleDelete(row)" type="danger">删除</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" @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="areaName" show-overflow-tooltip width="150" label="区域名称" />
                    <el-table-column show-overflow-tooltip width="160" label="区域位置">
                        <template v-slot="{ row }">
                            {{ formatLocation(row) }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="areaSize" show-overflow-tooltip width="130" label="区域面积(k㎡)" />
                    <el-table-column prop="areaType" show-overflow-tooltip width="120" label="区域类型">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.areaType, dictObj.areaType) }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="triggerCondition" show-overflow-tooltip width="130" label="触发条件" />
                    <el-table-column prop="responseMechanism" show-overflow-tooltip width="130" label="响应机制" />
                    <el-table-column prop="controlLevel" show-overflow-tooltip width="120" label="管控级别">
                        <template v-slot="{ row }">
                            {{ 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="可飞行时间段">
                        <template v-slot="{ row }">
                            {{ formatFlyDate(row) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('view', row)">查看</el-link>
                            <el-link @click="openForm('edit', row)">编辑</el-link>
                            <el-link @click="handleDelete(row)">删除</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" />
@@ -81,6 +77,7 @@
</template>
<script setup>
import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
import { nextTick, onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fwAreaDividePageApi, fwAreaDivideRemoveApi } from './partitionApi'
@@ -105,7 +102,7 @@
const dialogVisible = ref(false)
// 获取列表
async function getList() {
async function getList () {
    loading.value = true
    try {
        const res = await fwAreaDividePageApi(searchParams.value)
@@ -117,20 +114,20 @@
}
// 查询
function handleSearch() {
function handleSearch () {
    searchParams.value.current = 1
    getList()
}
// 重置查询
function resetForm() {
function resetForm () {
    queryParamsRef.value?.resetFields()
    searchParams.value.current = 1
    getList()
}
// 删除
async function handleDelete(row) {
async function handleDelete (row) {
    const tips = row ? '该条' : '选中的项'
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' })
    const ids = row ? row.id : selectedIds.value.join(',')
@@ -141,35 +138,35 @@
}
// 勾选值设置
function handleSelectionChange(rows) {
function handleSelectionChange (rows) {
    selectedIds.value = rows.map(item => item.id)
}
function formatLocation(row) {
function formatLocation (row) {
    if (row?.longitude == null || row?.latitude == null) return ''
    return `${row.longitude}, ${row.latitude}`
}
function formatFlyDate(row) {
function formatFlyDate (row) {
    if (!row?.flyDateStart && !row?.flyDateEnd) return ''
    return `${row.flyDateStart || '-'} ~ ${row.flyDateEnd || '-'}`
}
const dictObj = ref({
    areaType: [], //区域类型
    controlLevel: [], //管控级别
    areaType: [], // 区域类型
    controlLevel: [], // 管控级别
})
provide('dictObj', dictObj)
// 获取字典
function getDictList() {
function getDictList () {
    getDictionaryByCode('areaType,controlLevel').then(res => {
        dictObj.value = res.data.data
    })
}
// 新增/编辑/查看 弹框
function openForm(mode, row) {
function openForm (mode, row) {
    dialogVisible.value = true
    nextTick(() => {
        dialogRef.value?.open({ mode, row })
applications/drone-command/src/views/areaManage/precinctInfo/index.vue
@@ -1,47 +1,47 @@
<template>
    <basic-container>
        <el-form ref="queryParamsRef" :model="searchParams">
            <el-row :gutter="16">
                <el-col :span="4">
                    <el-form-item label="派出所名称" prop="stationName">
                        <el-input v-model="searchParams.stationName" placeholder="请输入" clearable @clear="handleSearch" />
                    </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="stationName">
                <el-input class="ztzf-data-cockpit-search-input" v-model="searchParams.stationName" placeholder="请输入"
                    clearable @clear="handleSearch" />
            </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>
        <div>
            <el-button type="primary" @click="openForm('add')">新增</el-button>
        <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>
        </div>
        <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55" />
            <el-table-column type="index" width="60" label="序号" />
            <el-table-column prop="stationName" label="派出所名称" />
            <el-table-column prop="address" label="位置" />
            <el-table-column prop="longitude" label="经度" />
            <el-table-column prop="latitude" label="纬度" />
            <el-table-column prop="contactPerson" label="联系人" />
            <el-table-column prop="contactPhone" label="联系方式" />
            <el-table-column label="操作">
                <template v-slot="{ row }">
                    <el-link @click="openForm('edit', row)" type="warning">编辑</el-link>
                    <el-link @click="handleDelete(row)" type="danger">删除</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" @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 label="操作" class-name="operation-btns">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('edit', row)">编辑</el-link>
                            <el-link @click="handleDelete(row)">删除</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 ref="dialogRef" @success="getList" v-if="dialogVisible" v-model="dialogVisible" />
@@ -49,7 +49,8 @@
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
import { nextTick, onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fwPoliceStationPageApi, fwPoliceStationRemoveApi } from './precinctInfoApi'
import FormDiaLog from './FormDiaLog.vue'
@@ -69,7 +70,7 @@
const dialogVisible = ref(false)
// 获取列表
async function getList() {
async function getList () {
    loading.value = true
    try {
        const res = await fwPoliceStationPageApi(searchParams.value)
@@ -81,20 +82,20 @@
}
// 查询
function handleSearch() {
function handleSearch () {
    searchParams.value.current = 1
    getList()
}
// 重置查询
function resetForm() {
function resetForm () {
    queryParamsRef.value?.resetFields()
    searchParams.value.current = 1
    getList()
}
// 删除
async function handleDelete(row) {
async function handleDelete (row) {
    const tips = row ? '该条' : '选中的项'
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' })
    const ids = row ? row.id : selectedIds.value.join(',')
@@ -105,12 +106,12 @@
}
// 勾选值设置
function handleSelectionChange(rows) {
function handleSelectionChange (rows) {
    selectedIds.value = rows.map(item => item.id)
}
// 新增
function openForm(mode, row) {
function openForm (mode, row) {
    dialogVisible.value = true
    nextTick(() => {
        dialogRef.value?.open({ mode, row })
applications/drone-command/src/views/areaManage/sceneConfig/index.vue
@@ -1,78 +1,74 @@
<template>
    <basic-container>
        <el-form ref="queryParamsRef" :model="searchParams">
            <el-row :gutter="16">
                <el-col :span="4">
                    <el-form-item label="场景名称" prop="sceneName">
                        <el-input v-model="searchParams.sceneName" placeholder="请输入" clearable @clear="handleSearch" />
                    </el-form-item>
                </el-col>
                <el-col :span="4">
                    <el-form-item label="场景类型" prop="sceneType">
                        <el-select v-model="searchParams.sceneType" placeholder="请选择" clearable @change="handleSearch">
                            <el-option
                                v-for="item in dictObj.sceneType"
                                :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="status">
                        <el-select v-model="searchParams.status" placeholder="请选择" clearable @change="handleSearch">
                            <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
                        </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="sceneName">
                <el-input class="ztzf-data-cockpit-search-input" v-model="searchParams.sceneName" placeholder="请输入"
                    clearable @clear="handleSearch" />
            </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="searchParams.sceneType" placeholder="请选择" clearable @change="handleSearch">
                    <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="status">
                <el-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-select-popper"
                    v-model="searchParams.status" placeholder="请选择" clearable @change="handleSearch">
                    <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
                </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>
        <div>
            <el-button type="primary" @click="openForm('add')">新增</el-button>
        <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>
        </div>
        <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55" />
            <el-table-column type="index" width="60" label="序号" />
            <el-table-column prop="deviceName" label="设备名称" />
            <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-column label="指挥点位置">
                <template v-slot="{ row }">
                    {{ formatLocation(row) }}
                </template>
            </el-table-column>
            <el-table-column prop="defenseLeader" label="防控负责人" />
            <el-table-column prop="leaderPhone" label="防控负责人电话" />
            <el-table-column prop="areaCount" label="区域数量" />
            <el-table-column prop="counterDeviceCount" label="反制设备数量" />
            <el-table-column prop="detectDeviceCount" label="探测设备数量" />
            <el-table-column label="操作">
                <template v-slot="{ row }">
                    <el-link @click="openForm('view', row)" type="primary">查看</el-link>
                    <el-link @click="openForm('edit', row)" type="warning">编辑</el-link>
                    <el-link @click="handleDelete(row)" type="danger">删除</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" @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="sceneName" show-overflow-tooltip width="130" label="场景名称" />
                    <el-table-column prop="sceneType" show-overflow-tooltip width="120" label="场景类型">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.sceneType, dictObj.sceneType) }}
                        </template>
                    </el-table-column>
                    <el-table-column show-overflow-tooltip width="150" 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 label="操作" class-name="operation-btns">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('view', row)">查看</el-link>
                            <el-link @click="openForm('edit', row)">编辑</el-link>
                            <el-link @click="handleDelete(row)">删除</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" />
@@ -80,6 +76,7 @@
</template>
<script setup>
import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
import { nextTick, onMounted, provide, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fwDefenseScenePageApi, fwDefenseSceneRemoveApi } from './sceneConfigApi'
@@ -110,7 +107,7 @@
])
// 获取列表
async function getList() {
async function getList () {
    loading.value = true
    try {
        const res = await fwDefenseScenePageApi(searchParams.value)
@@ -122,20 +119,20 @@
}
// 查询
function handleSearch() {
function handleSearch () {
    searchParams.value.current = 1
    getList()
}
// 重置查询
function resetForm() {
function resetForm () {
    queryParamsRef.value?.resetFields()
    searchParams.value.current = 1
    getList()
}
// 删除
async function handleDelete(row) {
async function handleDelete (row) {
    const tips = row ? '该条' : '选中的项'
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' })
    const ids = row ? row.id : selectedIds.value.join(',')
@@ -146,11 +143,11 @@
}
// 勾选值设置
function handleSelectionChange(rows) {
function handleSelectionChange (rows) {
    selectedIds.value = rows.map(item => item.id)
}
function formatLocation(row) {
function formatLocation (row) {
    if (row?.longitude == null || row?.latitude == null) return ''
    return `${_.round(row.longitude, 6)}, ${_.round(row.latitude, 6)}`
}
@@ -163,14 +160,14 @@
provide('dictObj', dictObj)
// 获取字典
function getDictList() {
function getDictList () {
    getDictionaryByCode('sceneType,deviceMode,areaType').then(res => {
        dictObj.value = res.data.data
    })
}
// 新增/编辑/查看 弹框
function openForm(mode, row) {
function openForm (mode, row) {
    dialogVisible.value = true
    nextTick(() => {
        dialogRef.value?.open({ mode, row })
applications/drone-command/src/views/basicManage/deviceScrap/index.vue
@@ -1,82 +1,72 @@
<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="belongDept">
                        <el-tree-select
                            v-model="searchParams.belongDept"
                            :data="deptTree"
                            :props="treeProps"
                            node-key="id"
                            check-strictly
                            clearable
                            @change="handleSearch"
                        />
                    </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="belongDept">
                <el-tree-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-tree-select-popper"
                    v-model="searchParams.belongDept" :data="deptTree" :props="treeProps" node-key="id" check-strictly
                    clearable @change="handleSearch" />
            </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" @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55" />
            <el-table-column type="index" width="60" label="序号" />
            <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-column prop="deviceModel" label="型号" />
            <el-table-column prop="manufacturer" label="生产厂商" />
            <el-table-column prop="createTime" label="入库时间" />
            <el-table-column prop="source" label="来源" />
            <el-table-column prop="charger" label="负责人" />
            <el-table-column prop="scrapTime" label="报废时间" />
            <el-table-column prop="scrapReason" label="报废原因" />
            <el-table-column prop="disposeWay" label="处置方式" />
            <el-table-column prop="maintainReminder" label="维护提醒" />
            <el-table-column label="操作">
                <template v-slot="{ row }">
                    <el-link @click="handleView(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" @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="设备类型">
                        <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 label="操作" class-name="operation-btns">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">查看</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 ref="dialogRef" @success="getList" />
        <MaintenanceDiaLog ref="maintenanceDialogRef" @success="getList" />
    </basic-container>
</template>
<script setup>
import { Search, RefreshRight } from '@element-plus/icons-vue'
import { onMounted, ref } from 'vue'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getDeptTree } from '@/api/system/dept'
@@ -101,11 +91,11 @@
const dialogRef = ref(null) // 弹框实例
const maintenanceDialogRef = ref(null) // 弹框实例
const dictObj = ref({
    deviceType: [], //设备类型
    deviceAtt: [], //设备属性
    deviceType: [], // 设备类型
    deviceAtt: [], // 设备属性
})
provide('dictObj', dictObj)
const deptTree = ref([]) //部门树
const deptTree = ref([]) // 部门树
const treeProps = {
    label: 'name',
    children: 'children',
@@ -118,7 +108,7 @@
provide('planCycleOptions', planCycleOptions)
// 获取列表
async function getList() {
async function getList () {
    loading.value = true
    try {
        const res = await fwDeviceScrapPageApi(searchParams.value)
@@ -130,48 +120,48 @@
}
// 查询
function handleSearch() {
function handleSearch () {
    searchParams.value.current = 1
    getList()
}
// 重置查询
function resetForm() {
function resetForm () {
    queryParamsRef.value?.resetFields()
    searchParams.value.current = 1
    getList()
}
// 查看
function handleView(row) {
function handleView (row) {
    dialogRef.value?.open({ mode: 'view', row: { ...row } })
}
// 勾选值设置
function handleSelectionChange(rows) {
function handleSelectionChange (rows) {
    selectedIds.value = rows.map(item => item.id)
}
// 获取字典
function getDictList() {
function getDictList () {
    getDictionaryByCode('deviceType,deviceAtt').then(res => {
        dictObj.value = res.data.data
    })
}
// 获取部门树
function getDeptTreeFun() {
function getDeptTreeFun () {
    getDeptTree().then(res => {
        deptTree.value = res.data.data
    })
}
// 新增
function handleAdd() {
function handleAdd () {
    dialogRef.value?.open({ mode: 'add' })
}
function getPlanCycleLabel(row) {
function getPlanCycleLabel (row) {
    const item = planCycleOptions.find(option => option.value === row.planCycleType)
    return item?.label + '-' + row.planCycleValue.join(',')
}
applications/drone-command/src/views/basicManage/deviceStock/index.vue
@@ -1,104 +1,89 @@
<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="belongDept">
                        <el-tree-select
                            v-model="searchParams.belongDept"
                            :data="deptTree"
                            :props="treeProps"
                            node-key="id"
                            check-strictly
                            clearable
                            @change="handleSearch"
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="4">
                    <el-form-item label="时间">
                        <el-date-picker
                            popper-class="ztzf-date-picker-popper"
                            class="ztzf-date-picker"
                            v-model="dateRange"
                            type="daterange"
                            range-separator="至"
                            start-placeholder="开始日期"
                            end-placeholder="结束日期"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            @change="handleSearch"
                        />
                    </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="belongDept">
                <el-tree-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-tree-select-popper"
                    v-model="searchParams.belongDept" :data="deptTree" :props="treeProps" node-key="id" check-strictly
                    clearable @change="handleSearch" />
            </el-form-item>
            <el-form-item label="时间">
                <el-date-picker popper-class="ztzf-data-cockpit-date-picker-popper"
                    class="ztzf-data-cockpit-date-picker" v-model="dateRange" type="daterange" range-separator="至"
                    start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD HH:mm:ss"
                    @change="handleSearch" />
            </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>
        <div>
            <el-button type="primary" @click="handleAdd">新增</el-button>
            <el-button type="primary" @click="exportFile" :loading="exportLoading">导出</el-button>
        <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 type="danger" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>-->
        </div>
        <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55" />
            <el-table-column type="index" width="60" label="序号" />
            <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-column prop="deviceModel" label="型号" />
            <el-table-column prop="deviceSpecification" label="规格" />
            <el-table-column prop="manufacturer" label="生产厂商" />
            <el-table-column prop="createTime" label="入库时间" />
            <el-table-column prop="source" label="来源" />
            <el-table-column prop="purpose" label="用途" />
            <el-table-column prop="belongDeptName" label="所属部门" />
            <el-table-column prop="charger" label="负责人" />
            <el-table-column prop="yxzt" label="运行状态">
                <template v-slot="{ row }">
                    {{ DEVICE_STATUS[row.status] || '-' }}
                </template>
            </el-table-column>
            <el-table-column label="操作">
                <template v-slot="{ row }">
                    <el-link @click="handleView(row)" type="primary">查看</el-link>
                    <el-link @click="handleEdit(row)" type="warning">编辑</el-link>
                    <el-link @click="outbound(row)" type="warning">出库</el-link>
                    <el-link @click="scrap(row)" type="warning" v-if="row.status !== 3">报废</el-link>
                    <!--                    <el-link @click="handleDelete(row)" type="danger">删除</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" @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="类型">
                        <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="deviceSpecification" show-overflow-tooltip width="112" label="规格" />
                    <el-table-column prop="manufacturer" show-overflow-tooltip width="130" label="生产厂商" />
                    <el-table-column prop="createTime" show-overflow-tooltip width="96" label="入库时间">
                        <template v-slot="{ row }">
                            {{ formatDateToSlash(row.createTime) }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="source" show-overflow-tooltip width="112" label="来源" />
                    <el-table-column prop="purpose" show-overflow-tooltip width="206" label="用途" />
                    <el-table-column prop="belongDeptName" show-overflow-tooltip width="124" label="所属部门" />
                    <el-table-column prop="charger" show-overflow-tooltip width="100" label="负责人" />
                    <el-table-column prop="yxzt" show-overflow-tooltip width="116" label="运行状态">
                        <template v-slot="{ row }">
                            {{ DEVICE_STATUS[row.status] || '-' }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">查看</el-link>
                            <el-link @click="handleEdit(row)">编辑</el-link>
                            <el-link @click="outbound(row)">出库</el-link>
                            <el-link @click="scrap(row)" v-if="row.status !== 3">报废</el-link>
                            <!--                    <el-link @click="handleDelete(row)" type="danger">删除</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 ref="dialogRef" @success="getList" />
@@ -108,13 +93,14 @@
</template>
<script setup>
import { Search, RefreshRight, Plus, Download } from '@element-plus/icons-vue'
import { onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { exportFwDeviceApi, fwDevicePageApi, fwDeviceRemoveApi } from '@/views/basicManage/deviceStock/fwDevice'
import FormDiaLog from './FormDiaLog.vue'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getDeptTree } from '@/api/system/dept'
import { blobDownload, dateRangeFormat, getDictLabel } from '@ztzf/utils'
import { blobDownload, dateRangeFormat, getDictLabel, formatDateToSlash } from '@ztzf/utils'
import { DEVICE_STATUS } from '@ztzf/constants'
import DeviceTrackDiaLog from '@/views/basicManage/deviceStock/DeviceTrackDiaLog.vue'
import DeviceScrapDiaLog from '@/views/basicManage/deviceStock/DeviceScrapDiaLog.vue'
@@ -150,7 +136,7 @@
provide('deptTree', deptTree)
// 获取列表
async function getList() {
async function getList () {
    const range = dateRangeFormat(dateRange.value)
    loading.value = true
    try {
@@ -164,13 +150,13 @@
}
// 查询
function handleSearch() {
function handleSearch () {
    searchParams.value.current = 1
    getList()
}
// 重置查询
function resetForm() {
function resetForm () {
    dateRange.value = []
    queryParamsRef.value?.resetFields()
    searchParams.value.current = 1
@@ -178,25 +164,25 @@
}
// 查看
function handleView(row) {
function handleView (row) {
    dialogRef.value?.open({ mode: 'view', row: { ...row } })
}
// 编辑
function handleEdit(row) {
function handleEdit (row) {
    dialogRef.value?.open({ mode: 'edit', row: { ...row } })
}
function outbound(row) {
function outbound (row) {
    outboundDialogRef.value?.open({ mode: 'edit', row: { ...row } })
}
function scrap(row) {
function scrap (row) {
    scrapDialogRef.value?.open({ mode: 'edit', row: { ...row } })
}
// 删除
async function handleDelete(row) {
async function handleDelete (row) {
    const tips = row ? '该条' : '选中的项'
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' })
    const ids = row ? row.id : selectedIds.value.join(',')
@@ -207,11 +193,11 @@
}
// 勾选值设置
function handleSelectionChange(rows) {
function handleSelectionChange (rows) {
    selectedIds.value = rows.map(item => item.id)
}
function exportFile() {
function exportFile () {
    exportLoading.value = true
    exportFwDeviceApi()
        .then(res => {
@@ -223,21 +209,21 @@
}
// 获取字典
function getDictList() {
function getDictList () {
    getDictionaryByCode('deviceType,deviceAtt').then(res => {
        dictObj.value = res.data.data
    })
}
// 获取部门树
function getDeptTreeFun() {
function getDeptTreeFun () {
    getDeptTree().then(res => {
        deptTree.value = res.data.data
    })
}
// 新增
function handleAdd() {
function handleAdd () {
    dialogRef.value?.open({ mode: 'add' })
}
applications/drone-command/src/views/basicManage/maintainRecord/index.vue
@@ -1,88 +1,79 @@
<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="belongDept">
                        <el-tree-select
                            v-model="searchParams.belongDept"
                            :data="deptTree"
                            :props="treeProps"
                            node-key="id"
                            check-strictly
                            clearable
                            @change="handleSearch"
                        />
                    </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="belongDept">
                <el-tree-select class="ztzf-data-cockpit-select" popper-class="ztzf-data-cockpit-tree-select-popper"
                    v-model="searchParams.belongDept" :data="deptTree" :props="treeProps" node-key="id" check-strictly
                    clearable @change="handleSearch" />
            </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>
        <div>
            <el-button type="primary" @click="handleAdd">维护计划</el-button>
        <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>
        </div>
        <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55" />
            <el-table-column type="index" width="60" label="序号" />
            <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-column prop="deviceModel" label="型号" />
            <el-table-column prop="manufacturer" label="生产厂商" />
            <el-table-column prop="charger" label="负责人" />
            <el-table-column prop="maintainReminder" label="维护提醒" />
            <el-table-column prop="planCycleType" label="维护计划">
                <template v-slot="{ row }">
                    {{ getPlanCycleLabel(row) }}
                </template>
            </el-table-column>
            <el-table-column label="操作">
                <template v-slot="{ row }">
                    <el-link @click="handleView(row)" type="primary">查看</el-link>
                    <el-link @click="maintenance(row)" type="warning">维护</el-link>
                    <el-link @click="handleDelete(row)" type="danger">删除</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" @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="设备类型">
                        <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="维护计划">
                        <template v-slot="{ row }">
                            {{ getPlanCycleLabel(row) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">查看</el-link>
                            <el-link @click="maintenance(row)">维护</el-link>
                            <el-link @click="handleDelete(row)">删除</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 ref="dialogRef" @success="getList" />
        <MaintenanceDiaLog ref="maintenanceDialogRef" @success="getList" />
    </basic-container>
</template>
<script setup>
import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
import { onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getDictionaryByCode } from '@/api/system/dictbiz'
@@ -111,11 +102,11 @@
const dialogRef = ref(null) // 弹框实例
const maintenanceDialogRef = ref(null) // 弹框实例
const dictObj = ref({
    deviceType: [], //设备类型
    deviceAtt: [], //设备属性
    deviceType: [], // 设备类型
    deviceAtt: [], // 设备属性
})
provide('dictObj', dictObj)
const deptTree = ref([]) //部门树
const deptTree = ref([]) // 部门树
const treeProps = {
    label: 'name',
    children: 'children',
@@ -128,7 +119,7 @@
provide('planCycleOptions', planCycleOptions)
// 获取列表
async function getList() {
async function getList () {
    loading.value = true
    try {
        const res = await fwDeviceMaintainPlanPageApi(searchParams.value)
@@ -140,30 +131,30 @@
}
// 查询
function handleSearch() {
function handleSearch () {
    searchParams.value.current = 1
    getList()
}
// 重置查询
function resetForm() {
function resetForm () {
    queryParamsRef.value?.resetFields()
    searchParams.value.current = 1
    getList()
}
// 查看
function handleView(row) {
function handleView (row) {
    dialogRef.value?.open({ mode: 'view', row: { ...row } })
}
// 维护
function maintenance(row) {
function maintenance (row) {
    maintenanceDialogRef.value?.open({ mode: 'edit', row: { ...row } })
}
// 删除
async function handleDelete(row) {
async function handleDelete (row) {
    const tips = row ? '该条' : '选中的项'
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' })
    const ids = row ? row.id : selectedIds.value.join(',')
@@ -174,30 +165,30 @@
}
// 勾选值设置
function handleSelectionChange(rows) {
function handleSelectionChange (rows) {
    selectedIds.value = rows.map(item => item.id)
}
// 获取字典
function getDictList() {
function getDictList () {
    getDictionaryByCode('deviceType,deviceAtt').then(res => {
        dictObj.value = res.data.data
    })
}
// 获取部门树
function getDeptTreeFun() {
function getDeptTreeFun () {
    getDeptTree().then(res => {
        deptTree.value = res.data.data
    })
}
// 新增
function handleAdd() {
function handleAdd () {
    dialogRef.value?.open({ mode: 'add' })
}
function getPlanCycleLabel(row) {
function getPlanCycleLabel (row) {
    const item = planCycleOptions.find(option => option.value === row.planCycleType)
    return item?.label + '-' + row.planCycleValue.join(',')
}
applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue
@@ -1,7 +1,7 @@
<template>
    <el-dialog class="ztzf-data-cockpit-dialog" append-to-body v-model="visibleModel" :title="dialogTitle"
        :close-on-click-modal="false" :destroy-on-close="true">
        <el-form ref="queryParamsRef" :model="searchParams" class="history-search">
        <el-form ref="queryParamsRef" :model="searchParams" class="ztzf-dialog-history-search">
            <el-row :gutter="12">
                <el-col :span="4">
                    <el-form-item label="关键字" prop="keyword">
@@ -19,8 +19,9 @@
                </el-col>
                <el-col :span="4">
                    <el-form-item label="区域" prop="area">
                        <el-tree-select  class="ztzf-data-cockpit-select" popper-class="ztzf-tree-cockpit-select-popper" v-model="searchParams.area" :data="areaTree" :props="areaTreeProps"
                            node-key="value" check-strictly clearable placeholder="请选择" @change="handleSearch" />
                        <el-tree-select class="ztzf-data-cockpit-select" popper-class="ztzf-tree-cockpit-select-popper"
                            v-model="searchParams.area" :data="areaTree" :props="areaTreeProps" node-key="value"
                            check-strictly clearable placeholder="请选择" @change="handleSearch" />
                    </el-form-item>
                </el-col>
                <el-col :span="5">
@@ -32,17 +33,16 @@
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="6" class="history-search-actions">
                    <el-form-item>
                        <el-button :icon="RefreshRight" @click="resetForm"></el-button>
                        <el-button :icon="Search" @click="handleSearch"></el-button>
                    </el-form-item>
                </el-col>
                <el-form-item class="history-search-actions">
                    <el-button :icon="RefreshRight" @click="resetForm"></el-button>
                    <el-button :icon="Search" @click="handleSearch"></el-button>
                </el-form-item>
            </el-row>
        </el-form>
        <div class="table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
            <div class="table-content">
        <div class="ztzf-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
            <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 prop="droneName" label="无人机名称" min-width="120" />
@@ -68,11 +68,11 @@
                    </el-table-column>
                </el-table>
            </div>
            <div class="table-pagination">
                <el-pagination popper-class="ztzf-data-cockpit-select-popper" v-model:current-page="searchParams.current" v-model:page-size="searchParams.size"
                    layout="total, prev, pager, next, sizes"
                    :total="total" @change="getList" />
            <div 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>
    </el-dialog>
applications/drone-command/src/views/dataCockpit/index.vue
@@ -10,7 +10,11 @@
-->
<template>
  <div class="page-container">
    <MapContainer :online-devices="onlineDevices" :left-collapsed="leftCollapsed" />
    <DeviceMapContainer
      :online-devices="onlineDevices"
      :left-collapsed="leftCollapsed"
      container-id="data-cockpit-map"
    />
    <LeftContainer class="left-container" v-model:activeId="leftActiveId" v-model:collapsed="leftCollapsed" />  
    <RightContainer class="right-container" v-model:collapsed="rightCollapsed" v-model:onlineDevices="onlineDevices" />  
    <CenterContainer @select-warning="onSelectWarning" @select-equipment="onSelectEquipment" />  
@@ -18,7 +22,7 @@
</template>
<script setup>
  import MapContainer from './components/MapContainer.vue';
  import DeviceMapContainer from '@/components/map-container/device-map-container.vue';
  import LeftContainer from './components/LeftContainer.vue';
  import RightContainer from './components/RightContainer.vue';
  import CenterContainer from './components/CenterContainer.vue';
applications/drone-command/src/views/detectionCountermeasure/components/DetectionRangeDialog.vue
New file
@@ -0,0 +1,348 @@
<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>
            </div>
            <div class="dialog-form">
                <div class="form-title">{{ formTitle }}</div>
                <el-form
                    ref="formRef"
                    :model="formData"
                    :rules="rules"
                    label-width="84px"
                    :disabled="dialogReadonly"
                >
                    <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>
                    </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>
                    </el-form-item>
                    <el-form-item label="设备位置" prop="deployLocation">
                        <el-input v-model="formData.deployLocation" placeholder="请选择位置">
                            <template #suffix>
                                <span class="suffix-action">地图选点</span>
                            </template>
                        </el-input>
                    </el-form-item>
                    <el-form-item label="设备编号" prop="deviceCode">
                        <el-input v-model="formData.deviceCode" placeholder="请输入" clearable />
                    </el-form-item>
                    <el-form-item label="设备型号" prop="deviceModel">
                        <el-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-form-item>
                </el-form>
            </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 { ElMessage } from 'element-plus'
import DeviceMapContainer from '@/components/map-container/device-map-container.vue'
import {
    detectionRangeDetailApi,
    detectionRangePageApi,
    detectionRangeSubmitApi,
} from '@/api/detectionCountermeasure/detectionRange'
const initForm = () => ({
    deviceId: '', // 侦测设备ID
    deviceName: '', // 侦测设备
    deviceType: '', // 设备类型
    deployLocation: '', // 设备位置
    deviceCode: '', // 设备编号
    deviceModel: '', // 设备型号
    effectiveRangeKm: null, // 覆盖范围
})
const dictObj = inject('dictObj')
const emit = defineEmits(['success'])
const formRef = ref(null) // 表单实例
const formData = ref(initForm()) // 表单数据
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 '新增'
})
const formTitle = computed(() => {
    if (dialogMode.value === 'edit') {
        return '编辑侦测范围'
    }
    if (dialogMode.value === 'view') {
        return '查看侦测范围'
    }
    return '新增侦测范围'
})
const rules = {
    deviceId: [{ required: true, message: '请选择侦测设备', trigger: ['blur', 'change'] }],
    deviceType: [{ required: true, message: '请选择设备类型', trigger: ['blur', 'change'] }],
    effectiveRangeKm: [{ required: true, message: '请输入覆盖范围', trigger: ['blur', 'change'] }],
}
function handleDeviceChange(value) {
    const target = deviceOptions.value.find(item => item.value === value)
    const raw = target?.raw
    if (!raw) return
    formData.value.deviceId = raw.id ?? formData.value.deviceId
    formData.value.deviceName = raw.deviceName ?? formData.value.deviceName
    formData.value.deviceType = raw.deviceType ?? formData.value.deviceType
    formData.value.deviceModel = raw.deviceModel ?? formData.value.deviceModel
    formData.value.deviceCode =
        raw.deviceCode ?? raw.deviceSn ?? raw.deviceNo ?? formData.value.deviceCode
    formData.value.deployLocation =
        raw.deployLocation ??
        raw.address ??
        raw.location ??
        raw.deployAddress ??
        formData.value.deployLocation
}
// 关闭弹框
function handleCancel() {
    visible.value = false
}
// 提交新增/编辑
async function handleSubmit() {
    const isValid = await formRef.value?.validate().catch(() => false)
    if (!isValid) return
    submitting.value = true
    try {
        const payload = {
            ...formData.value,
            id: formData.value.id || formData.value.deviceId || undefined,
            effectiveRangeKmIsNotNull: formData.value.effectiveRangeKm ? 1 : 2,
        }
        delete payload.deviceId
        await detectionRangeSubmitApi(payload)
        ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
        visible.value = false
        emit('success')
    } finally {
        submitting.value = false
    }
}
// 加载详情
async function loadDetail(id) {
    if (!id) return null
    const res = await detectionRangeDetailApi({ id })
    return res?.data?.data ?? null
}
async function loadDeviceOptions() {
    const res = await detectionRangePageApi({
        current: 1,
        size: 200,
        effectiveRangeKmIsNotNull: 2,
    })
    const records = res?.data?.data?.records ?? []
    deviceOptions.value = records.map(item => ({
        label: item.deviceName ?? '-',
        value: item.id ?? '',
        raw: item,
    }))
}
// 关闭后重置
function handleClosed() {
    formData.value = initForm()
}
// 打开弹框
async function open({ mode, row } = {}) {
    dialogMode.value = mode || 'add'
    visible.value = true
    if (dialogMode.value === 'add') {
        await loadDeviceOptions()
        formData.value = initForm()
        return
    }
    formData.value = row
    if (dialogMode.value !== 'add') {
        const detail = await loadDetail(row?.id)
        formData.value = detail ?? row
    }
    formData.value.deviceId = formData.value.id ?? formData.value.deviceId
    await loadDeviceOptions()
    const currentName = formData.value.deviceName
    const currentId = formData.value.deviceId
    if (currentId && !deviceOptions.value.some(item => item.value === currentId)) {
        deviceOptions.value.unshift({ label: currentName || '-', value: currentId })
    }
}
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>
applications/drone-command/src/views/detectionCountermeasure/detectionRange.vue
@@ -1,9 +1,191 @@
<template>
  <basic-container>
    侦测范围管理
  </basic-container>
    <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>
        <div>
            <el-button type="primary" @click="handleAdd">新增</el-button>
        </div>
        <el-table v-loading="loading" :data="list">
            <el-table-column type="index" width="60" label="序号" />
            <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-column label="部署位置">
                <template v-slot="{ row }">
                    {{ formatDeployLocation(row) }}
                </template>
            </el-table-column>
            <el-table-column prop="deviceModel" label="型号" />
            <el-table-column label="覆盖范围">
                <template v-slot="{ row }">
                    {{ formatRange(row) }}
                </template>
            </el-table-column>
            <el-table-column label="设备编码">
                <template v-slot="{ row }">
                    {{ formatDeviceCode(row) }}
                </template>
            </el-table-column>
            <el-table-column label="操作">
                <template v-slot="{ row }">
                    <el-link @click="handleView(row)" type="primary">查看</el-link>
                    <el-link @click="handleEdit(row)" type="warning">编辑</el-link>
                    <el-link @click="handleDelete(row)" type="danger">删除</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>
        <DetectionRangeDialog ref="dialogRef" @success="getList" />
    </basic-container>
</template>
<script setup>
import { onMounted, ref, provide } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getDictLabel } from '@ztzf/utils'
import { detectionRangePageApi, detectionRangeSubmitApi } from '@/api/detectionCountermeasure/detectionRange'
import DetectionRangeDialog from './components/DetectionRangeDialog.vue'
const initSearchParams = () => ({
    deviceName: '', // 设备名称
    deviceType: '', // 设备类型
    effectiveRangeKmIsNotNull: 1, // 1-不为空
    current: 1, // 当前页
    size: 10, // 每页大小
})
const searchParams = ref(initSearchParams()) // 查询参数
const loading = ref(false) // 列表加载中
const list = ref([]) // 列表数据
const total = ref(0) // 总数
const queryParamsRef = ref(null) // 查询表单实例
const dialogRef = ref(null) // 弹框实例
const dictObj = ref({
    deviceType: [], // 设备类型
})
provide('dictObj', dictObj)
// 获取列表
async function getList() {
    loading.value = true
    try {
        const res = await detectionRangePageApi(searchParams.value)
        list.value = res?.data?.data?.records ?? []
        total.value = res?.data?.data?.total ?? 0
    } finally {
        loading.value = false
    }
}
// 查询
function handleSearch() {
    searchParams.value.current = 1
    getList()
}
// 重置查询
function resetForm() {
    queryParamsRef.value?.resetFields()
    searchParams.value.current = 1
    getList()
}
// 新增
function handleAdd() {
    dialogRef.value?.open({ mode: 'add' })
}
// 查看
function handleView(row) {
    dialogRef.value?.open({ mode: 'view', row: { ...row } })
}
// 编辑
function handleEdit(row) {
    dialogRef.value?.open({ mode: 'edit', row: { ...row } })
}
// 删除
async function handleDelete(row) {
    await ElMessageBox.confirm('确认删除该条数据吗?', '提示', { type: 'warning' })
    await detectionRangeSubmitApi({ id: row.id, effectiveRangeKm: 0 })
    ElMessage.success('删除成功')
    getList()
}
// 获取字典
function getDictList() {
    getDictionaryByCode('deviceType').then(res => {
        dictObj.value = res.data.data
    })
}
function formatDeployLocation(row) {
    if (!row) return '-'
    const location = row.deployLocation ?? row.address ?? row.location ?? row.deployAddress
    if (Array.isArray(location) && location.length) {
        return location.filter(value => value !== null && value !== undefined).join(',')
    }
    if (typeof location === 'string' && location.trim()) {
        return location
    }
    const longitude = row.deployLongitude ?? row.longitude
    const latitude = row.deployLatitude ?? row.latitude
    if (longitude != null || latitude != null) {
        return `${longitude ?? '-'},${latitude ?? '-'}`
    }
    return '-'
}
function formatRange(row) {
    if (!row) return '-'
    const value = row.effectiveRangeKm ?? row.range
    return value === null || value === undefined || value === '' ? '-' : value
}
function formatDeviceCode(row) {
    if (!row) return '-'
    return row.deviceCode || row.deviceSn || row.deviceNo || '-'
}
onMounted(() => {
    getList()
    getDictList()
})
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>
packages/utils/date/index.js
@@ -6,3 +6,18 @@
    let end_date = dateRange?.length ? dayjs(dateRange?.[1]).endOf('day').format(timeFormat) : null
    return [start_date, end_date]
}
export function formatDateToSlash(dateStr) {
    if (!dateStr) return '-'
    // 兼容 'YYYY-MM-DD HH:mm:ss'
    const date = new Date(dateStr.replace(/-/g, '/'))
    if (isNaN(date.getTime())) return '-'
    const y = date.getFullYear()
    const m = String(date.getMonth() + 1).padStart(2, '0')
    const d = String(date.getDate()).padStart(2, '0')
    return `${y}/${m}/${d}`
  }