吉安感知网项目-前端
shuishen
2026-01-27 9fb1aa745bb4447c47b0851335077cdb44b41b80
feat:无人机轨迹显示调整
5 files modified
585 ■■■■■ changed files
applications/drone-command/src/components/map-container/device-map-container.vue 250 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue 57 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue 35 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/RealWarning.vue 130 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/index.vue 113 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -39,7 +39,7 @@
            @close="closePopup" />
        <DronePopup :visible="popupVisible && isDronePopup" :position="popupPosition" :drone="selectedDevice"
            :favorite="Boolean(selectedDevice?.isFavorite)" @close="closePopup" @toggle-favorite="toggleDroneFavorite"
            :favorite="Boolean(selectedDevice?.isFavorite)" @close="closePopup" @toggle-favorite="handleDroneFavorite"
            @signal="handleDroneSignal" @counter="handleDroneCounter" />
    </div>
</template>
@@ -78,6 +78,10 @@
        type: Array,
        default: () => [],
    },
    alarmDrones: {
        type: Array,
        default: () => [],
    },
    leftCollapsed: {
        type: Boolean,
        default: false,
@@ -91,7 +95,7 @@
        default: true,
    },
})
const emit = defineEmits(['droneSignal', 'droneCounter'])
const emit = defineEmits(['droneSignal', 'droneCounter', 'droneFavorite'])
const DEFAULT_ZONE_PAGE_SIZE = 999
@@ -363,44 +367,86 @@
    })
}
const buildSimulatedTrackPoints = () => {
    return [
        {
            longitude: 114.963191,
            latitude: 27.136716,
            height: 120
        },
        {
            longitude: 114.957308,
            latitude: 27.138452,
            height: 120
        },
        {
            longitude: 114.952,
            latitude: 27.136317,
            height: 120
        },
        {
            longitude: 114.949293,
            latitude: 27.133864,
            height: 120
        },
        {
            longitude: 114.944666,
            latitude: 27.130526,
            height: 120
        },
        {
            longitude: 114.945909,
            latitude: 27.127845,
            height: 120
        },
        {
            longitude: 114.962974,
            latitude: 27.136242,
            height: 120
        },
const buildSimulatedTrackPoints = center => {
    const basePoints = [
        [
            {
                longitude: 114.963191,
                latitude: 27.136716,
                height: 120
            },
            {
                longitude: 114.957308,
                latitude: 27.138452,
                height: 120
            },
            {
                longitude: 114.952,
                latitude: 27.136317,
                height: 120
            },
            {
                longitude: 114.949293,
                latitude: 27.133864,
                height: 120
            },
            {
                longitude: 114.944666,
                latitude: 27.130526,
                height: 120
            },
            {
                longitude: 114.945909,
                latitude: 27.127845,
                height: 120
            },
            {
                longitude: 114.962974,
                latitude: 27.136242,
                height: 120
            },
        ],
        [
            {
                longitude: 114.931063,
                latitude: 27.095052,
                height: 120
            },
            {
                longitude: 114.928653,
                latitude: 27.096307,
                height: 120
            },
            {
                longitude: 114.925958,
                latitude: 27.096963,
                height: 120
            },
            {
                longitude: 114.925836,
                latitude: 27.096102,
                height: 120
            },
            {
                longitude: 114.928384,
                latitude: 27.095866,
                height: 120
            },
            {
                longitude: 114.929874,
                latitude: 27.096095,
                height: 120
            },
            {
                longitude: 114.931048,
                latitude: 27.097666,
                height: 120
            },
        ]
    ]
    return basePoints[center.trackIndex]
}
@@ -486,70 +532,67 @@
    droneTrackTickHandler = null
}
const renderSimulatedDroneTrack = () => {
const renderSimulatedDroneTrack = (list) => {
    if (!viewer) return
    clearDroneTrackEntities()
    if (!list?.length) return
    ensureDroneTrackCollections()
    droneTrackBillboardCollection.show = detailVisible.value
    droneTrackPolylineCollection.show = detailVisible.value
    droneTrackRuntime = []
    const baseCenters = [['默认区域', { longitude: 114.958541, latitude: 27.121917 }]]
    const segmentDuration = 6
    const baseTrackColor = Cesium.Color.fromCssColorString('red')
    baseCenters.forEach(([centerName, center], trackIndex) => {
        const points = buildSimulatedTrackPoints(center)
        if (points.length < 2) return
        const positions = points.map(point =>
            Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height)
        )
        let trackMaterial = createDroneTrackMaterial({
            color: baseTrackColor,
            speed: 4.5,
            headWidth: 0.2,
            glowPower: 1.8,
            backgroundAlpha: 0.34,
        ; (list || []).forEach((item, trackIndex) => {
            const position = getDevicePosition(item)
            if (!position) return
            const points = buildSimulatedTrackPoints({ ...position, height: item.flightHeightM, trackIndex })
            if (points.length < 2) return
            const positions = points.map(point =>
                Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height)
            )
            let trackMaterial = createDroneTrackMaterial({
                color: baseTrackColor,
                speed: 4.5,
                headWidth: 0.2,
                glowPower: 1.8,
                backgroundAlpha: 0.34,
            })
            if (!trackMaterial) {
                trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor })
            }
            droneTrackPolylineCollection.add({
                positions,
                width: 3,
                material: trackMaterial,
            })
            const droneId = `drone-alarm-${item?.alarmRecordId ?? item?.id ?? trackIndex}`
            const billboard = droneTrackBillboardCollection.add({
                position: positions[positions.length - 1],
                image: droneIcon,
                width: 36,
                height: 36,
                verticalOrigin: Cesium.VerticalOrigin.CENTER,
                disableDepthTestDistance: Number.POSITIVE_INFINITY,
            })
            billboard.id = droneId
            const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration)
            dronePickMap.set(droneId, {
                data: {
                    ...item,
                    flightHeightM: item.flightHeightM ?? points[0].height,
                    flightSpeedMs: item.flightSpeedMs ?? speedMs,
                    longitude: item.longitude ?? points[0].longitude,
                    latitude: item.latitude ?? points[0].latitude,
                },
                billboard,
            })
            droneTrackRuntime.push({
                positions,
                billboard,
                segmentDuration,
                duration: (points.length - 1) * segmentDuration,
            })
        })
        if (!trackMaterial) {
            trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor })
        }
        droneTrackPolylineCollection.add({
            positions,
            width: 3,
            material: trackMaterial,
        })
        const droneId = `drone-track-${trackIndex}`
        const billboard = droneTrackBillboardCollection.add({
            position: positions[positions.length - 1],
            image: droneIcon,
            width: 36,
            height: 36,
            verticalOrigin: Cesium.VerticalOrigin.CENTER,
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
        })
        billboard.id = droneId
        const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration)
        dronePickMap.set(droneId, {
            data: {
                droneName: `无人机-${trackIndex + 1}`,
                droneSerialNo: `UAV-${trackIndex + 1}`,
                flightHeightM: points[0].height,
                flightSpeedMs: speedMs,
                longitude: points[0].longitude,
                latitude: points[0].latitude,
                flightStatus: 1,
                deviceName: centerName,
                dataSource: centerName,
                counterDeviceName: centerName,
            },
            billboard,
        })
        droneTrackRuntime.push({
            positions,
            billboard,
            segmentDuration,
            duration: (points.length - 1) * segmentDuration,
        })
    })
    viewer.clock.multiplier = 1
    startDroneTrackAnimation()
    reorderCockpitPrimitives()
@@ -860,9 +903,9 @@
const popupPosition = ref({ x: 0, y: 0 })
const popupVisible = computed(() => Boolean(selectedDevice.value))
const isDronePopup = computed(() => selectedTargetType.value === 'drone')
const toggleDroneFavorite = () => {
const handleDroneFavorite = () => {
    if (!selectedDevice.value) return
    selectedDevice.value.isFavorite = !selectedDevice.value.isFavorite
    emit('droneFavorite', selectedDevice.value)
}
const handleDroneSignal = () => emit('droneSignal', selectedDevice.value)
const handleDroneCounter = () => emit('droneCounter', selectedDevice.value)
@@ -911,6 +954,19 @@
    () => props.onlineDevices,
    devices => {
        renderDeviceEntities(devices || [])
    },
    { deep: true }
)
watch(
    () => props.alarmDrones,
    list => {
        renderSimulatedDroneTrack(list || [])
        if (selectedTargetType.value === 'drone' && selectedDevice.value) {
            const selectedId = selectedDevice.value.alarmRecordId ?? selectedDevice.value.id
            const match = (list || []).find(item => (item?.alarmRecordId ?? item?.id) === selectedId)
            if (match) selectedDevice.value = { ...selectedDevice.value, ...match }
        }
    },
    { deep: true }
)
@@ -970,7 +1026,7 @@
    loadPartitions()
    loadAggregation()
    loadCommandPosts()
    renderSimulatedDroneTrack()
    renderSimulatedDroneTrack(props.alarmDrones)
    initDeviceClickHandler()
}
applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue
@@ -62,15 +62,20 @@
                        <el-table-column prop="charger" label="负责人" />
                        <el-table-column prop="contactPhone" label="联系电话" />
                        <el-table-column prop="outTarget" label="出库去向" />
                        <el-table-column prop="outTime" label="出库时间" />
                        <el-table-column prop="outTime" show-overflow-tooltip label="出库时间">
                            <template v-slot="{ row }">
                                {{ dayjs(row.outTime).format('YYYY-MM-DD') }}
                            </template>
                        </el-table-column>
                        <el-table-column prop="purpose" label="用途" />
                    </el-table>
                </div>
                <div class="command-table-pagination">
                    <el-pagination popper-class="command-select-popper"
                        v-model:current-page="searchParams.current" v-model:page-size="searchParams.size"
                        layout="total, prev, pager, next, sizes" :total="total" @change="getList" />
                    <el-pagination popper-class="command-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>
        </div>
@@ -79,8 +84,8 @@
            <el-row>
                <el-col :span="12">
                    <el-form-item label="设备名称" prop="deviceName">
                        <el-input class="command-input" v-model="formData.deviceName" maxlength="50"
                            placeholder="请输入" clearable />
                        <el-input class="command-input" v-model="formData.deviceName" maxlength="50" placeholder="请输入"
                            clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
@@ -94,8 +99,8 @@
                </el-col>
                <el-col :span="12">
                    <el-form-item label="设备编码" prop="deviceSn">
                        <el-input class="command-input" v-model="formData.deviceSn" maxlength="50"
                            placeholder="请输入" clearable />
                        <el-input class="command-input" v-model="formData.deviceSn" maxlength="50" placeholder="请输入"
                            clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
@@ -109,40 +114,39 @@
                </el-col>
                <el-col :span="12">
                    <el-form-item label="型号" prop="deviceModel">
                        <el-input class="command-input" v-model="formData.deviceModel" maxlength="50"
                            placeholder="请输入" clearable />
                        <el-input class="command-input" v-model="formData.deviceModel" maxlength="50" placeholder="请输入"
                            clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="规格" prop="deviceSpecification">
                        <el-input class="command-input" v-model="formData.deviceSpecification"
                            maxlength="50" placeholder="请输入" clearable />
                        <el-input class="command-input" v-model="formData.deviceSpecification" maxlength="50"
                            placeholder="请输入" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="生产厂商" prop="manufacturer">
                        <el-input class="command-input" v-model="formData.manufacturer" maxlength="50"
                            placeholder="请输入" clearable />
                        <el-input class="command-input" v-model="formData.manufacturer" maxlength="50" placeholder="请输入"
                            clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="来源" prop="source">
                        <el-input class="command-input" v-model="formData.source" maxlength="50"
                            placeholder="请输入" clearable />
                        <el-input class="command-input" v-model="formData.source" maxlength="50" placeholder="请输入"
                            clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="用途" prop="purpose">
                        <el-input class="command-input" v-model="formData.purpose" maxlength="50"
                            placeholder="请输入" clearable />
                        <el-input class="command-input" v-model="formData.purpose" maxlength="50" placeholder="请输入"
                            clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="所属部门" prop="belongDept">
                        <el-tree-select class="command-select"
                            popper-class="command-tree-select-popper" v-model="formData.belongDept"
                            node-key="id" :data="deptTree" :props="treeProps" check-strictly @change="belongDeptChange"
                            clearable />
                        <el-tree-select class="command-select" popper-class="command-tree-select-popper"
                            v-model="formData.belongDept" node-key="id" :data="deptTree" :props="treeProps"
                            check-strictly @change="belongDeptChange" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
@@ -155,14 +159,15 @@
                </el-col>
                <el-col :span="12">
                    <el-form-item label="联系电话" prop="contactPhone">
                        <el-input class="command-input" v-model="formData.contactPhone" maxlength="50"
                            placeholder="请输入" clearable />
                        <el-input class="command-input" v-model="formData.contactPhone" maxlength="50" placeholder="请输入"
                            clearable />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <template #footer>
            <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
            <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消'
                }}</el-button>
            <el-button color="#284FE3" v-if="!dialogReadonly" type="primary" :loading="submitting"
                :disabled="submitting" @click="handleSubmit">
                保存
@@ -172,6 +177,8 @@
</template>
<script setup>
import dayjs from 'dayjs'
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { fwDeviceDetailApi, fwDeviceSubmitApi } from '@/views/basicManage/deviceStock/fwDevice'
applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue
@@ -12,7 +12,16 @@
            </div>
            <div class="content">
                <component :is="currentComponent" />
                <RealWarning
                    v-if="activeIdModel === 1"
                    :data="realWarningData"
                    :loading="realWarningLoading"
                    @signal="emit('real-warning-signal', $event)"
                    @counter="emit('real-warning-counter', $event)"
                    @favorite="emit('real-warning-favorite', $event)"
                />
                <EquipmentWarning v-else-if="activeIdModel === 2" />
                <HistoryWarning v-else />
            </div>
        </div>
@@ -24,11 +33,11 @@
<script setup>
import TitleTemplate from './templateComponents/TitleTemplate.vue'
import RealWarning from './RealWarning.vue'
import EquipmentWarning from './EquipmentWarning.vue'
import HistoryWarning from './HistoryWarning.vue'
import { alarmStatisticsApi } from '@/api/dataCockpit'
import { onMounted } from 'vue'
import { computed, onMounted, ref } from 'vue'
import RealWarning from './RealWarning.vue'
const props = defineProps({
    activeId: {
@@ -38,10 +47,24 @@
    collapsed: {
        type: Boolean,
        default: false
    },
    realWarningData: {
        type: Array,
        default: () => []
    },
    realWarningLoading: {
        type: Boolean,
        default: false
    }
})
const emit = defineEmits(['update:activeId', 'update:collapsed'])
const emit = defineEmits([
    'update:activeId',
    'update:collapsed',
    'real-warning-signal',
    'real-warning-counter',
    'real-warning-favorite'
])
const activeIdModel = computed({
    get: () => props.activeId,
@@ -73,10 +96,6 @@
        component: HistoryWarning
    }
])
const currentComponent = computed(() => {
    return categoryList.value.find(i => i.id === activeIdModel.value)?.component
})
const toggleCollapse = () => {
    collapsedModel.value = !collapsedModel.value
applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
@@ -2,137 +2,39 @@
 * @Author       : yuan
 * @Date         : 2026-01-08 09:29:07
 * @LastEditors  : yuan
 * @LastEditTime : 2026-01-26 09:59:52
 * @LastEditTime : 2026-01-27 12:01:37
 * @FilePath     : \applications\drone-command\src\views\dataCockpit\components\RealWarning.vue
 * @Description  : 
 * Copyright 2026 OBKoro1, All Rights Reserved. 
 * 2026-01-08 09:29:07
-->
<template>
    <div
        class="real-warning"
        v-loading="loading"
        element-loading-background="rgba(5, 5, 15, 0.6)"
        element-loading-text="加载中..."
    >
    <div class="real-warning" v-loading="props.loading" element-loading-background="rgba(5, 5, 15, 0.6)"
        element-loading-text="???...">
        <div class="list-content">
            <EmptyState v-if="!realWarningData.length" />
            <RealTemplate
                v-else
                v-for="(item, ind) in realWarningData"
                :key="ind"
                :data="item"
                @signal="() => handleAction('signal', item)"
                @counter="() => handleAction('counter', item)"
                @favorite="() => handleFavorite(item)"
            />
            <EmptyState v-if="!props.data.length && !props.loading" />
            <RealTemplate v-else v-for="(item, ind) in props.data" :key="ind" :data="item"
                @signal="emit('signal', item)" @counter="emit('counter', item)" @favorite="emit('favorite', item)" />
        </div>
    </div>
</template>
<script setup>
import RealTemplate from './templateComponents/RealTemplate.vue'
import {
    alarmLogApi,
    alarmFavoriteRemoveApi,
    alarmFavoriteSaveApi,
    interferenceAndExpulsionApi
} from '@/api/dataCockpit'
import { onMounted, ref } from 'vue'
import EmptyState from './EmptyState.vue'
import { ElMessage } from 'element-plus'
const realWarningData = ref([])
const loading = ref(true)
const minLoadingMs = 400
const actionTextMap = {
    signal: '信号干扰',
    counter: '诱导驱离'
}
const getAlarmRecordId = (item) => item?.alarmRecordId ?? item?.id
const getFavoriteId = (item) =>
    item?.favoriteId ??
    item?.alarmFavoriteId ??
    item?.fwAlarmFavoriteId ??
    item?.favoriteRecordId ??
    getAlarmRecordId(item)
const isFavorited = (item) => {
    const value = item?.favorited ?? item?.isFavorite
    return value === 1 || value === '1' || value === true
}
const buildPayload = (type, item) => ({
    counterWay: type === 'signal' ? 1 : type === 'counter' ? 2 : '',
    alarmRecordId: getAlarmRecordId(item)
const props = defineProps({
    data: {
        type: Array,
        default: () => []
    },
    loading: {
        type: Boolean,
        default: false
    }
})
const handleAction = 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 handleFavorite = async (item) => {
    const alarmRecordId = getAlarmRecordId(item)
    if (!alarmRecordId) {
        ElMessage({ type: 'warning', message: '缺少告警记录ID' })
        return
    }
    const isFavorite = isFavorited(item)
    const actionText = isFavorite ? '取消关注' : '关注'
    try {
        const res = isFavorite
            ? 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 + '失败' })
    }
}
const fetchRealWarning = async () => {
    const startAt = Date.now()
    loading.value = true
    try {
        const params = { current: 1, size: 20, 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 {
        const elapsed = Date.now() - startAt
        if (elapsed < minLoadingMs) {
            await new Promise((resolve) => setTimeout(resolve, minLoadingMs - elapsed))
        }
        loading.value = false
    }
}
onMounted(() => {
    fetchRealWarning()
})
const emit = defineEmits(['signal', 'counter', 'favorite'])
</script>
<style lang="scss" scoped>
applications/drone-command/src/views/dataCockpit/index.vue
@@ -12,10 +12,23 @@
  <div class="page-container">
    <DeviceMapContainer
      :online-devices="onlineDevices"
      :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" />
    <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:onlineDevices="onlineDevices" />  
    <CenterContainer @select-warning="onSelectWarning" @select-equipment="onSelectEquipment" />  
    <LegendBar class="legend-container" />
@@ -28,12 +41,23 @@
  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 rightCollapsed = ref(false);
  const onlineDevices = ref([]);
  const realWarningData = ref([]);
  const realWarningLoading = ref(true);
  const minLoadingMs = 400;
  const onSelectWarning = (type) => {
    leftActiveId.value = type;
@@ -43,6 +67,93 @@
  const onSelectEquipment = () => {
    rightCollapsed.value = false;
  };
  const actionTextMap = {
    signal: '信号干扰',
    counter: '诱导驱离'
  };
  const getAlarmRecordId = (item) => item?.alarmRecordId ?? item?.id;
  const getFavoriteId = (item) =>
    item?.favoriteId ??
    item?.alarmFavoriteId ??
    item?.fwAlarmFavoriteId ??
    item?.favoriteRecordId ??
    getAlarmRecordId(item);
  const isFavorited = (item) => {
    const value = item?.favorited ?? item?.isFavorite;
    return value === 1 || value === '1' || value === true;
  };
  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 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 + '失败' });
    }
  };
  const fetchRealWarning = async () => {
    const startAt = Date.now();
    realWarningLoading.value = true;
    try {
      const params = { current: 1, size: 20, 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 {
      const elapsed = Date.now() - startAt;
      if (elapsed < minLoadingMs) {
        await new Promise((resolve) => setTimeout(resolve, minLoadingMs - elapsed));
      }
      realWarningLoading.value = false;
    }
  };
  onMounted(() => {
    fetchRealWarning();
  });
</script>
<style scoped lang="scss">