Merge remote-tracking branch 'origin/master'
10 files modified
1 files added
| | |
| | | @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> |
| | |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | alarmDrones: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | leftCollapsed: { |
| | | type: Boolean, |
| | | default: false, |
| | |
| | | default: true, |
| | | }, |
| | | }) |
| | | const emit = defineEmits(['droneSignal', 'droneCounter']) |
| | | const emit = defineEmits(['droneSignal', 'droneCounter', 'droneFavorite']) |
| | | |
| | | const DEFAULT_ZONE_PAGE_SIZE = 999 |
| | | |
| | |
| | | }) |
| | | } |
| | | |
| | | 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] |
| | | } |
| | | |
| | | |
| | |
| | | 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() |
| | |
| | | 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) |
| | |
| | | () => 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 } |
| | | ) |
| | |
| | | loadPartitions() |
| | | loadAggregation() |
| | | loadCommandPosts() |
| | | renderSimulatedDroneTrack() |
| | | renderSimulatedDroneTrack(props.alarmDrones) |
| | | initDeviceClickHandler() |
| | | } |
| | | |
| | |
| | | <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> |
| | |
| | | <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"> |
| | |
| | | </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"> |
| | |
| | | </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"> |
| | |
| | | </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"> |
| | | 保存 |
| | |
| | | </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' |
| | |
| | | </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> |
| | | |
| | |
| | | |
| | | <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: { |
| | |
| | | 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, |
| | |
| | | component: HistoryWarning |
| | | } |
| | | ]) |
| | | |
| | | const currentComponent = computed(() => { |
| | | return categoryList.value.find(i => i.id === activeIdModel.value)?.component |
| | | }) |
| | | |
| | | const toggleCollapse = () => { |
| | | collapsedModel.value = !collapsedModel.value |
| | |
| | | * @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> |
| | |
| | | <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" /> |
| | |
| | | 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; |
| | |
| | | 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"> |
| | |
| | | let viewPlane |
| | | const hasPatrolTaskList = computed(() => ['30', '40', '50', '60'].includes(String(formData.value.workOrderStatus))) |
| | | import droneIcon from '@/assets/images/orderView/orderManage/drone.png' |
| | | import droneActiveIcon from '@/assets/images/orderView/orderManage/droneActive.png' |
| | | |
| | | const gdStatusObj = { |
| | | '10': { reason: '拒绝原因', operationType: '2' }, |
| | |
| | | const selectedIds = (formData.value.recommendDeviceIds || '').split(',').filter(Boolean) |
| | | if (!viewer?.entities) return |
| | | viewer.entities.values.forEach(entity => { |
| | | if (entity.billboard?.image?._value === droneIcon && entity.label) { |
| | | const image = entity.billboard?.image?._value |
| | | if ((image === droneIcon || image === droneActiveIcon) && entity.label) { |
| | | const device = deviceList.value.find(d => d.nickname === entity.label.text._value) |
| | | if (device && selectedIds.includes(String(device.id))) { |
| | | entity.label.fillColor = Cesium.Color.YELLOW |
| | | } else { |
| | | entity.label.fillColor = Cesium.Color.WHITE |
| | | } |
| | | entity.billboard.image = device && selectedIds.includes(String(device.id)) |
| | | ? droneActiveIcon |
| | | : droneIcon |
| | | } |
| | | }) |
| | | } |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.name" border="none"></u-input> |
| | | <u-input v-model="formParams.name" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="SN(出厂序列号)" |
| | |
| | | :borderBottom="true" |
| | | ref="item2" |
| | | > |
| | | <u-input v-model="formParams.sn" border="none"></u-input> |
| | | <u-input v-model="formParams.sn" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="飞行器厂商" |
| | |
| | | ref="item2" |
| | | > |
| | | <u-input |
| | | v-model="formParams.manufacturer" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | v-model="formParams.manufacturerText" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.model" border="none"></u-input> |
| | | <u-input v-model="formParams.model" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="是否改装" |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.isModified" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.type" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | ref="item1" |
| | | > |
| | | <u-input |
| | | v-model="formParams.holder" |
| | | v-model="formParams.holder" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.purchaseDate" border="none" placeholder="请选择日期" suffixIcon="calendar"></u-input> |
| | | <u-input v-model="formParams.purchaseDate" readonly border="none" placeholder="请选择日期" suffixIcon="calendar"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="有无保险" |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.hasInsurance" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.region" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | ref="item1" |
| | | > |
| | | <u-input |
| | | v-model="formParams.caacRegistrationCode" |
| | | v-model="formParams.caacRegistrationCode" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | ref="item1" |
| | | > |
| | | <u-input |
| | | v-model="formParams.deviceImage" |
| | | v-model="formParams.deviceImage" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | ref="item1" |
| | | > |
| | | <u-input |
| | | v-model="formParams.remarks" |
| | | v-model="formParams.remarks" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | current: 1, |
| | | size: 1000, |
| | | }).then(res => { |
| | | manufacturerList.value = res.data.data.records.map(item => ({ |
| | | ...item, |
| | | label: item.unitName, |
| | | value: item.id, |
| | | })) |
| | | // manufacturerList.value = res.data.data.records.map(item => ({ |
| | | // ...item, |
| | | // label: item.unitName, |
| | | // value: item.id, |
| | | // })) |
| | | console.log(formParams.value.manufacturer, res.data.data.records.find(item => item.id === formParams.value.manufacturer)) |
| | | formParams.value.manufacturerText = res.data.data.records.find(item => item.id === formParams.value.manufacturer)?.unitName || '' |
| | | }) |
| | | } |
| | | onLoad((options) => { |
| | |
| | | formParams.value = res.data.data |
| | | formParams.value.isModified = res.data.data.isModified ? '是' : '否' |
| | | formParams.value.hasInsurance = res.data.data.hasInsurance === '1' ? '有' : '无' |
| | | formParams.value.manufacturer = manufacturerList.value.find(item => item.value === res.data.data.manufacturer)?.label || '' |
| | | // formParams.value.manufacturer = manufacturerList.value.find(item => item.value === res.data.data.manufacturer)?.label || '' |
| | | formParams.value.type = typeList.value.find(item => item.value === res.data.data.type)?.label || '' |
| | | formParams.value.region = res.data.data.region |
| | | getManufacturerInfoApi() |
| | | }) |
| | | |
| | | |
| | | }) |
| | | onMounted(async () => { |
| | | // getManufacturerInfoApi() |
| | | getProTypeApi() |
| | | getManufacturerInfoApi() |
| | | // 动态导入xzqhData,确保在使用前已初始化 |
| | | const { xzqhData } = await import('@/static/xzqh/index') |
| | | areaData.value = xzqhData |
| | |
| | | @confirm="onPickerPlanType" |
| | | @cancel="isShowPlanType = false" |
| | | /> |
| | | <up-datetime-picker |
| | | <u-datetime-picker |
| | | :show="isShowStartTime" |
| | | v-model="startTime" |
| | | mode="datetime" |
| | | :min-date="minDate" |
| | | @confirm="onConfirmStartTime" |
| | | @cancel="isShowStartTime = false" |
| | | ></up-datetime-picker> |
| | | <up-datetime-picker |
| | | ></u-datetime-picker> |
| | | <u-datetime-picker |
| | | :show="isShowEndTime" |
| | | v-model="endTime" |
| | | mode="datetime" |
| | | :min-date="minDate" |
| | | @confirm="onConfirmEndTime" |
| | | @cancel="isShowEndTime = false" |
| | | ></up-datetime-picker> |
| | | ></u-datetime-picker> |
| | | <!-- <u-calendar :show="isShowTimeRange" mode="date" @confirm="onConfirmTimeRange" @close="isShowTimeRange = false" :close-on-click-overlay="true"></u-calendar> --> |
| | | <!-- <u-calendar :show="isShowTimeRange" mode="range" @confirm="onConfirmTimeRange" @close="isShowTimeRange = false" :close-on-click-overlay="true" :allow-same-day="true" :show-time="true" time-format="HH:mm:ss"></u-calendar> --> |
| | | <!-- 飞行器 --> |
| | |
| | | const isShowStartTime = ref(false) |
| | | // 是否显示结束时间选择器 |
| | | const isShowEndTime = ref(false) |
| | | const minDate = ref(Date.now()); // 设置最小日期为当前时间戳 |
| | | const startTime = ref(Date.now()); // 设置默认值为当前时间戳 |
| | | const endTime = ref(Date.now()); // 设置默认值为当前时间戳 |
| | | |
| | |
| | | duration: 2000 |
| | | }) |
| | | } |
| | | }).finally(() => { |
| | | isSubmitDisabled.value = false |
| | | }) |
| | | } |
| | | onMounted(() => { |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.flightPlanName" border="none"></u-input> |
| | | <u-input v-model="formParams.flightPlanName" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="任务类型" |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.flightTaskType" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.flightRule" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.flightMode" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.flightStartTime" border="none" suffixIcon="calendar"></u-input> |
| | | <u-input v-model="formParams.flightStartTime" border="none" readonly suffixIcon="calendar"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="飞行活动结束日期" |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.flightEndTime" border="none" suffixIcon="calendar"></u-input> |
| | | <u-input v-model="formParams.flightEndTime" readonly border="none" suffixIcon="calendar"></u-input> |
| | | </u-form-item> |
| | | |
| | | <u-form-item |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.planType" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.contactPerson" border="none"></u-input> |
| | | <u-input v-model="formParams.contactPerson" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="联系电话" |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.contactPhone" border="none"></u-input> |
| | | <u-input v-model="formParams.contactPhone" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="承办人" |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.undertaker" border="none"></u-input> |
| | | <u-input v-model="formParams.undertaker" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="承办人联系电话" |
| | |
| | | :borderBottom="true" |
| | | ref="item1" |
| | | > |
| | | <u-input v-model="formParams.undertakerPhone" border="none"></u-input> |
| | | <u-input v-model="formParams.undertakerPhone" readonly border="none"></u-input> |
| | | </u-form-item> |
| | | <u-form-item |
| | | label="临时空域批件" |
| | |
| | | ref="item1" |
| | | > |
| | | <u-input |
| | | v-model="formParams.weatherCondition" |
| | | v-model="formParams.weatherCondition" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.aircraftId" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.pilotId" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.airspaceId" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | > |
| | | <u-input |
| | | v-model="formParams.routeId" |
| | | disabled |
| | | disabledColor="#ffffff" |
| | | readonly |
| | | border="none" |
| | | ></u-input> |
| | | </u-form-item> |
| | |
| | | flightPlanDetailsApi(options.id).then(res => { |
| | | // return |
| | | const result = res.data.data |
| | | console.log(result, '7777') |
| | | formParams.value = result |
| | | formParams.value.routeId = result.routeInfo.name |
| | | formParams.value.airspaceId = result.airspaceInfo.taskName || '无' |
| | | formParams.value.aircraftId = result.airspaceInfo.aircraftInfoList.map(item => item.name).join(',') || '无' |
| | | formParams.value.pilotId = result.operatorInfoList.map(item => item.name).join(',') || '无' |
| | | // 字典 |
| | | formParams.value.flightRule = actionsFlightRule.value.find(item => item.dictValue === result.flightRule)?.dictLabel || '无' |
| | | formParams.value.flightMode = actionsFlightMode.value.find(item => item.dictValue === result.flightMode)?.dictLabel || '无' |
| | | formParams.value.planType = actionsPlanType.value.find(item => item.dictValue === result.planType)?.dictLabel || '无' |
| | | formParams.value.flightTaskType = actionsFlightTaskType.value.find(item => item.dictValue === result.flightTaskType)?.dictLabel || '无' |
| | | // 数据 |
| | | formParams.value.routeId = result?.routeInfo?.name || '无' |
| | | formParams.value.airspaceId = result?.airspaceInfo?.taskName || '无' |
| | | formParams.value.aircraftId = result?.airspaceInfo?.aircraftInfoList?.map(item => item.name).join(',') || '无' |
| | | formParams.value.pilotId = result?.operatorInfoList?.map(item => item.name).join(',') || '无' |
| | | |
| | | }) |
| | | }) |
| | | onMounted(() => { |