吉安感知网项目-前端
张含笑
2026-01-27 794dfadf25327ed83f87fb63bacf6d86c80563d3
Merge remote-tracking branch 'origin/master'
5 files modified
240 ■■■■■ changed files
applications/drone-command/src/components/map-container/device-map-container.vue 123 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/RealWarning.vue 4 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue 31 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/styles/common/cockpit.scss 24 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue 58 ●●●●● patch | view | raw | blame | history
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>