吉安感知网项目-前端
罗广辉
2026-01-27 7319ec49da9f1490ad35f47e0da62546d1cc7ff4
Merge remote-tracking branch 'origin/master'
29 files modified
2 files added
1879 ■■■■ changed files
applications/drone-command/src/components/map-container/device-map-container.vue 343 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/DrawPolygon.js 24 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue 13 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/index.vue 9 ●●●● 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/components/templateComponents/EquipmentTemplate.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/index.vue 113 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue 31 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/permissionManage/permissionRole/index.vue 4 ●●●● 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/flyingHand/FlyingHandDialog.vue 36 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/index.vue 4 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue 58 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/FormDiaLog.vue 98 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/index.vue 30 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/zoningManagement/FormDiaLog.vue 316 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/zoningManagement/index.vue 337 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/zoningManagement/zoningApi.js 66 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/subPackages/workDetail/index.vue 8 ●●●●● patch | view | raw | blame | history
uniapps/work-wx/src/pages/equipmentManagement/index.vue 6 ●●●● patch | view | raw | blame | history
uniapps/work-wx/src/pages/legalPolicy/index.vue 7 ●●●● patch | view | raw | blame | history
uniapps/work-wx/src/subPackages/deviceRegistration/add.vue 1 ●●●● patch | view | raw | blame | history
uniapps/work-wx/src/subPackages/deviceRegistration/details.vue 54 ●●●● patch | view | raw | blame | history
uniapps/work-wx/src/subPackages/flightApplication/add.vue 13 ●●●●● patch | view | raw | blame | history
uniapps/work-wx/src/subPackages/flightApplication/details.vue 52 ●●●● patch | view | raw | blame | history
uniapps/work-wx/src/subPackages/flightApplication/index.vue 2 ●●● 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
@@ -115,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()
@@ -168,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
@@ -307,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
@@ -341,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()
    }
@@ -363,50 +437,93 @@
    })
}
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]
}
const clearDroneTrackEntities = () => {
    if (!viewer) return
    stopDroneTrackAnimation()
    stopFavoritePulseAnimation()
    if (droneTrackBillboardCollection) {
        if (!droneTrackBillboardCollection.isDestroyed?.()) {
            removeCockpitPrimitive(droneTrackBillboardCollection)
@@ -420,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()
@@ -486,72 +609,95 @@
    droneTrackTickHandler = null
}
const renderSimulatedDroneTrack = () => {
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()
    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,
        })
        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)
        ; (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: {
                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,
                ...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 (isFavorited(item)) {
            createFavoritePulseEntity(billboard)
        }
            droneTrackRuntime.push({
                positions,
                billboard,
                segmentDuration,
                duration: (points.length - 1) * segmentDuration,
            })
        })
    })
    viewer.clock.multiplier = 1
    startDroneTrackAnimation()
    startFavoritePulseAnimation()
    reorderCockpitPrimitives()
}
@@ -860,9 +1006,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 +1057,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 +1129,7 @@
    loadPartitions()
    loadAggregation()
    loadCommandPosts()
    renderSimulatedDroneTrack()
    renderSimulatedDroneTrack(props.alarmDrones)
    initDeviceClickHandler()
}
applications/drone-command/src/utils/cesium/DrawPolygon.js
@@ -715,7 +715,8 @@
        this.initHandler(viewer)
        this.isPureSpotPreview = isPurePreview
        this.startDrawing()
        let newPosition = positions.map(item => {
        const normalizedPositions = this.normalizePolygonPositions(positions)
        let newPosition = normalizedPositions.map(item => {
            return Cesium.Cartesian3.fromDegrees(Number(item.lng), Number(item.lat), Number(item?.height || 0))
        })
        // 预览航线的时候调用
@@ -728,17 +729,14 @@
        })
        // 视角飞入区域
        const newBox = boxTransformScale(
            positions.map(item => [item.lng, item.lat]),
            5
        )
        const newBox = boxTransformScale(normalizedPositions.map(item => [item.lng, item.lat]), 5)
        viewer.camera.flyTo({
            destination: Cesium.Rectangle.fromDegrees(...newBox),
            offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 0),
            duration: 0.5,
        })
        let pointList = await getPointPositionsHeight(positions, viewer)
        let pointList = await getPointPositionsHeight(normalizedPositions, viewer)
        flyVisual({ positionsData: pointList.map(item => [item.lng, item.lat, item.ASL]), viewer })
        this.drawingMode = false
@@ -748,6 +746,20 @@
        this.rebuildMidPoints()
    }
    // 规范化多边形点位:如果首尾重复,去掉末尾闭合点
    normalizePolygonPositions(positions) {
        if (!Array.isArray(positions) || positions.length < 2) return positions || []
        const first = positions[0]
        const last = positions[positions.length - 1]
        const sameLng = Number(first?.lng) === Number(last?.lng)
        const sameLat = Number(first?.lat) === Number(last?.lat)
        const sameHeight = Number(first?.height || 0) === Number(last?.height || 0)
        if (sameLng && sameLat && sameHeight) {
            return positions.slice(0, -1)
        }
        return positions
    }
    // 初始化事件处理器
    initHandler(viewer) {
        this.viewer = viewer
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -42,7 +42,9 @@
                        </el-col>
                        <el-col :span="24">
                            <div class="label">触发条件</div>
                            <div class="val">{{ formData.triggerCondition }}</div>
                            <div class="val">
                                {{ getDictLabel(formData.triggerCondition, dictObj.triggeringCondition) }}
                            </div>
                        </el-col>
                        <el-col :span="24">
                            <div class="label">响应机制</div>
@@ -102,8 +104,11 @@
                            maxlength="200" placeholder="请输入" clearable />
                    </el-form-item>
                    <el-form-item label="触发条件" prop="triggerCondition">
                        <el-input class="command-input" v-model="formData.triggerCondition"
                            maxlength="200" placeholder="请输入" clearable />
                        <el-select class="command-select" popper-class="command-select-popper"
                            v-model="formData.triggerCondition" placeholder="请选择" clearable>
                            <el-option v-for="item in dictObj.triggeringCondition" :key="item.dictKey"
                                :label="item.dictValue" :value="item.dictKey" />
                        </el-select>
                    </el-form-item>
                    <el-form-item label="管控级别" prop="controlLevel">
                        <el-select class="command-select" popper-class="command-select-popper"
@@ -410,7 +415,7 @@
// 获取设备列表
async function getDeviceList () {
    if (deviceOptions.value.length) return
    const res = await fwDeviceListApi({ isAreaSelect: 1, areaId: formData.value.id })
    const res = await fwDeviceListApi({ isAreaSelect: 1, areaId: formData.value.id, isTrack: 1 })
    deviceOptions.value = res?.data?.data ?? []
    if (dialogMode.value === 'view') {
        deviceOptions.value = deviceOptions.value.filter(item => formData.value.deviceIds.includes(item.id))
applications/drone-command/src/views/areaManage/partition/index.vue
@@ -42,7 +42,11 @@
                            {{ getDictLabel(row.areaType, dictObj.areaType) }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="triggerCondition" show-overflow-tooltip width="130" label="触发条件" />
                    <el-table-column prop="triggerCondition" show-overflow-tooltip width="130" label="触发条件">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.triggerCondition, dictObj.triggeringCondition) }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="responseMechanism" show-overflow-tooltip width="130" label="响应机制" />
                    <el-table-column prop="controlLevel" show-overflow-tooltip width="120" label="管控级别">
                        <template v-slot="{ row }">
@@ -168,13 +172,14 @@
const dictObj = ref({
    areaType: [], //区域类型
    controlLevel: [], //管控级别
    triggeringCondition: [], //触发条件
    deviceType: [],
})
provide('dictObj', dictObj)
// 获取字典
function getDictList() {
    getDictionaryByCode('areaType,controlLevel,deviceType').then(res => {
    getDictionaryByCode('areaType,controlLevel,deviceType,triggeringCondition').then(res => {
        dictObj.value = res.data.data
    })
}
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 14:49:56
 * @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/components/templateComponents/EquipmentTemplate.vue
@@ -48,7 +48,7 @@
            <div class="row">
                <div class="col">
                    <span class="label">有效范围</span>
                    {{ data.range }}
                    {{ Number(data.range).toFixed(0) }}m
                </div>
                <div class="col">
                    <span class="label">设备型号</span>
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">
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/drone-command/src/views/permissionManage/permissionRole/index.vue
@@ -36,7 +36,7 @@
                color="#1A2652"
                type="primary"
                :disabled="selectedIds.length !== 1"
                v-if="userInfo.role_name?.includes('admin')"
                v-if="permission.permissionRole_set"
                @click="handleRole"
            >
                权限设置
@@ -113,7 +113,7 @@
const dialogRef = ref(null)
const roleDialogRef = ref(null)
const store = useStore()
const userInfo = computed(() => store.getters.userInfo || {})
const permission = computed(() => store.getters.permission || {})
const route = useRoute()
async function loadList() {
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/flyingHand/FlyingHandDialog.vue
@@ -199,7 +199,8 @@
})
const formRef = ref(null)
const formData = reactive({
const initForm = () => ({
    flyerName: '',
    flyerId: '',
    flyerPhone: '',
@@ -208,7 +209,22 @@
    technicalStrength: '',
    skilledUavType: '',
    certification: [], // 证书
    regionCode: '', // 区域编码
})
const formData = ref(initForm()) // 表单数据
// const rules = {
//     flyerId: fieldRules(true),
//     flyerPhone: fieldRules(true, 50),
//     flightHours: fieldRules(true, 50),
//     technicalSkills: fieldRules(true, 50),
//     skilledTaskType: fieldRules(true),
//     projectExperience: fieldRules(true),
//     technicalStrength: fieldRules(true),
//     skilledUavType: fieldRules(true, 50),
// }
const rules = {
    flyerId: [
@@ -243,10 +259,11 @@
watch(
    () => props.flyingHandData,
    (newData) => {
        formData.value = initForm()
        if (newData) {
            Object.assign(formData, newData)
            formData.flightHours = Number(newData.flightHours)
        }
            Object.assign(formData.value, newData)
            formData.value.flightHours = Number(newData.flightHours)
        }
    },
    { deep: true, immediate: true }
)
@@ -260,8 +277,9 @@
// 处理选择飞手姓名
function handleChange(val) {
    const result = flyingHandList.value.find(item => item.id === val) || []
    formData.flyerName = result.name
    formData.certification = formData.certification.concat(result.licenseFrontFileUrl || [], result.licenseBackFileUrl || [])
    formData.value.flyerName = result.name
    formData.value.regionCode = result.regionCode
    formData.value.certification = formData.value.certification.concat(result.licenseFrontFileUrl || [], result.licenseBackFileUrl || [])
}
@@ -270,7 +288,7 @@
function resetForm() {
    formRef.value?.resetFields()
    // 重置为默认值
    Object.assign(formData, {
    Object.assign(formData.value, {
        flyerName: '',
        flyerPhone: '',
        flightHours: 0,
@@ -290,10 +308,10 @@
    try {
        if (props.dialogType === 'add') {
            await submitFlyingHand(formData)
            await submitFlyingHand(formData.value)
            ElMessage.success('添加成功')
        } else if (props.dialogType === 'edit') {
            await submitFlyingHand(formData)
            await submitFlyingHand(formData.value)
            ElMessage.success('编辑成功')
        }
        emit('confirm')
applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue
@@ -45,7 +45,7 @@
                <el-table class="gd-table" :data="list" @selection-change="handleSelectionChange">
                    <el-table-column label="序号" width="70" align="center">
                        <template #default="{ $index }">
                            {{ formatNumber((searchParams.current - 1) * searchParams.size + $index + 1) }}
                            {{ $index + 1 }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="caseName" show-overflow-tooltip label="案例名称" />
applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/index.vue
@@ -45,7 +45,7 @@
                <el-table class="gd-table" :data="list">
                    <el-table-column label="序号" width="70">
                        <template v-slot="{ $index }">
                            {{ String($index + 1).padStart(2, '0') }}
                            {{ $index + 1 }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="title" show-overflow-tooltip label="标题" />
@@ -63,7 +63,7 @@
                    <el-table-column prop="otherObjectionDetail" show-overflow-tooltip label="其他异议详情" />
                    <el-table-column prop="submitter" show-overflow-tooltip label="问题提交人" />
                    <el-table-column prop="submitterContact" show-overflow-tooltip width="120" label="提交人联系方式" />
                    <el-table-column prop="submitUnitName" show-overflow-tooltip label="提交单位" />
                    <el-table-column prop="createDeptName" show-overflow-tooltip label="提交单位" />
                    <el-table-column prop="handleUnit" show-overflow-tooltip label="处理单位" />
                    <el-table-column prop="objectionDesc" show-overflow-tooltip label="异议描述" />
                    <el-table-column prop="objectionBasis" show-overflow-tooltip label="异议依据" />
applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue
@@ -39,7 +39,7 @@
                <el-table class="gd-table" :data="list">
                    <el-table-column label="序号" width="80">
                        <template v-slot="{ $index }">
                            {{ ((searchParams.current - 1) * searchParams.size + $index + 1).toString().padStart(2, '0') }}
                            {{ $index + 1 }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="title" show-overflow-tooltip label="标题" />
applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/index.vue
@@ -44,7 +44,7 @@
                <el-table class="gd-table" :data="list">
                    <el-table-column label="序号" width="70" align="center">
                        <template #default="scope">
                            {{ ((searchParams.current - 1) * searchParams.size + scope.$index + 1).toString().padStart(2, '0') }}
                            {{ scope.$index + 1 }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="demandName" show-overflow-tooltip label="需求名称" />
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>
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/FormDiaLog.vue
@@ -48,6 +48,7 @@
              popper-class="gd-tree-select-popper"
              v-model="formData.areaCode"
              node-key="id"
              :default-expanded-keys="expandedKeys"
              :props="{
                value: 'id',
                label: 'name',
@@ -136,6 +137,8 @@
const submitting = ref(false) // 提交中
const dialogReadonly = computed(() => dialogMode.value === 'view')
const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
const expandedKeys = ref([]) // 树形选择器展开的节点
const regionNodeCache = ref(new Map()) // 缓存区域节点及其父节点关系
const rules = {
  deptName: fieldRules(true),
@@ -173,6 +176,62 @@
    data.areaCode = String(data.areaCode)
  }
  formData.value = data
  // 如果是编辑模式,获取区域节点的路径以展开树形选择器
  if (dialogMode.value === 'edit' && data.areaCode) {
    await loadRegionPath(data.areaCode)
  }
}
// 加载区域节点路径
async function loadRegionPath(areaCode) {
  try {
    const path = []
    const queue = [{ code: '360800000000', currentPath: ['360800000000'] }]
    let found = false
    // 广度优先搜索,逐层加载节点直到找到目标节点
    while (queue.length > 0 && !found) {
      const { code, currentPath } = queue.shift()
      // 获取当前节点的子节点
      const res = await regionLazyTreeApi({ parentCode: code })
      const nodes = res?.data?.data || []
      for (const node of nodes) {
        const nodeId = node.id || node.value
        // 缓存节点及其父节点关系
        regionNodeCache.value.set(nodeId, {
          id: nodeId,
          name: node.name || node.title || node.label,
          parentId: code,
          hasChildren: node.hasChildren || false
        })
        // 检查是否找到目标节点
        if (nodeId === areaCode) {
          path.push(...currentPath, nodeId)
          found = true
          break
        }
        // 如果有子节点,加入队列继续搜索
        if (node.hasChildren) {
          queue.push({
            code: nodeId,
            currentPath: [...currentPath, nodeId]
          })
        }
      }
    }
    if (path.length > 0) {
      expandedKeys.value = path
    }
  } catch (error) {
    console.error('加载区域路径失败:', error)
  }
}
// 打开弹框
@@ -186,14 +245,25 @@
// 懒加载获取区域节点数据
async function loadRegionNode(node, resolve) {
  try {
    const parentCode = node.data?.id  || '360800000000'
    const res = await regionLazyTreeApi({ parentCode })
    // 初始化加载时传递 code 参数,加载子节点时传递 parentCode 参数
    const params = node.data?.id ? { parentCode: node.data.id } : { code: '360800000000' }
    const parentCode = node.data?.id || '360800000000'
    const res = await regionLazyTreeApi(params)
    const nodes = res?.data?.data || []
    
    // 处理返回的节点数据
    const processedNodes = nodes.map(item => {
      const nodeId = item.id || item.value
      // 缓存节点及其父节点关系
      regionNodeCache.value.set(nodeId, {
        id: nodeId,
        name: item.name || item.title || item.label,
        parentId: parentCode,
        hasChildren: item.hasChildren || false
      })
      return {
        id: item.id || item.value,
        id: nodeId,
        name: item.name || item.title || item.label,
        hasChildren: item.hasChildren || false
      }
@@ -205,30 +275,10 @@
    resolve([])
  }
}
// 获取初始区域数据(根节点)
async function getRegionList() {
  try {
    // 懒加载模式下,只需要初始化根节点
    const res = await regionLazyTreeApi({ parentCode: '360800000000' })
    const nodes = res?.data?.data || []
    // 处理根节点数据
    regionOptions.value = nodes.map(item => {
      return {
        id: item.id || item.value,
        name: item.name || item.title || item.label,
        hasChildren: item.hasChildren || false
      }
    })
  } catch (error) {
    console.error('获取区域数据失败:', error)
    ElMessage.error('获取区域数据失败')
  }
}
defineExpose({ open })
onMounted(() => {
  getRegionList()
})
</script>
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/index.vue
@@ -56,7 +56,7 @@
        <el-table class="gd-table" :data="list">
          <el-table-column label="序号" width="80">
            <template v-slot="{ $index }">
              {{ ((searchParams.current - 1) * searchParams.size + $index + 1).toString().padStart(2, '0') }}
              {{ $index + 1 }}
            </template>
          </el-table-column>
          <el-table-column prop="deptCode" show-overflow-tooltip label="机构编码" />
@@ -68,7 +68,7 @@
             {{ getDictLabel(row.status, dictObj.institutionStatus) }}
            </template>
          </el-table-column>
          <el-table-column label="操作" class-name="operation-btns" width="310">
          <el-table-column label="操作" class-name="operation-btns" width="400">
            <template v-slot="{ row }">
              <el-link type="primary" @click="openForm('view', row)">查看</el-link>
              <el-link type="primary"  @click="openForm('edit', row)">编辑</el-link>
@@ -197,8 +197,9 @@
async function loadRegionNode(node, resolve) {
  try {
    const parentCode = node.data?.id || '360800000000'
    const res = await regionLazyTreeApi({ parentCode })
    // 初始化加载时传递 code 参数,加载子节点时传递 parentCode 参数
    const params = node.data?.id ? { parentCode: node.data.id } : { code: '360800000000' }
    const res = await regionLazyTreeApi(params)
    const nodes = res?.data?.data || []
    
    // 处理返回的节点数据
@@ -217,25 +218,7 @@
  }
}
// 获取初始区域数据(根节点)
async function getRegionList() {
  try {
    // 懒加载模式下,只需要初始化根节点
    const res = await regionLazyTreeApi({ parentCode: '360800000000' })
    const nodes = res?.data?.data || []
    // 处理根节点数据
    regionOptions.value = nodes.map(item => {
      return {
        id: item.id || item.value,
        name: item.name || item.title || item.label,
        hasChildren: item.hasChildren || false
      }
    })
  } catch (error) {
    console.error('获取区域数据失败:', error)
  }
}
// 获取列表
async function getList() {
@@ -384,7 +367,6 @@
onMounted(() => {
  getDictList()
  getRegionList()
  getList()
})
</script>
applications/task-work-order/src/views/orderView/organizational/zoningManagement/FormDiaLog.vue
New file
@@ -0,0 +1,316 @@
<template>
  <el-dialog
    class="gd-dialog"
    v-model="visible"
    :title="titleEnum[dialogMode]"
    @closed="visible = false"
    destroy-on-close
    :close-on-click-modal="false"
  >
    <el-row class="detail-row-view" v-if="dialogReadonly">
      <el-col :span="12">
        <div class="label">区划代码</div>
        <div class="val">{{ formData.code }}</div>
      </el-col>
      <el-col :span="12">
        <div class="label">区划名称</div>
        <div class="val">{{ formData.name }}</div>
      </el-col>
      <el-col :span="12">
        <div class="label">上级区划</div>
        <div class="val">{{ formData.parentName }}</div>
      </el-col>
      <el-col :span="12">
        <div class="label">区划级别</div>
        <div class="val"> {{ getDictLabel(formData.regionLevel, dictObj.divisionLevel) }}</div>
      </el-col>
      <el-col :span="12">
        <div class="label">区划排序</div>
        <div class="val">{{ formData.sort }}</div>
      </el-col>
      <el-col :span="12">
        <div class="label">区划状态</div>
        <div class="val"> {{ getDictLabel(formData.status, dictObj.zoningStatus) }}</div>
      </el-col>
    </el-row>
    <el-form
      class="gd-dialog-form"
      v-else
      ref="formRef"
      :model="formData"
      :rules="rules"
      :disabled="dialogReadonly"
      label-width="140px"
    >
      <el-row>
        <el-col :span="12">
          <el-form-item label="区划代码" prop="code">
            <el-input class="gd-input" v-model="formData.code" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="区划名称" prop="name">
            <el-input class="gd-input" v-model="formData.name" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="上级区划" prop="parentCode">
            <el-tree-select
              class="gd-select"
              popper-class="gd-tree-select-popper"
              v-model="formData.parentCode"
              node-key="id"
              :default-expanded-keys="expandedKeys"
              :props="{
                value: 'id',
                label: 'name',
                children: 'children'
              }"
              placeholder="请选择"
              clearable
              filterable
              :check-strictly="true"
              lazy
              :load="loadRegionNode"
              :render-after-expand="false"
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="区划级别" prop="regionLevel">
            <el-select
              class="gd-select"
              popper-class="gd-select-popper"
              v-model="formData.regionLevel"
              placeholder="请选择"
              clearable
            >
              <el-option v-for="item in dictObj.divisionLevel" :key="item.dictKey"
                                :label="item.dictValue"
                                :value="item.dictKey" />
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="区划排序" prop="sort">
            <el-input class="gd-input" v-model="formData.sort" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="区划状态" prop="status">
            <el-select
              class="gd-select"
              popper-class="gd-select-popper"
              v-model="formData.status"
              placeholder="请选择"
              clearable
            >
              <el-option v-for="item in dictObj.zoningStatus" :key="item.dictKey"
                                :label="item.dictValue"
                                :value="item.dictKey" />
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <template #footer v-if="!dialogReadonly">
      <el-button color="#F2F3F5" @click="visible = false">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
      <el-button
        class="save-btn"
        color="#4C34FF"
        :loading="submitting"
        :disabled="submitting"
        @click="handleSubmit"
      >
        保存
      </el-button>
    </template>
  </el-dialog>
</template>
<script setup>
import { getDictLabel } from '@ztzf/utils'
import { computed, ref, inject, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { fieldRules } from '@ztzf/utils'
import {
  zoningSubmitApi,
  zoningDetailApi,
  regionLazyTreeApi
} from './zoningApi'
// 初始化表单数据
const initForm = () => ({
  code: '',
  name: '',
  parentCode: '',
  regionLevel: '',
  sort: '',
  status: '',
  id: null,
  parentName: ''
})
const dictObj = inject('dictObj')
const regionOptions = inject('regionOptions')
const emit = defineEmits(['success'])
const formRef = ref(null) // 表单实例
const formData = ref(initForm()) // 表单数据
const visible = defineModel() // 弹框显隐
const dialogMode = ref('add') // 弹框模式
const submitting = ref(false) // 提交中
const dialogReadonly = computed(() => dialogMode.value === 'view')
const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
const expandedKeys = ref([]) // 树形选择器展开的节点
const regionNodeCache = ref(new Map()) // 缓存区域节点及其父节点关系
const rules = {
  code: fieldRules(true),
  name: fieldRules(true),
  parentCode: fieldRules(true),
  regionLevel: fieldRules(true),
  sort: fieldRules(true),
  status: fieldRules(true),
}
// 提交新增/编辑
async function handleSubmit() {
  const isValid = await formRef.value?.validate().catch(() => false)
  if (!isValid) return
  submitting.value = true
  try {
    await zoningSubmitApi(formData.value)
    ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
    visible.value = false
    emit('success')
  } finally {
    submitting.value = false
  }
}
// 加载详情
async function loadDetail() {
  if (!formData.value.id) return
  const res = await zoningDetailApi({ id: formData.value.id })
  const data = res?.data?.data ?? {}
  // 将 status 和 regionLevel 转换为字符串类型,与字典中的 dictKey 类型保持一致
  if (data.status !== undefined) {
    data.status = String(data.status)
  }
  if (data.regionLevel !== undefined) {
    data.regionLevel = String(data.regionLevel)
  }
  // 将 parentCode 转换为字符串类型,与 regionOptions 中的 value 类型保持一致
  if (data.parentCode !== undefined) {
    data.parentCode = String(data.parentCode)
  }
  formData.value = data
  // 如果是编辑模式,获取区域节点的路径以展开树形选择器
  if (dialogMode.value === 'edit' && data.parentCode) {
    await loadRegionPath(data.parentCode)
  }
}
// 加载区域节点路径
async function loadRegionPath(parentCode) {
  try {
    const path = []
    const queue = [{ code: '360800000000', currentPath: ['360800000000'] }]
    let found = false
    // 广度优先搜索,逐层加载节点直到找到目标节点
    while (queue.length > 0 && !found) {
      const { code, currentPath } = queue.shift()
      // 获取当前节点的子节点
      const res = await regionLazyTreeApi({ parentCode: code })
      const nodes = res?.data?.data || []
      for (const node of nodes) {
        const nodeId = node.id || node.value
        // 缓存节点及其父节点关系
        regionNodeCache.value.set(nodeId, {
          id: nodeId,
          name: node.name || node.title || node.label,
          parentId: code,
          hasChildren: node.hasChildren || false
        })
        // 检查是否找到目标节点
        if (nodeId === parentCode) {
          path.push(...currentPath, nodeId)
          found = true
          break
        }
        // 如果有子节点,加入队列继续搜索
        if (node.hasChildren) {
          queue.push({
            code: nodeId,
            currentPath: [...currentPath, nodeId]
          })
        }
      }
    }
    if (path.length > 0) {
      expandedKeys.value = path
    }
  } catch (error) {
    console.error('加载区域路径失败:', error)
  }
}
// 打开弹框
async function open({ mode = 'add', row } = {}) {
  dialogMode.value = mode
  formData.value = dialogMode.value === 'add' ? initForm() : row
  if (dialogMode.value !== 'add') {
    await loadDetail()
  }
}
// 懒加载获取区域节点数据
async function loadRegionNode(node, resolve) {
  try {
    // 初始化加载时传递 code 参数,加载子节点时传递 parentCode 参数
    const params = node.data?.id ? { parentCode: node.data.id } : { code: '360800000000' }
    const parentCode = node.data?.id || '360800000000'
    const res = await regionLazyTreeApi(params)
    const nodes = res?.data?.data || []
    // 处理返回的节点数据
    const processedNodes = nodes.map(item => {
      const nodeId = item.id || item.value
      // 缓存节点及其父节点关系
      regionNodeCache.value.set(nodeId, {
        id: nodeId,
        name: item.name || item.title || item.label,
        parentId: parentCode,
        hasChildren: item.hasChildren || false
      })
      return {
        id: nodeId,
        name: item.name || item.title || item.label,
        hasChildren: item.hasChildren || false
      }
    })
    resolve(processedNodes)
  } catch (error) {
    console.error('获取区域数据失败:', error)
    resolve([])
  }
}
defineExpose({ open })
onMounted(() => {
})
</script>
<style scoped lang="scss">
</style>
applications/task-work-order/src/views/orderView/organizational/zoningManagement/index.vue
@@ -1,19 +1,336 @@
<template>
 <div>
    区划管理
 </div>
  <basic-container>
    <el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form">
      <el-form-item label="上级区划名称" prop="parentName">
        <el-input
          class="gd-input gray"
          v-model="searchParams.parentName"
          placeholder="请输入"
          clearable
          @clear="handleSearch"
        />
      </el-form-item>
      <el-form-item label="区划名称" prop="districtName">
        <el-input
          class="gd-input gray"
          v-model="searchParams.districtName"
          placeholder="请输入"
          clearable
          @clear="handleSearch"
        />
      </el-form-item>
      <el-form-item label="区划代码" prop="cityCode">
        <el-input
          class="gd-input gray"
          v-model="searchParams.cityCode"
          placeholder="请输入"
          clearable
          @clear="handleSearch"
        />
      </el-form-item>
      <el-form-item class="gd-search-actions">
        <el-button :icon="RefreshRight" @click="resetForm"></el-button>
        <el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button>
      </el-form-item>
    </el-form>
    <div class="gd-table-toolbar">
      <el-button :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">新增区划</el-button>
      <el-button :icon="Upload" @click="handleImport">导入区划</el-button>
      <el-button :icon="Download" @click="handleExport">导出区划</el-button>
    </div>
    <div class="gd-table-container" v-loading="loading">
      <div class="gd-table-content gd-table-content-bg">
        <el-table class="gd-table" :data="list">
          <el-table-column label="序号" width="80">
            <template v-slot="{ $index }">
               {{ $index + 1 }}
            </template>
          </el-table-column>
          <el-table-column prop="code" show-overflow-tooltip label="区划代码" />
          <el-table-column prop="name" show-overflow-tooltip label="区划名称" />
          <el-table-column prop="parentName" show-overflow-tooltip label="上级区划名称" />
          <el-table-column prop="regionLevel" show-overflow-tooltip label="区划级别">
            <template v-slot="{ row }">
              {{ getDictLabel(row.regionLevel, dictObj.divisionLevel) }}
            </template>
          </el-table-column>
          <el-table-column prop="sort" show-overflow-tooltip label="排序" />
          <el-table-column prop="status" show-overflow-tooltip label="区划状态">
            <template v-slot="{ row }">
              {{ getDictLabel(row.status, dictObj.zoningStatus) }}
            </template>
          </el-table-column>
          <el-table-column label="操作" class-name="operation-btns" width="400">
            <template v-slot="{ row }">
              <el-link type="primary" @click="openForm('view', row)">查看</el-link>
              <el-link type="primary"  @click="openForm('edit', row)">编辑</el-link>
              <el-link type="primary" @click="handleDelete(row)">删除</el-link>
              <el-link type="primary" @click="toggleStatus(row)">{{row.status === 1 ? '禁用' : '启用'}}</el-link>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <div class="gd-pagination-parent">
        <el-pagination
          popper-class="gd-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>
    <FormDiaLog ref="dialogRef" @success="getList" v-if="dialogVisible" v-model="dialogVisible" />
    <!-- 导入区划弹框 -->
    <el-dialog
      class="gd-dialog"
      append-to-body
      v-model="isShowImportView"
      title="导入区划"
      :width="pxToRem(600)"
      :close-on-click-modal="false"
      :destroy-on-close="true"
      @close="handleImportClose"
    >
      <el-form class="gd-dialog-form" ref="importFormRef" :model="importParams" :rules="importRules" label-width="140px">
        <el-form-item label="上传文件" prop="file">
          {{ importFileName }}
          <el-upload class="avatar-uploader" action="" :show-file-list="false" :before-upload="onImportFileBefore">
            <el-button size="small" type="primary">点击上传</el-button>
          </el-upload>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button color="#F2F3F5" @click="isShowImportView = false">取消</el-button>
        <el-button
          class=""
          color="#4C34FF"
          :loading="importSubmitting"
          :disabled="importSubmitting"
          @click="submitImport(importFormRef)"
        >
          导入
        </el-button>
      </template>
    </el-dialog>
  </basic-container>
</template>
<script setup >
/**
 * @description index
 * @date 2026-01-26 (周一) 10:50:43
 */
defineOptions({
 name: 'index'
<script setup>
import { Search, RefreshRight, Plus, Upload, Download } from '@element-plus/icons-vue'
import { onMounted, ref, provide, nextTick } from 'vue'
import { ElMessage, ElMessageBox, ElUpload } from 'element-plus'
import FormDiaLog from './FormDiaLog.vue'
import {
  zoningPageApi,
  zoningRemoveApi,
  zoningImportApi,
  zoningExportApi,
  regionLazyTreeApi,
  zoningSubmitApi
} from './zoningApi'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getDictLabel } from '@ztzf/utils'
import { pxToRem } from '@/utils/rem'
// 初始化查询参数
const initSearchParams = () => ({
  parentName: '', // 上级区划名称
  districtName: '', // 区划名称
  cityCode: '', // 区划代码
  current: 1, // 当前页
  size: 10, // 每页大小
})
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const queryParamsRef = ref(null) // 查询表单实例
const dialogRef = ref(null) // 弹框实例
const dialogVisible = ref(false)
const regionOptions = ref([]) // 区域选项
const dictObj = ref({}) // 字典对象
// 导入区划相关
const isShowImportView = ref(false)
const importFileName = ref('')
const importParams = ref({
  isCovered: '0',
  file: '',
})
const importFormRef = ref()
const importSubmitting = ref(false)
const importRules = ref({
  file: [{ required: true, message: '请上传文件', trigger: ['change'] }],
})
provide('regionOptions', regionOptions)
provide('dictObj', dictObj)
// 获取字典列表
function getDictList() {
    return getDictionaryByCode('zoningStatus,divisionLevel').then(res => {
        dictObj.value = res.data.data
    })
}
// 获取列表
async function getList() {
  loading.value = true
  try {
    const res = await zoningPageApi(searchParams.value)
    list.value = res?.data?.data?.records ?? []
    total.value = res?.data?.data?.total ?? 0
  } finally {
    loading.value = false
  }
}
// 查询
function handleSearch() {
  searchParams.value.current = 1
  getList()
}
// 重置查询
function resetForm() {
  queryParamsRef.value?.resetFields()
  searchParams.value.current = 1
  getList()
}
// 新增/编辑/查看 弹框
function openForm(mode, row) {
  dialogVisible.value = true
  nextTick(() => {
    dialogRef.value?.open({ mode, row })
  })
}
// 删除
async function handleDelete(row) {
  await ElMessageBox.confirm('确认删除该条记录吗?', '提示', {
    type: 'warning',
    customClass: 'gd-confirm-custom',
    confirmButtonClass: 'gd-confirm-button',
    cancelButtonClass: 'gd-confirm-cancel-button',
  })
  await zoningRemoveApi({ id: row.id })
  ElMessage.success('删除成功')
  getList()
}
// 切换区划状态
async function toggleStatus(row) {
  // 计算目标状态:当前为1则切换为0,当前为0则切换为1
  const targetStatus = row.status === 1 ? 0 : 1
  const actionText = targetStatus === 0 ? '禁用' : '启用'
  await ElMessageBox.confirm(`确认${actionText}该区划吗?`, '提示', {
    type: 'warning',
    customClass: 'gd-confirm-custom',
    confirmButtonClass: 'gd-confirm-button',
    cancelButtonClass: 'gd-confirm-cancel-button',
  })
  try {
    await zoningSubmitApi({ ...row, status: targetStatus })
    ElMessage.success(`${actionText}成功`)
    getList()
  } catch (error) {
    ElMessage.error(`${actionText}失败`)
  }
}
// 导入区划
function handleImport() {
  isShowImportView.value = true
}
// 导入区划弹框关闭
function handleImportClose() {
  importFileName.value = ''
  importParams.value = {
    isCovered: '0',
    file: '',
  }
}
// 上传文件前处理
function onImportFileBefore(file) {
  // 检查文件类型
  const allowedTypes = ['.xlsx', '.xls']
  const fileExtension = file.name.substring(file.name.lastIndexOf('.')).toLowerCase()
  if (!allowedTypes.includes(fileExtension)) {
    ElMessage.error('请上传Excel文件(.xlsx 或 .xls)')
    return false
  }
  let data = new FormData()
  data.append('file', file)
  zoningImportApi(data, { isCovered: importParams.value.isCovered }).then(res => {
    if (res.data.code === 200) {
      ElMessage.success('导入成功')
      // 保存文件名
      importFileName.value = file.name
      importParams.value.file = file
      isShowImportView.value = false
      getList()
    } else {
      ElMessage.error(res.msg || '导入失败')
    }
  })
  return false // 阻止组件的默认上传行为
}
// 提交导入
async function submitImport(formValidate) {
  if (!formValidate) return
  await formValidate.validate(async (valid, fields) => {
    if (valid) {
      importSubmitting.value = true
      try {
        if (!importParams.value.file) {
          ElMessage.error('请先上传文件')
          return
        }
        // 触发上传操作
        await onImportFileBefore(importParams.value.file)
      } finally {
        importSubmitting.value = false
      }
    }
  })
}
// 导出区划
function handleExport() {
  zoningExportApi(searchParams.value).then(res => {
    const blob = new Blob([res.data])
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = `区划列表${new Date().getTime()}.xlsx`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(url)
  })
}
onMounted(() => {
  getDictList()
  getList()
})
</script>
<style scoped lang="scss">
applications/task-work-order/src/views/orderView/organizational/zoningManagement/zoningApi.js
New file
@@ -0,0 +1,66 @@
import request from '@/axios'
// 列表
export const zoningPageApi = params => {
  return request({
    url: `/blade-system/region/page`,
    method: 'get',
    params: { ...params },
  })
}
// 新增或编辑
export const zoningSubmitApi = data => {
  return request({
    url: `/blade-system/region/submit`,
    method: 'post',
    data,
  })
}
// 删除
export const zoningRemoveApi = params => {
  return request({
    url: `/blade-system/region/remove`,
    method: 'post',
    params,
  })
}
// 详情
export const zoningDetailApi = params => {
  return request({
    url: `/blade-system/region/detail`,
    method: 'get',
    params,
  })
}
// 导入区划
export const zoningImportApi = (data,params) => {
  return request({
    url: `/blade-system/region/import-region`,
    method: 'post',
    data,
    params
  })
}
// 导出区划
export const zoningExportApi = params => {
  return request({
    url: `/blade-system/region/export-region`,
    method: 'get',
    params,
    responseType: 'blob',
  })
}
// 懒加载列表
export const regionLazyTreeApi = params => {
  return request({
    url: `/blade-system/region/lazy-tree`,
    method: 'get',
    params,
  })
}
uniapps/work-app/src/subPackages/workDetail/index.vue
@@ -158,7 +158,11 @@
    uni.showToast({
      title: '退回成功',
      icon: 'success',
      duration: 1500
    })
    setTimeout(() => {
      uni.navigateBack()
    }, 1500)
  })
}
@@ -169,7 +173,11 @@
    uni.showToast({
      title: '确认成功',
      icon: 'success',
      duration: 1500
    })
    setTimeout(() => {
      uni.navigateBack()
    }, 1500)
  })
}
// 分享模态框状态
uniapps/work-wx/src/pages/equipmentManagement/index.vue
@@ -132,7 +132,7 @@
  }
  .card {
    display: flex;
    padding: 44rpx;
    padding: 40rpx 0;
    border-bottom: 1px solid #EBEEF3;
    .left {
      width: 70px;
@@ -157,6 +157,10 @@
        color: #86909C;
        margin-bottom: 14rpx;
      }
      // 最后一个txt 不添加margin-bottom
      .txt:last-child {
        margin-bottom: 0;
      }
    }
  }
  // 第一个card 不添加下边框
uniapps/work-wx/src/pages/legalPolicy/index.vue
@@ -1,7 +1,7 @@
<template>
  <view class="legalPolicy">
    <!-- <u-navbar title="法律政策" @rightClick="rightClick" :autoBack="true"></u-navbar> -->
    <u-search :bgColor="'white'" v-model="noticeTitle" :show-action="true" actionText="搜索" :animation="true" @custom="handleSearch"></u-search>
    <u-search :bgColor="'white'" v-model="formParams.noticeTitle" :show-action="true" actionText="搜索" :animation="true" @custom="handleSearch"></u-search>
    <view class="tabs-container">
      <u-tabs :list="tabs" lineColor="#265CFD" @click="tabClick"></u-tabs>
    </view>
@@ -27,8 +27,6 @@
  import docSvg from '@/static/images/doc.png'
import { ref, onMounted } from 'vue'
const noticeTitle = ref('')
const tabs = ref([
  {
    name: '法律法规',
@@ -42,7 +40,7 @@
  current: 1,
  size: 10,
  noticeType: 2,
  noticeTitle: noticeTitle.value,
  noticeTitle: '',
  isAsc: true
})
@@ -62,7 +60,6 @@
const handleSearch = () => {
  // 搜索时重置为第一页
  formParams.value.current = 1
  formParams.value.noticeTitle = formParams.value.noticeTitle.trim()
  getSysNoticeList()
}
// 触底加载
uniapps/work-wx/src/subPackages/deviceRegistration/add.vue
@@ -649,6 +649,7 @@
    :deep(.u-button--primary) {
        background-color: #265CFD;
        border-color: #265CFD;
    }
    :deep(.u-form) {
uniapps/work-wx/src/subPackages/deviceRegistration/details.vue
@@ -9,7 +9,7 @@
                :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(出厂序列号)"
@@ -18,7 +18,7 @@
                :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="飞行器厂商"
@@ -28,9 +28,8 @@
                ref="item2"
        >
            <u-input
                v-model="formParams.manufacturer"
                disabled
                disabledColor="#ffffff"
                v-model="formParams.manufacturerText"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -41,7 +40,7 @@
                :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="是否改装"
@@ -52,8 +51,7 @@
        >
            <u-input
                v-model="formParams.isModified"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -66,8 +64,7 @@
        >
            <u-input
                v-model="formParams.type"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -79,7 +76,8 @@
                ref="item1"
        >
            <u-input
                    v-model="formParams.holder"
                    v-model="formParams.holder"
                    readonly
                    border="none"
            ></u-input>
        </u-form-item>
@@ -90,7 +88,7 @@
                :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="有无保险"
@@ -101,8 +99,7 @@
        >
            <u-input
                v-model="formParams.hasInsurance"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -115,8 +112,7 @@
        >
            <u-input
                v-model="formParams.region"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -128,7 +124,8 @@
                ref="item1"
        >
            <u-input
                    v-model="formParams.caacRegistrationCode"
                    v-model="formParams.caacRegistrationCode"
                    readonly
                    border="none"
            ></u-input>
        </u-form-item>
@@ -140,7 +137,8 @@
                ref="item1"
        >
            <u-input
                    v-model="formParams.deviceImage"
                    v-model="formParams.deviceImage"
                    readonly
                    border="none"
            ></u-input>
        </u-form-item>
@@ -152,7 +150,8 @@
                ref="item1"
        >
            <u-input
                    v-model="formParams.remarks"
                    v-model="formParams.remarks"
                    readonly
                    border="none"
            ></u-input>
        </u-form-item>
@@ -314,11 +313,13 @@
        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) => {
@@ -326,16 +327,17 @@
        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
uniapps/work-wx/src/subPackages/flightApplication/add.vue
@@ -329,20 +329,22 @@
        @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> -->
    <!-- 飞行器 -->
@@ -541,6 +543,7 @@
const isShowStartTime = ref(false)
// 是否显示结束时间选择器
const isShowEndTime = ref(false)
const minDate = ref(Date.now()); // 设置最小日期为当前时间戳
const startTime = ref(Date.now()); // 设置默认值为当前时间戳
const endTime = ref(Date.now()); // 设置默认值为当前时间戳
@@ -714,6 +717,8 @@
        duration: 2000
      })
    }
  }).finally(() => {
    isSubmitDisabled.value = false
  })
}
onMounted(() => {
uniapps/work-wx/src/subPackages/flightApplication/details.vue
@@ -10,7 +10,7 @@
                :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="任务类型"
@@ -21,8 +21,7 @@
        >
            <u-input
                v-model="formParams.flightTaskType"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -35,8 +34,7 @@
        >
            <u-input
                v-model="formParams.flightRule"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -49,8 +47,7 @@
        >
            <u-input
                v-model="formParams.flightMode"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -61,7 +58,7 @@
                :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="飞行活动结束日期"
@@ -70,7 +67,7 @@
                :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
@@ -82,8 +79,7 @@
        >
            <u-input
                v-model="formParams.planType"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -94,7 +90,7 @@
                :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="联系电话"
@@ -103,7 +99,7 @@
                :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="承办人"
@@ -112,7 +108,7 @@
                :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="承办人联系电话"
@@ -121,7 +117,7 @@
                :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="临时空域批件"
@@ -140,7 +136,8 @@
                ref="item1"
        >
            <u-input
                    v-model="formParams.weatherCondition"
                    v-model="formParams.weatherCondition"
                    readonly
                    border="none"
            ></u-input>
        </u-form-item>
@@ -154,8 +151,7 @@
        >
            <u-input
                v-model="formParams.aircraftId"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -168,8 +164,7 @@
        >
            <u-input
                v-model="formParams.pilotId"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -183,8 +178,7 @@
        >
            <u-input
                v-model="formParams.airspaceId"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -197,8 +191,7 @@
        >
            <u-input
                v-model="formParams.routeId"
                disabled
                disabledColor="#ffffff"
                readonly
                border="none"
            ></u-input>
        </u-form-item>
@@ -317,16 +310,19 @@
     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(() => {
uniapps/work-wx/src/subPackages/flightApplication/index.vue
@@ -187,7 +187,7 @@
                flex: 1;
                max-width: 350rpx;
                height: 166rpx;
                background: rgba(251,253,255,0.57);
                background: rgba(251,253,255,0.8);
                box-shadow: inset 0px 0px 7px 0px rgba(64,156,230,0.14), 0px 1px 3px 0px rgba(35,49,96,0.11);
                border-radius: 8px 8px 8px 8px;
                border: 1px solid #fff;