吉安感知网项目-前端
shuishen
2026-01-08 bc4ea36aed76bd5e863e703d57394ae589575771
feat:数据驾驶舱页面基本处理
5 files modified
3 files added
460 ■■■■ changed files
applications/drone-command/src/assets/images/dataCockpit/left-btn.png patch | view | raw | blame | history
applications/drone-command/src/assets/images/dataCockpit/right-btn.png patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/CenterContainer.vue 6 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue 245 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue 98 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/RightContainer.vue 67 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue 24 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/index.vue 20 ●●●● patch | view | raw | blame | history
applications/drone-command/src/assets/images/dataCockpit/left-btn.png
applications/drone-command/src/assets/images/dataCockpit/right-btn.png
applications/drone-command/src/views/dataCockpit/components/CenterContainer.vue
@@ -81,13 +81,15 @@
    }
])
const emit = defineEmits(['select-warning', 'select-equipment'])
const warningTypeClick = (type) => {
    console.log('点击了告警类型:', type)
    emit('select-warning', type)
}
const equipmentClick = (isHighlight) => {
    if (isHighlight) {
        console.log('点击了设备统计')
        emit('select-equipment')
    }
}
</script>
applications/drone-command/src/views/dataCockpit/components/DeviceHistoryDialog.vue
New file
@@ -0,0 +1,245 @@
<template>
    <el-dialog
        class="ztzf-dialog-mange data-cockpit-history-dialog"
        append-to-body
        v-model="visibleModel"
        :title="dialogTitle"
        width="1100px"
        :close-on-click-modal="false"
        :destroy-on-close="true"
    >
        <div class="dialog-body">
            <div class="device-summary">
                <div class="item">
                    <span class="label">设备名称</span>
                    <span class="value">{{ device?.name || '反制设备名称' }}</span>
                </div>
                <div class="item">
                    <span class="label">类型</span>
                    <span class="value">{{ device?.type || '-' }}</span>
                </div>
                <div class="item">
                    <span class="label">厂商</span>
                    <span class="value">{{ device?.vendor || '-' }}</span>
                </div>
                <div class="item">
                    <span class="label">状态</span>
                    <span class="value">{{ device?.statusText || '-' }}</span>
                </div>
            </div>
            <div class="table">
                <div class="thead">
                    <div class="cell index">序号</div>
                    <div class="cell">无人机名称</div>
                    <div class="cell">无人机序列号</div>
                    <div class="cell">无人机类型</div>
                    <div class="cell">区域</div>
                    <div class="cell">数据来源</div>
                    <div class="cell">信号频段</div>
                    <div class="cell">发现时间</div>
                    <div class="cell">停留时间</div>
                    <div class="cell">反制方式</div>
                </div>
                <div class="tbody">
                    <div class="tr" v-for="(row, index) in historyList" :key="row.id">
                        <div class="cell index">{{ index + 1 }}</div>
                        <div class="cell">{{ row.droneName }}</div>
                        <div class="cell">{{ row.serialNumber }}</div>
                        <div class="cell">{{ row.droneType }}</div>
                        <div class="cell">{{ row.area }}</div>
                        <div class="cell">{{ row.dataSource }}</div>
                        <div class="cell">{{ row.frequency }}</div>
                        <div class="cell">{{ row.discoveredAt }}</div>
                        <div class="cell">{{ row.stayTime }}</div>
                        <div class="cell">{{ row.counterMethod }}</div>
                    </div>
                </div>
            </div>
        </div>
    </el-dialog>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
    modelValue: {
        type: Boolean,
        default: false
    },
    device: {
        type: Object,
        default: () => null
    }
})
const emit = defineEmits(['update:modelValue'])
const visibleModel = computed({
    get: () => props.modelValue,
    set: (val) => emit('update:modelValue', val)
})
const dialogTitle = computed(() => {
    return `反制设备名称 ${props.device?.name || ''}`.trim()
})
const historyList = computed(() => {
    const dataSource = props.device?.name || '侦测反制设备名称'
    return [
        {
            id: 'his_001',
            droneName: '无人机名称',
            serialNumber: 'XLH789456',
            droneType: '微型机',
            area: '古村区',
            dataSource,
            frequency: '5800MHZ',
            discoveredAt: '2025/12/26 12:15:26',
            stayTime: '00:10:50',
            counterMethod: '信号干扰'
        },
        {
            id: 'his_002',
            droneName: '无人机名称',
            serialNumber: 'XLH789456',
            droneType: '微型机',
            area: '古村区',
            dataSource,
            frequency: '5800MHZ',
            discoveredAt: '2025/12/26 12:18:06',
            stayTime: '00:08:12',
            counterMethod: '信号干扰'
        },
        {
            id: 'his_003',
            droneName: '无人机名称',
            serialNumber: 'XLH789456',
            droneType: '微型机',
            area: '古村区',
            dataSource,
            frequency: '2400MHZ',
            discoveredAt: '2025/12/26 12:22:31',
            stayTime: '00:06:09',
            counterMethod: '诱导驱离'
        },
        {
            id: 'his_004',
            droneName: '无人机名称',
            serialNumber: 'XLH789456',
            droneType: '微型机',
            area: '古村区',
            dataSource,
            frequency: '2400MHZ',
            discoveredAt: '2025/12/26 12:28:44',
            stayTime: '00:12:03',
            counterMethod: '诱导驱离'
        },
        {
            id: 'his_005',
            droneName: '无人机名称',
            serialNumber: 'XLH789456',
            droneType: '微型机',
            area: '古村区',
            dataSource,
            frequency: '5800MHZ',
            discoveredAt: '2025/12/26 12:32:10',
            stayTime: '00:04:39',
            counterMethod: '信号干扰'
        },
        {
            id: 'his_006',
            droneName: '无人机名称',
            serialNumber: 'XLH789456',
            droneType: '微型机',
            area: '古村区',
            dataSource,
            frequency: '5800MHZ',
            discoveredAt: '2025/12/26 12:36:42',
            stayTime: '00:07:21',
            counterMethod: '信号干扰'
        }
    ]
})
</script>
<style lang="scss" scoped>
.dialog-body {
    display: flex;
    flex-direction: column;
    gap: 16px;
    color: #C3C3DD;
}
.device-summary {
    display: flex;
    flex-wrap: wrap;
    gap: 16px 24px;
    .item {
        display: flex;
        align-items: center;
        gap: 8px;
        font-size: 14px;
        .label {
            color: #8B8BA7;
        }
        .value {
            color: #FFFFFF;
        }
    }
}
.table {
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 6px;
    overflow: hidden;
}
.thead,
.tr {
    display: grid;
    grid-template-columns: 60px 1.1fr 1.1fr 0.9fr 0.8fr 1.2fr 0.9fr 1.3fr 0.9fr 0.9fr;
    align-items: center;
}
.thead {
    background: rgba(20, 24, 48, 0.9);
    color: #FFFFFF;
    font-size: 13px;
    height: 42px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.tbody {
    max-height: 360px;
    overflow-y: auto;
    background: rgba(10, 14, 34, 0.6);
}
.tr {
    font-size: 12px;
    color: #C3C3DD;
    height: 40px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    &:nth-child(2n) {
        background: rgba(255, 255, 255, 0.02);
    }
}
.cell {
    padding: 0 10px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    &.index {
        text-align: center;
        color: #86909C;
    }
}
</style>
applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue
@@ -1,18 +1,23 @@
<template>
    <div class="left-container">
    <div class="left-container" :class="{ collapsed: collapsedModel }">
        <div class="wrapper">
            <TitleTemplate>异常情况告警</TitleTemplate>
            <div class="category-container">
                <div class="category-item" v-for="(item, ind) in categoryList" :key="ind" @click="activeId = item.id"
                    :class="{ active: activeId === item.id }">
                <div class="category-item" v-for="(item, ind) in categoryList" :key="ind"
                    @click="activeIdModel = item.id" :class="{ active: activeIdModel === item.id }">
                    {{ item.name }}
                    <span class="badge">{{ item.badge }}</span>
                </div>
            </div>
            <div class="content">
                <component :is="currentComponent" />
            </div>
        </div>
        <div class="collapse-btn" @click="toggleCollapse">
            <span class="arrow" :class="collapsedModel ? 'right' : 'left'"></span>
        </div>
    </div>
</template>
@@ -23,32 +28,97 @@
import EquipmentWarning from './EquipmentWarning.vue'
import HistoryWarning from './HistoryWarning.vue'
const activeId = ref(1)
const props = defineProps({
    activeId: {
        type: Number,
        default: 1
    },
    collapsed: {
        type: Boolean,
        default: false
    }
})
const emit = defineEmits(['update:activeId', 'update:collapsed'])
const activeIdModel = computed({
    get: () => props.activeId,
    set: (val) => emit('update:activeId', val)
})
const collapsedModel = computed({
    get: () => props.collapsed,
    set: (val) => emit('update:collapsed', val)
})
const categoryList = ref([
    {
        id: 1,
        name: '实时告警',
        badge: 8,
        component: RealWarning
    },
    {
        id: 2,
        name: '设备告警',
        badge: 8,
        component: EquipmentWarning
    },
    {
        id: 3,
        name: '历史告警',
        badge: 8,
        component: HistoryWarning
    }
])
const currentComponent = computed(() => {
    return categoryList.value.find(i => i.id === activeId.value)?.component
    return categoryList.value.find(i => i.id === activeIdModel.value)?.component
})
const toggleCollapse = () => {
    collapsedModel.value = !collapsedModel.value
}
</script>
<style lang="scss" scoped>
.left-container {
    position: relative;
    transition: transform 0.3s ease-in-out;
    &.collapsed {
        transform: translateX(-100%);
    }
}
.collapse-btn {
    position: absolute;
    top: 50%;
    left: 357px;
    background: rgba(17, 23, 34, 0.8);
    border: 1px solid #333355;
    border-left: none;
    border-radius: 0 4px 4px 0;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    z-index: 10;
    backdrop-filter: blur(5px);
    transform: translateY(-50%);
    .arrow {
        width: 24px;
        height: 24px;
        background: url('@/assets/images/dataCockpit/left-btn.png') center / 100% 100% no-repeat;
    }
    &:hover {
        background: rgba(40, 79, 227, 0.6);
    }
}
.category-container {
    display: flex;
    justify-content: space-between;
@@ -95,6 +165,22 @@
            box-shadow: 0px 3px 2px 0px rgba(15, 89, 255, 0.1), 0px 7px 5px 0px rgba(15, 89, 255, 0.15), 0px 13px 10px 0px rgba(15, 89, 255, 0.18), 0px 22px 18px 0px rgba(15, 89, 255, 0.21), 0px 42px 33px 0px rgba(15, 89, 255, 0.26), 0px 100px 80px 0px rgba(15, 89, 255, 0.36);
            border-radius: 5px 5px 0px 0px;
        }
        .badge {
            position: absolute;
            top: 16px;
            right: 4px;
            min-width: 16px;
            height: 16px;
            line-height: 16px;
            font-size: 12px;
            color: #FFFFFF;
            background: #FF3B30;
            border-radius: 9px;
            text-align: center;
            box-shadow: 0px 2px 6px rgba(255, 59, 48, 0.6);
            pointer-events: none;
        }
    }
}
@@ -105,4 +191,4 @@
    overflow-x: hidden;
    overflow-y: auto;
}
</style>
</style>
applications/drone-command/src/views/dataCockpit/components/RightContainer.vue
@@ -1,15 +1,15 @@
<!--
<!--
 * @Author       : yuan
 * @Date         : 2026-01-07 15:17:54
 * @LastEditors  : yuan
 * @LastEditTime : 2026-01-08 15:58:15
 * @LastEditTime : 2026-01-08 17:04:11
 * @FilePath     : \applications\drone-command\src\views\dataCockpit\components\RightContainer.vue
 * @Description  : 
 * Copyright 2026 OBKoro1, All Rights Reserved. 
 * 2026-01-07 15:17:54
-->
<template>
    <div class="right-container">
    <div class="right-container" :class="{ collapsed: collapsedModel }">
        <div class="wrapper">
            <TitleTemplate>侦测反制设备</TitleTemplate>
@@ -31,14 +31,35 @@
                    @history="onHistory" @click="onCardClick" />
            </div>
        </div>
        <div class="collapse-btn" @click="toggleCollapse">
            <span class="arrow" :class="collapsedModel ? 'left' : 'right'"></span>
        </div>
        <DeviceHistoryDialog v-model="historyVisible" :device="historyDevice" />
    </div>
</template>
<script setup>
import TitleTemplate from './templateComponents/TitleTemplate.vue'
import RealEquipmentTemplate from './templateComponents/RealEquipmentTemplate.vue'
import DeviceHistoryDialog from './DeviceHistoryDialog.vue'
import zcsbLogo from '@/assets/images/dataCockpit/zcsb.png'
import fzsbLogo from '@/assets/images/dataCockpit/fzsb.png'
const props = defineProps({
    collapsed: {
        type: Boolean,
        default: false
    }
})
const emit = defineEmits(['update:collapsed'])
const collapsedModel = computed({
    get: () => props.collapsed,
    set: (val) => emit('update:collapsed', val)
})
const categoryList = ref([
    {
@@ -59,7 +80,7 @@
    {
        id: 'cd_001',
        name: '反制设备名称',
        actionText: '侦测中',          // 胶囊文案
        actionText: '侦测中',
        actionType: 'detecting',       // detecting | jamming | idle
        type: '察打一体',
        battery: 100,                  // 电量 %
@@ -86,12 +107,20 @@
    }
])
const historyVisible = ref(false)
const historyDevice = ref(null)
const onHistory = (item) => {
    console.log('历史数据:', item.id)
    historyDevice.value = item
    historyVisible.value = true
    console.log('历史数据:', item.id)
}
const onCardClick = (item) => {
    console.log('点击卡片:', item.id)
    console.log('点击卡片:', item.id)
}
const toggleCollapse = () => {
    collapsedModel.value = !collapsedModel.value
}
</script>
@@ -168,7 +197,7 @@
.right-container {
    transition: transform 0.3s ease-in-out;
    /* position: relative; Removed to avoid conflict with parent absolute positioning */
    position: relative;
    &.collapsed {
        transform: translateX(100%);
@@ -178,9 +207,7 @@
.collapse-btn {
    position: absolute;
    top: 50%;
    left: -24px;
    width: 24px;
    height: 60px;
    right: 357px;
    background: rgba(17, 23, 34, 0.8);
    border: 1px solid #333355;
    border-right: none;
@@ -190,28 +217,18 @@
    align-items: center;
    justify-content: center;
    color: #fff;
    transform: translateY(-50%);
    z-index: 10;
    backdrop-filter: blur(5px);
    transform: translateY(-50%);
    .arrow {
        border: solid #fff;
        border-width: 0 2px 2px 0;
        display: inline-block;
        padding: 3px;
        transition: transform 0.3s;
        &.left {
            transform: rotate(135deg);
        }
        &.right {
            transform: rotate(-45deg);
        }
        width: 24px;
        height: 24px;
        background: url('@/assets/images/dataCockpit/right-btn.png') center / 100% 100% no-repeat;
    }
    &:hover {
        background: rgba(40, 79, 227, 0.6);
    }
}
</style>
</style>
applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue
@@ -1,15 +1,3 @@
<script setup>
defineProps({
    data: {
        type: Object,
        required: true,
        default: () => ({})
    }
})
const emit = defineEmits(['history', 'click'])
</script>
<template>
    <div class="counter-card" @click="emit('click', data)">
        <!-- 头部 -->
@@ -83,6 +71,18 @@
    </div>
</template>
<script setup>
defineProps({
    data: {
        type: Object,
        required: true,
        default: () => ({})
    }
})
const emit = defineEmits(['history', 'click'])
</script>
<style scoped lang="scss">
.counter-card {
    margin-top: 10px;
applications/drone-command/src/views/dataCockpit/index.vue
@@ -11,9 +11,9 @@
<template>
  <div class="page-container">
    <MapContainer />  
    <LeftContainer class="left-container" />
    <RightContainer class="right-container" />
    <CenterContainer />
    <LeftContainer class="left-container" v-model:activeId="leftActiveId" v-model:collapsed="leftCollapsed" />
    <RightContainer class="right-container" v-model:collapsed="rightCollapsed" />
    <CenterContainer @select-warning="onSelectWarning" @select-equipment="onSelectEquipment" />
  </div>
</template>
@@ -22,6 +22,20 @@
  import LeftContainer from './components/LeftContainer.vue';
  import RightContainer from './components/RightContainer.vue';
  import CenterContainer from './components/CenterContainer.vue';
  const leftActiveId = ref(1);
  const leftCollapsed = ref(false);
  const rightCollapsed = ref(false);
  const onSelectWarning = (type) => {
    leftActiveId.value = type;
    leftCollapsed.value = false;
  };
  const onSelectEquipment = () => {
    rightCollapsed.value = false;
  };
</script>
<style scoped lang="scss">