吉安感知网项目-前端
shuishen
2026-02-09 d6260866520970082417a795f7dbe9806e1eb431
feat:场景部署列表点击相关事件处理
5 files modified
400 ■■■■ changed files
applications/drone-command/src/components/map-container/common-cesium-map.vue 15 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/components/map-container/device-map-container.vue 41 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/RightContainer.vue 6 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/SceneDeployment.vue 4 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/index.vue 334 ●●●● patch | view | raw | blame | history
applications/drone-command/src/components/map-container/common-cesium-map.vue
@@ -203,7 +203,20 @@
const getViewer = () => viewer
const getPublicCesium = () => viewInstance
defineExpose({ getMap, getViewer, getPublicCesium, setAdminBoundaryVisible, zoomToAdminBoundary })
const flyToPoint = (options) => {
    const { longitude, latitude, height } = options.position
    viewer.camera.setView({
        destination: Cesium.Cartesian3.fromDegrees(longitude, latitude, height),
        orientation: {
            heading: Cesium.Math.toRadians(0), // 方向
            pitch: Cesium.Math.toRadians(-90), // 仰角
            roll: Cesium.Math.toRadians(0) // 翻滚角
        }
    })
}
defineExpose({ getMap, getViewer, getPublicCesium, flyToPoint, setAdminBoundaryVisible, zoomToAdminBoundary })
</script>
<style scoped lang="scss">
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -1,4 +1,4 @@
<template>
<template>
    <div class="map-shell">
        <CommonCesiumMap ref="mapRef" class="command-cesium map-container" :dom-id="props.containerId" :active="true"
            :flat-mode="false" :terrain="true" :layer-mode="4" :contour="false" :boundary="false"
@@ -466,7 +466,7 @@
            },
            {
                longitude: 114.963974,
                latitude:  27.132677,
                latitude: 27.132677,
                height: 320
            },
            {
@@ -513,22 +513,22 @@
                height: 400
            },
            {
                longitude: 114.925836,
                longitude: 114.925836,
                latitude: 27.096102,
                height: 400
            },
            {
                longitude: 114.928384,
                longitude: 114.928384,
                latitude: 27.095866,
                height: 400
            },
            {
                longitude: 114.929874,
                longitude: 114.929874,
                latitude: 27.096095,
                height: 400
            },
            {
                longitude: 114.931048,
                longitude: 114.931048,
                latitude: 27.097666,
                height: 400
            },
@@ -629,11 +629,11 @@
        const pos = isEntityPosition
            ? getBillboardPosition(track.billboard)
            : Cesium.Cartesian3.lerp(
                    track.positions[seg],
                    track.positions[seg + 1],
                    ratio,
                    new Cesium.Cartesian3()
              )
                track.positions[seg],
                track.positions[seg + 1],
                ratio,
                new Cesium.Cartesian3()
            )
        if (!pos) return
        if (!isEntityPosition) {
            track.billboard.position = pos
@@ -848,6 +848,7 @@
                width: 40,
                height: 56,
                verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                disableDepthTestDistance: Number.POSITIVE_INFINITY,
            })
            billboard.id = entry.entityId
            devicePickMap.set(entry.entityId, { data: entry.data, billboard })
@@ -976,7 +977,7 @@
    if (!trimmed) return null
    try {
        return JSON.parse(trimmed)
    } catch (error) {}
    } catch (error) { }
    return null
}
@@ -1029,7 +1030,7 @@
                        .filter(radius => Number.isFinite(radius) && radius > 0)
                    radii.forEach((radius, levelIndex) => {
                        const positions = buildEllipsePositions(centerCartesian, radius, radius)
                    const style = BUFFER_LEVEL_STYLES[levelIndex] || BUFFER_LEVEL_STYLES[BUFFER_LEVEL_STYLES.length - 1]
                        const style = BUFFER_LEVEL_STYLES[levelIndex] || BUFFER_LEVEL_STYLES[BUFFER_LEVEL_STYLES.length - 1]
                        if (positions.length >= 3) {
                            shapes.push({
                                positions,
@@ -1380,6 +1381,20 @@
    viewer = null
    publicCesium = null
})
const getMap = () => {
    return mapRef.value?.getMap()
}
// 暴露给父组件调用
const flyTo = (options) => {
    mapRef.value?.flyToPoint(options)
}
defineExpose({
    getMap,
    flyTo
})
</script>
<style lang="scss" scoped>
applications/drone-command/src/views/dataCockpit/components/RightContainer.vue
@@ -2,7 +2,7 @@
 * @Author       : yuan
 * @Date         : 2026-01-07 15:17:54
 * @LastEditors  : yuan
 * @LastEditTime : 2026-01-31 15:47:40
 * @LastEditTime : 2026-02-09 15:20:23
 * @FilePath     : \applications\drone-command\src\views\dataCockpit\components\RightContainer.vue
 * @Description  : 
 * Copyright 2026 OBKoro1, All Rights Reserved. 
@@ -30,7 +30,7 @@
                    v-if="activeTab === 1"
                    @update:allDevices="handleUpdateAllDevices"
                />
                <SceneDeployment v-else />
                <SceneDeployment v-else @click="(item) => emit('scene-click', item)" />
            </div>
        </div>
@@ -53,7 +53,7 @@
    }
})
const emit = defineEmits(['update:collapsed', 'update:allDevices'])
const emit = defineEmits(['update:collapsed', 'update:allDevices', 'scene-click'])
const collapsedModel = computed({
    get: () => props.collapsed,
applications/drone-command/src/views/dataCockpit/components/SceneDeployment.vue
@@ -16,7 +16,7 @@
        <div class="content" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)"
            element-loading-text="加载中...">
            <EmptyState v-if="!sceneList.length && !loading" />
            <div class="scene-card" v-else v-for="(item, index) in sceneList" :key="index">
            <div class="scene-card" v-else v-for="(item, index) in sceneList" :key="index" @click="emit('click', item)">
                <div class="card-header">
                    <el-tooltip class="title-tooltip" :content="item.sceneName || '-'" placement="top" effect="dark"
                        :show-after="200">
@@ -136,6 +136,8 @@
    }
}
const emit = defineEmits(['click'])
const fetchList = async () => {
    // 只有超过200ms才显示loading,避免闪烁
    loadingTimer.value = setTimeout(() => {
applications/drone-command/src/views/dataCockpit/index.vue
@@ -2,7 +2,7 @@
 * @Author       : yuan
 * @Date         : 2026-01-06 16:35:50
 * @LastEditors  : yuan
 * @LastEditTime : 2026-01-31 15:40:15
 * @LastEditTime : 2026-02-09 15:40:07
 * @FilePath     : \applications\drone-command\src\views\dataCockpit\index.vue
 * @Description  : 
 * Copyright 2026 OBKoro1, All Rights Reserved. 
@@ -10,201 +10,203 @@
-->
<template>
  <div class="page-container">
    <DeviceMapContainer
      :all-devices="allDevices"
      :alarm-drones="realWarningData"
      :left-collapsed="leftCollapsed"
      container-id="data-cockpit-map"
      @drone-signal="handleAlarmAction('signal', $event)"
      @drone-counter="handleAlarmAction('counter', $event)"
      @drone-favorite="handleAlarmFavorite"
    />
    <LeftContainer
      class="left-container"
      v-model:activeId="leftActiveId"
      v-model:collapsed="leftCollapsed"
      :real-warning-data="realWarningData"
      :real-warning-loading="realWarningLoading"
    <DeviceMapContainer ref="deviceMapRef" :all-devices="allDevices" :alarm-drones="realWarningData"
      :left-collapsed="leftCollapsed" container-id="data-cockpit-map"
      @drone-signal="handleAlarmAction('signal', $event)" @drone-counter="handleAlarmAction('counter', $event)"
      @drone-favorite="handleAlarmFavorite" />
    <LeftContainer class="left-container" v-model:activeId="leftActiveId" v-model:collapsed="leftCollapsed"
      :real-warning-data="realWarningData" :real-warning-loading="realWarningLoading"
      @real-warning-signal="handleAlarmAction('signal', $event)"
      @real-warning-counter="handleAlarmAction('counter', $event)"
      @real-warning-favorite="handleAlarmFavorite"
    />
    <RightContainer class="right-container" v-model:collapsed="rightCollapsed"
      v-model:allDevices="allDevices" />
    <CenterContainer @select-warning="onSelectWarning" @select-equipment="onSelectEquipment" />
      @real-warning-counter="handleAlarmAction('counter', $event)" @real-warning-favorite="handleAlarmFavorite" />
    <RightContainer class="right-container" v-model:collapsed="rightCollapsed" v-model:allDevices="allDevices"
      @scene-click="handleSceneClick" />
    <CenterContainer @select-warning="onSelectWarning" @select-equipment="onSelectEquipment" />
    <LegendBar class="legend-container" />
  </div>
</template>
<script setup>
  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';
  import LegendBar from './components/LegendBar.vue';
  import {
    alarmLogApi,
    alarmFavoriteRemoveApi,
    alarmFavoriteSaveApi,
    interferenceAndExpulsionApi
  } from '@/api/dataCockpit';
  import { ElMessage } from 'element-plus';
  import { onMounted, ref } from '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'
import LegendBar from './components/LegendBar.vue'
import {
  alarmLogApi,
  alarmFavoriteRemoveApi,
  alarmFavoriteSaveApi,
  interferenceAndExpulsionApi
} from '@/api/dataCockpit'
import { ElMessage } from 'element-plus'
import { onMounted, ref } from 'vue'
  const leftActiveId = ref(1);
  const leftCollapsed = ref(false);
const leftActiveId = ref(1)
const leftCollapsed = ref(false)
  const rightCollapsed = ref(false);
  const allDevices = ref([]);
  const realWarningData = ref([]);
  const realWarningLoading = ref(false);
  const loadingTimer = ref(null);
const rightCollapsed = ref(false)
const allDevices = ref([])
const realWarningData = ref([])
const realWarningLoading = ref(false)
const loadingTimer = ref(null)
const deviceMapRef = ref(null)
  const onSelectWarning = (type) => {
    leftActiveId.value = type;
    leftCollapsed.value = false;
  };
const onSelectWarning = (type) => {
  leftActiveId.value = type
  leftCollapsed.value = false
}
  const onSelectEquipment = () => {
    rightCollapsed.value = false;
  };
const onSelectEquipment = () => {
  rightCollapsed.value = false
}
  const actionTextMap = {
    signal: '信号干扰',
    counter: '诱导驱离'
  };
const handleSceneClick = (item) => {
  if (item?.longitude && item?.latitude) {
    deviceMapRef.value?.flyTo({
      position: {
        longitude: Number(item.longitude),
        latitude: Number(item.latitude),
        height: 18000
      },
    })
  }
}
  const getAlarmRecordId = (item) => item?.alarmRecordId ?? item?.id;
  const getFavoriteId = (item) =>
    item?.favoriteId ??
    item?.alarmFavoriteId ??
    item?.fwAlarmFavoriteId ??
    item?.favoriteRecordId ??
    getAlarmRecordId(item);
const actionTextMap = {
  signal: '信号干扰',
  counter: '诱导驱离'
}
  const isFavorited = (item) => {
    const value = item?.favorited ?? item?.isFavorite;
    return value === 1 || value === '1' || value === true;
  };
const getAlarmRecordId = (item) => item?.alarmRecordId ?? item?.id
const getFavoriteId = (item) =>
  item?.favoriteId ??
  item?.alarmFavoriteId ??
  item?.fwAlarmFavoriteId ??
  item?.favoriteRecordId ??
  getAlarmRecordId(item)
  const buildPayload = (type, item) => ({
    counterWay: type === 'signal' ? 1 : type === 'counter' ? 2 : '',
    alarmRecordId: getAlarmRecordId(item)
  });
const isFavorited = (item) => {
  const value = item?.favorited ?? item?.isFavorite
  return value === 1 || value === '1' || value === true
}
  const handleAlarmAction = async (type, item) => {
    const actionText = actionTextMap[type] || '操作';
    try {
      const res = await interferenceAndExpulsionApi(buildPayload(type, item));
      if (res?.data?.success) {
        ElMessage({ type: 'success', message: actionText + '成功' });
        await fetchRealWarning();
      } else {
        ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' });
      }
    } catch (error) {
      ElMessage({ type: 'error', message: actionText + '失败' });
const buildPayload = (type, item) => ({
  counterWay: type === 'signal' ? 1 : type === 'counter' ? 2 : '',
  alarmRecordId: getAlarmRecordId(item)
})
const handleAlarmAction = async (type, item) => {
  const actionText = actionTextMap[type] || '操作'
  try {
    const res = await interferenceAndExpulsionApi(buildPayload(type, item))
    if (res?.data?.success) {
      ElMessage({ type: 'success', message: actionText + '成功' })
      await fetchRealWarning()
    } else {
      ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' })
    }
  };
  } catch (error) {
    ElMessage({ type: 'error', message: actionText + '失败' })
  }
}
  const handleAlarmFavorite = async (item) => {
    const alarmRecordId = getAlarmRecordId(item);
    if (!alarmRecordId) {
      ElMessage({ type: 'warning', message: '缺少告警记录ID' });
      return;
const handleAlarmFavorite = async (item) => {
  const alarmRecordId = getAlarmRecordId(item)
  if (!alarmRecordId) {
    ElMessage({ type: 'warning', message: '缺少告警记录ID' })
    return
  }
  const favorite = isFavorited(item)
  const actionText = favorite ? '取消关注' : '关注'
  try {
    const res = favorite
      ? await alarmFavoriteRemoveApi(getFavoriteId(item))
      : await alarmFavoriteSaveApi({ alarmRecordId })
    if (res?.data?.success) {
      ElMessage({ type: 'success', message: actionText + '成功' })
      await fetchRealWarning()
    } else {
      ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' })
    }
    const favorite = isFavorited(item);
    const actionText = favorite ? '取消关注' : '关注';
    try {
      const res = favorite
        ? await alarmFavoriteRemoveApi(getFavoriteId(item))
        : await alarmFavoriteSaveApi({ alarmRecordId });
      if (res?.data?.success) {
        ElMessage({ type: 'success', message: actionText + '成功' });
        await fetchRealWarning();
      } else {
        ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' });
      }
    } catch (error) {
      ElMessage({ type: 'error', message: actionText + '失败' });
    }
  };
  } catch (error) {
    ElMessage({ type: 'error', message: actionText + '失败' })
  }
}
  const fetchRealWarning = async () => {
    // 只有超过200ms才显示loading,避免闪烁
    loadingTimer.value = setTimeout(() => {
      realWarningLoading.value = true;
    }, 200);
const fetchRealWarning = async () => {
  // 只有超过200ms才显示loading,避免闪烁
  loadingTimer.value = setTimeout(() => {
    realWarningLoading.value = true
  }, 200)
    try {
      const params = { current: 1, size: 9999, alarmType: '1' };
      const res = await alarmLogApi(params);
      const records = res?.data?.data?.records ?? [];
      realWarningData.value = records.map((record) => ({
        ...record,
        isFavorite:
          record?.isFavorite ??
          (record?.favorited === 1 || record?.favorited === '1')
      }));
    } finally {
      if (loadingTimer.value) clearTimeout(loadingTimer.value);
      realWarningLoading.value = false;
    }
  };
  try {
    const params = { current: 1, size: 9999, alarmType: '1' }
    const res = await alarmLogApi(params)
    const records = res?.data?.data?.records ?? []
    realWarningData.value = records.map((record) => ({
      ...record,
      isFavorite:
        record?.isFavorite ??
        (record?.favorited === 1 || record?.favorited === '1')
    }))
  } finally {
    if (loadingTimer.value) clearTimeout(loadingTimer.value)
    realWarningLoading.value = false
  }
}
  onMounted(() => {
    fetchRealWarning();
  });
onMounted(() => {
  fetchRealWarning()
})
</script>
<style scoped lang="scss">
  .page-container {
    position: relative;
    width: 100%;
    height: 100%;
  }
.page-container {
  position: relative;
  width: 100%;
  height: 100%;
}
  .left-container,
  .right-container {
.left-container,
.right-container {
  position: absolute;
  bottom: 20px;
  width: 300px;
  z-index: 9;
  backdrop-filter: blur(2px);
  border-radius: 10px 10px 10px 10px;
  ::v-deep(.wrapper) {
    display: flex;
    flex-direction: column;
    position: absolute;
    bottom: 20px;
    top: 0;
    bottom: 0;
    padding: 20px;
    width: 300px;
    z-index: 9;
    backdrop-filter: blur(2px);
    background: rgba(0, 0, 0, 0.3);
    backdrop-filter: blur(16px);
    border-radius: 10px 10px 10px 10px;
    ::v-deep(.wrapper) {
      display: flex;
      flex-direction: column;
      position: absolute;
      top: 0;
      bottom: 0;
      padding: 20px;
      width: 300px;
      background: rgba(0,0,0,0.3);
      backdrop-filter: blur(16px);
      border-radius: 10px 10px 10px 10px;
      box-sizing: border-box;
    }
    box-sizing: border-box;
  }
}
  .left-container {
    top: 113px;
    left: 17px;
    background: linear-gradient( 270deg, rgba(17,23,34,0) 0%, rgba(17,23,34,0.56) 50%, rgba(17,23,34,0.96) 100%);
  }
.left-container {
  top: 113px;
  left: 17px;
  background: linear-gradient(270deg, rgba(17, 23, 34, 0) 0%, rgba(17, 23, 34, 0.56) 50%, rgba(17, 23, 34, 0.96) 100%);
}
  .right-container {
    top: 75px;
    right: 17px;
    background: linear-gradient( 270deg, rgba(17,23,34,0.96) 0.16%, rgba(17,23,34,0.56) 57.57%, rgba(4,30,37,0) 100%);
  }
.right-container {
  top: 75px;
  right: 17px;
  background: linear-gradient(270deg, rgba(17, 23, 34, 0.96) 0.16%, rgba(17, 23, 34, 0.56) 57.57%, rgba(4, 30, 37, 0) 100%);
}
  .legend-container {
    position: absolute;
    left: 50%;
    bottom: 22px;
    transform: translateX(-50%);
    z-index: 9;
  }
.legend-container {
  position: absolute;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  z-index: 9;
}
</style>