applications/drone-command/src/components/map-container/device-map-container.vue
@@ -119,6 +119,14 @@ let droneTrackTickHandler = null let droneTrackStartTime = null let droneTrackRuntime = [] let favoritePulseRafId = null let favoritePulseStartAt = 0 let favoritePulseElapsed = 0 const favoritePulseEntities = [] const pulseBaseColor = Cesium.Color.fromCssColorString('#FF3B30') const PULSE_MIN_RADIUS_M = 60 const PULSE_MAX_RADIUS_M = 180 const PULSE_DURATION_S = 1.6 const detailVisible = ref(true) const clusterVisible = ref(false) const countyCenterMap = new Map() @@ -172,6 +180,57 @@ */ const adminBoundaryVisible = ref(treeCheckedKeys.value.includes('admin')) const isFavorited = item => { const value = item?.favorited ?? item?.isFavorite return value === 1 || value === '1' || value === true } let pulseCanvas = null const getPulseCanvas = () => { if (pulseCanvas) return pulseCanvas const size = 256 const canvas = document.createElement('canvas') canvas.width = size canvas.height = size const ctx = canvas.getContext('2d') const center = size / 2 const radius = center - 2 const gradient = ctx.createRadialGradient(center, center, radius * 0.1, center, center, radius) gradient.addColorStop(0, 'rgba(255,59,48,0.55)') gradient.addColorStop(0.35, 'rgba(255,59,48,0.35)') gradient.addColorStop(0.7, 'rgba(255,59,48,0.15)') gradient.addColorStop(1, 'rgba(255,59,48,0)') ctx.fillStyle = gradient ctx.beginPath() ctx.arc(center, center, radius, 0, Math.PI * 2) ctx.fill() pulseCanvas = canvas return canvas } const createFavoritePulseEntity = (billboard) => { if (!viewer || !billboard) return const material = new Cesium.ImageMaterialProperty({ image: getPulseCanvas(), transparent: true, }) const pulse = { material, offset: Math.random() * PULSE_DURATION_S, entity: null, } pulse.entity = viewer.entities.add({ position: new Cesium.CallbackProperty(() => billboard.position, false), ellipse: { semiMajorAxis: new Cesium.CallbackProperty(() => getPulseRadius(pulse), false), semiMinorAxis: new Cesium.CallbackProperty(() => getPulseRadius(pulse), false), material, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, }, }) favoritePulseEntities.push(pulse) } const getDevicePosition = item => { const longitudeRaw = item.longitude ?? item.lng ?? item.lon @@ -311,6 +370,12 @@ } } const getPulseRadius = pulse => { const phase = ((favoritePulseElapsed + pulse.offset) % PULSE_DURATION_S) / PULSE_DURATION_S const wave = phase < 0.5 ? phase * 2 : (1 - phase) * 2 return PULSE_MIN_RADIUS_M + (PULSE_MAX_RADIUS_M - PULSE_MIN_RADIUS_M) * wave } const setDetailVisibility = visible => { detailVisible.value = visible if (defenseZonePrimitive) defenseZonePrimitive.show = visible @@ -345,6 +410,11 @@ const setDroneVisibility = visible => { if (droneTrackBillboardCollection) droneTrackBillboardCollection.show = visible if (droneTrackPolylineCollection) droneTrackPolylineCollection.show = visible if (favoritePulseEntities.length) { favoritePulseEntities.forEach(pulse => { if (pulse?.entity) pulse.entity.show = visible }) } if (!visible && selectedTargetType.value === 'drone') { closePopup() } @@ -453,6 +523,7 @@ const clearDroneTrackEntities = () => { if (!viewer) return stopDroneTrackAnimation() stopFavoritePulseAnimation() if (droneTrackBillboardCollection) { if (!droneTrackBillboardCollection.isDestroyed?.()) { removeCockpitPrimitive(droneTrackBillboardCollection) @@ -466,6 +537,12 @@ droneTrackPolylineCollection = null } droneTrackRuntime = [] if (favoritePulseEntities.length) { favoritePulseEntities.forEach(pulse => { if (pulse?.entity) viewer?.entities?.remove(pulse.entity) }) favoritePulseEntities.length = 0 } dronePickMap.clear() if (selectedTargetType.value === 'drone') { closePopup() @@ -532,6 +609,28 @@ droneTrackTickHandler = null } const startFavoritePulseAnimation = () => { if (!viewer || favoritePulseRafId) return favoritePulseStartAt = performance.now() const tick = now => { if (!viewer || viewer.isDestroyed?.() || !favoritePulseEntities.length) { favoritePulseRafId = null return } favoritePulseElapsed = (now - favoritePulseStartAt) / 1000 viewer.scene.requestRender() favoritePulseRafId = requestAnimationFrame(tick) } favoritePulseRafId = requestAnimationFrame(tick) } const stopFavoritePulseAnimation = () => { if (favoritePulseRafId) { cancelAnimationFrame(favoritePulseRafId) favoritePulseRafId = null } } const renderSimulatedDroneTrack = (list) => { if (!viewer) return clearDroneTrackEntities() @@ -576,16 +675,19 @@ }) 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, }) 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, }) if (isFavorited(item)) { createFavoritePulseEntity(billboard) } droneTrackRuntime.push({ positions, billboard, @@ -595,6 +697,7 @@ }) viewer.clock.multiplier = 1 startDroneTrackAnimation() startFavoritePulseAnimation() reorderCockpitPrimitives() } applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
@@ -2,7 +2,7 @@ * @Author : yuan * @Date : 2026-01-08 09:29:07 * @LastEditors : yuan * @LastEditTime : 2026-01-27 12:01:37 * @LastEditTime : 2026-01-27 14:49:56 * @FilePath : \applications\drone-command\src\views\dataCockpit\components\RealWarning.vue * @Description : * Copyright 2026 OBKoro1, All Rights Reserved. @@ -10,7 +10,7 @@ --> <template> <div class="real-warning" v-loading="props.loading" element-loading-background="rgba(5, 5, 15, 0.6)" element-loading-text="???..."> element-loading-text="加载中..."> <div class="list-content"> <EmptyState v-if="!props.data.length && !props.loading" /> <RealTemplate v-else v-for="(item, ind) in props.data" :key="ind" :data="item" applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue
@@ -71,14 +71,14 @@ <el-col :span="12"> <el-form-item label="部门类型" prop="deptCategory"> <el-select class="command-select" popper-class="command-select-popper" v-model="formData.deptCategory" placeholder="请选择" clearable> <el-option v-for="item in dictObj.org_category" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> <el-option v-for="item in dictObj.org_category" :key="item.dictKey" :label="item.dictValue" :value="Number(item.dictKey)" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="部门性质" prop="deptNature"> <el-select class="command-select" popper-class="command-select-popper" v-model="formData.deptNature" placeholder="请选择" clearable> <el-option v-for="item in dictObj.org_nature" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> <el-option v-for="item in dictObj.org_nature" :key="item.dictKey" :label="item.dictValue" :value="Number(item.dictKey)" /> </el-select> </el-form-item> </el-col> @@ -137,9 +137,9 @@ fullName: '', parentId: '', parentName: '', deptCategory: '', deptNature: '', areaCode: '', deptCategory: null, deptNature: null, areaCode: [], areaName: '', deploymentMode: 0, sort: 0, @@ -154,7 +154,7 @@ const areaProps = { label: 'title', value: 'value', emitPath: false, emitPath: true, checkStrictly: true, lazy: true, lazyLoad(node, resolve) { @@ -230,19 +230,26 @@ } function getFullAreaCode(areaCode) { if (!areaCode) return '' if (!areaCode) return [] if (Array.isArray(areaCode)) return areaCode const code = areaCode.toString() if (code.includes(',')) return code if (code.includes(',')) return code.split(',') if (code.endsWith('0000000000')) { return code return [code] } else if (code.endsWith('00000000') && !code.endsWith('0000000000')) { const provinceCode = code.substring(0, 2) + '0000000000' return `${provinceCode},${code}` return [provinceCode, code] } else { const provinceCode = code.substring(0, 2) + '0000000000' const cityCode = code.substring(0, 4) + '00000000' return `${provinceCode},${cityCode},${code}` return [provinceCode, cityCode, code] } } function normalizeNumber(value) { if (value === '' || value === null || value === undefined) return null const num = Number(value) return Number.isNaN(num) ? null : num } function handleCancel() { @@ -283,6 +290,8 @@ const data = res?.data?.data ?? {} formData.value = { ...data, deptCategory: normalizeNumber(data.deptCategory), deptNature: normalizeNumber(data.deptNature), areaCode: getFullAreaCode(data.areaCode), } } applications/task-work-order/src/styles/common/cockpit.scss
@@ -475,6 +475,7 @@ .detail-container { display: flex; justify-content: space-between; height: 680px; .detail-left { margin-right: 20px; @@ -484,16 +485,21 @@ } .detail-right { &>div { .detail-title { margin: 10px 0; } width: 920px; flex: 1; overflow: auto; display: flex; flex-direction: column; &:first-child { .detail-title { margin-top: 0; } } .view-container { display: flex; flex-direction: column; } .gd-cesium { width: 100%; min-height: 380px; flex: 1; } } } applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue
@@ -5,11 +5,10 @@ :title="titleEnum[dialogMode]" @closed="visible = false" destroy-on-close width="auto" :close-on-click-modal="false" > <div class="content" style="display: flex"> <div class="processBox" v-if="dialogMode !== 'add' && processList.length"> <div class="detail-container" style="display: flex"> <div class="detail-left" v-if="dialogMode !== 'add' && processList.length"> <el-timeline class="gd-timeline"> <el-timeline-item v-for="(activity, index) in processList" @@ -27,9 +26,10 @@ </el-timeline-item> </el-timeline> </div> <div class="leftBox"> <div class="detail-right"> <!-- 查看模式 --> <div class="detail-container" v-if="dialogReadonly"> <div class="view-container" v-if="dialogReadonly"> <div class="detail-title">工单详情</div> <el-row class="detail-row-view"> <el-col :span="12"> @@ -507,52 +507,4 @@ </script> <style lang="scss" scoped> .content { display: flex; gap: 0 20px; height: 680px; .leftBox { width: 920px; flex: 1; overflow: auto; display: flex; flex-direction: column; .gd-cesium { width: 100%; min-height: 380px; flex: 1; } } .processBox { width: 312px; padding: 24px 0; border: 1px solid #e4e4e4; overflow: auto; .item-content { position: relative; .flowName { width: 80px; position: absolute; left: -120px; top: 0px; } } } .imgBox { display: flex; flex-wrap: wrap; gap: 10px; > div { width: calc((100% - 40px) / 5); /* 5列布局,减去4个10px间隙 */ height: 110px; flex-shrink: 0; } .el-image { width: 100%; height: 100%; } } } </style>