吉安感知网项目-前端
罗广辉
2026-01-27 8a7fb9a519cae32d38ba66058d243aa7e6b6aeb2
Merge remote-tracking branch 'origin/master'
10 files modified
3 files renamed
3 files added
827 ■■■■■ changed files
applications/drone-command/src/api/dataCockpit/index.js 5 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/components/map-container/device-map-container.vue 122 ●●●● patch | view | raw | blame | history
applications/drone-command/src/components/map-container/device-map-materials.js 40 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/deviceStock/index.vue 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/LegendBar.vue 8 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue 28 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue 6 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue 6 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/flyingHand/FlyingHandDialog.vue 278 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/flyingHand/flyingHandApi.js 37 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/flyingHand/index.vue 222 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/FormDiaLog.vue 4 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/agenciesApi.js 66 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/index.vue patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/zoningManagement/index.vue patch | view | raw | blame | history
applications/drone-command/src/api/dataCockpit/index.js
@@ -2,7 +2,7 @@
 * @Author       : yuan
 * @Date         : 2026-01-09 11:14:04
 * @LastEditors  : yuan
 * @LastEditTime : 2026-01-26 09:38:16
 * @LastEditTime : 2026-01-26 19:02:07
 * @FilePath     : \applications\drone-command\src\api\dataCockpit\index.js
 * @Description  : 
 * Copyright 2026 OBKoro1, All Rights Reserved. 
@@ -78,7 +78,8 @@
export const cockpitAggregationApi = (params) => {
    return request({
        url: '/drone-fw/device/fwDevice/statisticalDeviceOut',
        method: 'get'
        method: 'get',
        params
    })
}
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -2,8 +2,9 @@
    <div class="map-shell">
        <CommonCesiumMap ref="mapRef" class="command-cesium map-container" :dom-id="props.containerId" :active="true"
            :flat-mode="false" :terrain="true" :layer-mode="4" :contour="false" :boundary="false"
            :show-admin-boundary="true" :zoom-to-boundary="true" :enable-stage-emit="true" :cluster-height="100000"
            @ready="handleMapReady" @stage-change="handleStageChange" />
            :show-admin-boundary="true" :zoom-to-boundary="true" :enable-stage-emit="true"
            :cluster-height="CLUSTER_HEIGHT" :detail-height="DETAIL_HEIGHT" @ready="handleMapReady"
            @stage-change="handleStageChange" />
        <div v-if="props.showLayerControl" class="layer-control-root" :class="{ collapsed: props.leftCollapsed }">
            <div class="layer-control-wrap" ref="layerWrapRef">
                <div class="layer-control" @click="toggleLayerPanel">
@@ -63,6 +64,9 @@
    createRadialGradientMaterial,
    getTexturedVertexFormat,
} from './device-map-materials'
const CLUSTER_HEIGHT = 100000
const DETAIL_HEIGHT = 10000
const props = defineProps({
    onlineDevices: {
@@ -157,6 +161,14 @@
    const latitude = Number(latitudeRaw)
    if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) return null
    return { longitude, latitude }
}
const getDeviceRange = item => {
    if (!item) return null
    const rawRange = item.effectiveRangeKm ?? item.range ?? item.coverRadiusM
    const range = Number(rawRange)
    if (!Number.isFinite(range) || range <= 0) return null
    return range
}
const ensureCockpitPrimitiveLayer = () => {
@@ -305,6 +317,20 @@
    if (aggregationSource) aggregationSource.show = visible
}
const setDroneVisibility = visible => {
    if (droneTrackBillboardCollection) droneTrackBillboardCollection.show = visible
    if (droneTrackPolylineCollection) droneTrackPolylineCollection.show = visible
    if (!visible && selectedTargetType.value === 'drone') {
        closePopup()
    }
}
const getStageByHeight = height => {
    if (height == null) return 'detail'
    if (height >= CLUSTER_HEIGHT) return 'cluster'
    if (height <= DETAIL_HEIGHT) return 'detail'
    return 'mid'
}
const ensureCountyCenterMap = () => {
    if (countyCenterMap.size) return
    const geojson = JSON.parse(jaGeojsonRaw)
@@ -316,24 +342,44 @@
    })
}
const buildSimulatedTrackPoints = base => {
    ensureCountyCenterMap()
    const anchor = base || { longitude: 116.397, latitude: 39.908 }
    const offsets = [
        [0, 0, 120],
        [0.02, 0.012, 150],
        [0.04, 0.006, 180],
        [0.06, -0.008, 200],
        [0.03, -0.02, 160],
        [0.0, -0.015, 140],
        [-0.02, -0.005, 120],
        [-0.01, 0.01, 130],
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
        },
    ]
    return offsets.map(item => ({
        longitude: anchor.longitude + item[0],
        latitude: anchor.latitude + item[1],
        height: item[2],
    }))
}
@@ -422,13 +468,11 @@
const renderSimulatedDroneTrack = () => {
    if (!viewer) return
    clearDroneTrackEntities()
    ensureCountyCenterMap()
    ensureDroneTrackCollections()
    droneTrackBillboardCollection.show = detailVisible.value
    droneTrackPolylineCollection.show = detailVisible.value
    droneTrackRuntime = []
    const centers = Array.from(countyCenterMap.entries())
    const baseCenters = centers.length
        ? centers.slice(0, 4)
        : [['默认区域', { longitude: 116.397, latitude: 39.908 }]]
    const baseCenters = [['默认区域', { longitude:114.958541, latitude: 27.121917 }]]
    const segmentDuration = 6
    const baseTrackColor = Cesium.Color.fromCssColorString('red')
    baseCenters.forEach(([centerName, center], trackIndex) => {
@@ -454,7 +498,7 @@
        })
        const droneId = `drone-track-${trackIndex}`
        const billboard = droneTrackBillboardCollection.add({
            position: positions[0],
            position: positions[positions.length - 1],
            image: droneIcon,
            width: 36,
            height: 36,
@@ -511,7 +555,8 @@
        const position = getDevicePosition(item)
        if (!position) return
        const entityId = `online-device-${item.id ?? index}-${index}`
        addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle)
        const rangeMeters = getDeviceRange(item)
        addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle, rangeMeters)
        const billboard = deviceBillboardCollection.add({
            position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
            image: equipmentIcon,
@@ -528,9 +573,20 @@
    ringFillInstancesByStyle.forEach((instances, index) => {
        if (!instances.length) return
        const ring = RING_STYLES[index]
        const alpha = ring.alpha ?? 0.64
        const innerRatio =
            typeof ring.innerRatio === 'number'
                ? ring.innerRatio
                : ring.inner && ring.outer
                    ? Math.min(Math.max(ring.inner / ring.outer, 0), 0.9)
                    : 0
        const material = createRadialGradientMaterial(
            Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.64),
            Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(0.64)
            Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(alpha),
            Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(alpha),
            {
                gamma: 1.7,
                innerCutoff: innerRatio,
            }
        )
        const primitive = new Cesium.GroundPrimitive({
            geometryInstances: instances,
@@ -629,7 +685,7 @@
const renderDefenseZones = zones => {
    if (!viewer) return
    clearDefenseZoneEntities()
    const result = buildZonePrimitives(zones, '#19D266', '#2AEDBF', '#012B11')
    const result = buildZonePrimitives(zones, '#72F3FF', '#72F3FF', '#0A7C88')
    defenseZonePrimitive = result.primitive
    defenseZoneOutlinePrimitive = result.outlinePrimitive
    if (defenseZonePrimitive) defenseZonePrimitive.show = detailVisible.value
@@ -640,7 +696,7 @@
const renderPartitions = zones => {
    if (!viewer) return
    clearPartitionEntities()
    const result = buildZonePrimitives(zones, '#FFCD2A', '#FFC609', '#583300')
    const result = buildZonePrimitives(zones, '#FFD772', '#FFD772', '#A86B00')
    partitionPrimitive = result.primitive
    partitionOutlinePrimitive = result.outlinePrimitive
    if (partitionPrimitive) partitionPrimitive.show = detailVisible.value
@@ -693,7 +749,7 @@
                width: 66.73,
                height: 43,
                verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
                // disableDepthTestDistance: Number.POSITIVE_INFINITY,
            },
            label: {
                text: `${count}`,
@@ -706,7 +762,6 @@
                pixelOffset: new Cesium.Cartesian2(0, -35),
                disableDepthTestDistance: Number.POSITIVE_INFINITY,
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            },
        })
    })
@@ -855,13 +910,14 @@
    const showCluster = stage === 'cluster'
    setClusterVisibility(showCluster)
    setDetailVisibility(!showCluster)
    setDroneVisibility(!showCluster)
}
const handleMapReady = ({ viewer: mapViewer }) => {
    viewer = mapViewer
    ensureCockpitPrimitiveLayer()
    const height = viewer?.camera?.positionCartographic?.height
    const stage = height != null && height >= 100000 ? 'cluster' : 'detail'
    const stage = getStageByHeight(height)
    updateStageDisplay(stage)
    renderDeviceEntities(props.onlineDevices)
    loadDefenseZones()
applications/drone-command/src/components/map-container/device-map-materials.js
@@ -1,6 +1,15 @@
import * as Cesium from 'cesium'
export const RING_STYLES = [{ inner: 0, outer: 2000, gradient: ['#FF361C', '#360B00'] }]
export const RING_STYLES = [
    {
        inner: 0,
        outer: 2000,
        gradient: ['#FF6A57', '#A11806'],
        alpha: 0.42,
        outlineAlpha: 0.85,
        innerRatio: 0.12,
    },
]
export const MATERIAL_TYPE = 'RadialGradientMaterial'
export const DRONE_TRACK_MATERIAL_TYPE = 'DroneTrackFlowMaterial'
@@ -26,15 +35,25 @@
            uniforms: {
                color1: new Cesium.Color(1.0, 1.0, 1.0, 1.0),
                color2: new Cesium.Color(0.0, 0.0, 0.0, 1.0),
                gamma: 1.7,
                innerCutoff: 0.0,
            },
            source: `
                uniform vec4 color1;
                uniform vec4 color2;
                uniform float gamma;
                uniform float innerCutoff;
                czm_material czm_getMaterial(czm_materialInput materialInput) {
                    czm_material material = czm_getDefaultMaterial(materialInput);
                    vec2 st = materialInput.st - vec2(0.5);
                    float t = clamp(length(st) * 2.0, 0.0, 1.0);
                    vec4 color = mix(color1, color2, t);
                    float ramp = smoothstep(innerCutoff, 1.0, t);
                    float shaped = pow(t, gamma);
                    float alpha = color.a * ramp * shaped;
                    material.diffuse = color.rgb;
                    material.alpha = color.a;
                    material.alpha = alpha;
                    return material;
                }
            `,
@@ -82,9 +101,9 @@
    })
}
export const createRadialGradientMaterial = (color1, color2) => {
export const createRadialGradientMaterial = (color1, color2, options = {}) => {
    registerRadialGradientMaterial()
    return Cesium.Material.fromType(MATERIAL_TYPE, { color1, color2 })
    return Cesium.Material.fromType(MATERIAL_TYPE, { color1, color2, ...(options || {}) })
}
export const createDroneTrackMaterial = options => {
@@ -115,18 +134,19 @@
    return positions
}
export const addDeviceRings = (center, ringFillInstancesByStyle, ringOutlineInstancesByStyle) => {
export const addDeviceRings = (center, ringFillInstancesByStyle, ringOutlineInstancesByStyle, rangeMeters) => {
    if (!ringFillInstancesByStyle) return
    const centerPosition = Cesium.Cartesian3.fromDegrees(center.longitude, center.latitude, 0)
    const vertexFormat = getTexturedVertexFormat()
    RING_STYLES.forEach((ring, index) => {
        if (!ringFillInstancesByStyle[index]) return
        const outer = Number.isFinite(rangeMeters) && rangeMeters > 0 ? rangeMeters : ring.outer
        ringFillInstancesByStyle[index].push(
            new Cesium.GeometryInstance({
                geometry: new Cesium.EllipseGeometry({
                    center: centerPosition,
                    semiMajorAxis: ring.outer,
                    semiMinorAxis: ring.outer,
                    semiMajorAxis: outer,
                    semiMinorAxis: outer,
                    vertexFormat,
                }),
            })
@@ -135,7 +155,9 @@
    if (!ringOutlineInstancesByStyle) return
    RING_STYLES.forEach((ring, index) => {
        if (!ringOutlineInstancesByStyle[index]) return
        const positions = buildCirclePositions(center, ring.outer)
        const outer = Number.isFinite(rangeMeters) && rangeMeters > 0 ? rangeMeters : ring.outer
        const positions = buildCirclePositions(center, outer)
        const outlineAlpha = typeof ring.outlineAlpha === 'number' ? ring.outlineAlpha : 0.8
        ringOutlineInstancesByStyle[index].push(
            new Cesium.GeometryInstance({
                geometry: new Cesium.GroundPolylineGeometry({
@@ -144,7 +166,7 @@
                }),
                attributes: {
                    color: Cesium.ColorGeometryInstanceAttribute.fromColor(
                        Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.8)
                        Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(outlineAlpha)
                    ),
                },
            })
applications/drone-command/src/views/basicManage/deviceStock/index.vue
@@ -62,7 +62,6 @@
        <div class="command-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
            <div class="command-table-content command-table-content-bg">
                <el-table class="command-table" :data="list" @selection-change="handleSelectionChange">
                    <el-table-column type="selection" width="46" />
                    <el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
                    <el-table-column prop="deviceName" show-overflow-tooltip width="130" label="设备名称" />
                    <el-table-column prop="deviceType" show-overflow-tooltip width="130" label="设备类型">
@@ -80,7 +79,7 @@
                    </el-table-column>
                    <el-table-column prop="source" show-overflow-tooltip width="112" label="来源" />
                    <el-table-column prop="purpose" show-overflow-tooltip width="206" label="用途" />
                    <el-table-column prop="belongDeptName" show-overflow-tooltip width="124" label="所属部门" />
                    <el-table-column prop="belongDeptName" show-overflow-tooltip label="所属部门" />
                    <el-table-column prop="charger" show-overflow-tooltip width="100" label="负责人" />
                    <el-table-column prop="yxzt" show-overflow-tooltip width="116" label="运行状态">
                        <template v-slot="{ row }">
applications/drone-command/src/views/dataCockpit/components/LegendBar.vue
@@ -85,13 +85,13 @@
}
.legend-color.defense {
    background: radial-gradient(50% 50% at 50% 50%, #2aedbf 0%, #012b11 100%);
    border: 1px solid #19d266;
    background: radial-gradient(50% 50% at 50% 50%, rgba(114, 243, 255, 0.45) 0%, rgba(10, 124, 136, 0.55) 100%);
    border: 1px solid rgba(114, 243, 255, 0.85);
}
.legend-color.partition {
    background: radial-gradient(50% 50% at 50% 50%, #ffc609 0%, #583300 100%);
    border: 1px solid #ffcd2a;
    background: radial-gradient(50% 50% at 50% 50%, rgba(255, 215, 114, 0.5) 0%, rgba(168, 107, 0, 0.6) 100%);
    border: 1px solid rgba(255, 215, 114, 0.9);
}
.legend-icon {
applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue
@@ -67,7 +67,7 @@
                </div>
                <div class="col">
                    <span class="label">有效范围</span>
                    {{ data.effectiveRangeKm }}m
                    {{ Number(data.effectiveRangeKm).toFixed(0) }}m
                </div>
            </div>
        </div>
applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
@@ -123,6 +123,7 @@
                            v-model="formData.effectiveRangeKm"
                            :min="0"
                            :precision="0"
                            :step="1"
                            :controls="false"
                            placeholder="请输入"
                        />
@@ -227,7 +228,14 @@
function formatRange(value) {
    if (value == null || value === '') return '-'
    return `${value}`
    const normalized = normalizeRange(value)
    return normalized == null ? '-' : `${normalized}`
}
function normalizeRange(value) {
    const numeric = Number(value)
    if (!Number.isFinite(numeric)) return null
    return Math.trunc(numeric)
}
// 关闭弹框
@@ -242,14 +250,16 @@
    submitting.value = true
    try {
        const {longitude, latitude} = parseDeployLocation(formData.value.deployLocation)
        const normalizedRange = normalizeRange(formData.value.effectiveRangeKm)
        const payload = {
            id: formData.value.id || formData.value.deviceId || undefined,
            effectiveRangeKmIsNotNull: formData.value.effectiveRangeKm ? 1 : 2,
            effectiveRangeKm: formData.value.effectiveRangeKm,
            effectiveRangeKmIsNotNull: normalizedRange != null ? 1 : 2,
            effectiveRangeKm: normalizedRange,
            longitude,
            latitude
        }
        formData.value.effectiveRangeKm = normalizedRange
        delete payload.deviceId
        await detectionRangeSubmitApi(payload)
@@ -280,6 +290,7 @@
        current: 1,
        size: 200,
        effectiveRangeKmIsNotNull: 2,
        isTrack: 1
    })
    const records = res?.data?.data?.records ?? []
    deviceOptions.value = records.map(item => ({
@@ -432,9 +443,18 @@
watch(
    () => formData.value.effectiveRangeKm,
    value => {
        const normalized = normalizeRange(value)
        if (value != null && normalized == null) {
            formData.value.effectiveRangeKm = null
            return
        }
        if (value != null && normalized !== value) {
            formData.value.effectiveRangeKm = normalized
            return
        }
        const parsed = parseDeployLocation(formData.value.deployLocation)
        if (!parsed) return
        updateRangeCircle(parsed.longitude, parsed.latitude, value)
        updateRangeCircle(parsed.longitude, parsed.latitude, normalized)
    }
)
</script>
applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue
@@ -204,8 +204,10 @@
function formatRange(row) {
    if (!row) return '-'
    const value = row.effectiveRangeKm ?? row.range
    return value === null || value === undefined || value === '' ? '-' : value
    let value = row.effectiveRangeKm ?? row.range
    return value === null || value === undefined || value === '' ? '-' : Number(value).toFixed(0)
}
function formatDeviceCode(row) {
applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue
@@ -151,7 +151,10 @@
async function getList() {
    loading.value = true
    try {
        const res = await fwDevicePageApi(searchParams.value)
        const res = await fwDevicePageApi({
            ...searchParams.value,
            deviceStatusList: '0,1,2'
        })
        list.value = res?.data?.data?.records ?? []
        total.value = res?.data?.data?.total ?? 0
    } finally {
@@ -176,6 +179,7 @@
function getDictList() {
    getDictionaryByCode('deviceType,deviceStatus,deviceAtt').then(res => {
        dictObj.value = res.data.data
        dictObj.value.deviceStatus = dictObj.value.deviceStatus.filter(item => item.dictKey != 3)
    })
}
applications/task-work-order/src/views/orderView/flyingHand/FlyingHandDialog.vue
New file
@@ -0,0 +1,278 @@
<!-- 飞手信息弹窗 -->
<template>
    <el-dialog
        v-model="dialogVisible"
        :title="dialogTitle"
        class="gd-dialog"
        destroy-on-close
        :close-on-click-modal="false"
    >
        <el-form
            ref="formRef"
            :model="formData"
            :rules="rules"
            class="gd-form"
            label-width="160px"
        >
            <el-row>
                <el-col :span="12">
                    <el-form-item label="飞手姓名" prop="flyerName">
                        <el-select
                            class="gd-select"
                            popper-class="gd-select-popper"
                            v-model="formData.flyerName"
                            placeholder="请选择飞手姓名"
                            :disabled="dialogType === 'view'"
                        >
                            <el-option
                                v-for="item in flyingHandList"
                                :key="item.id"
                                :label="item.name"
                                :value="item.id"
                            />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="飞手电话" prop="flyerPhone">
                        <el-input
                        class="gd-input"
                            v-model="formData.flyerPhone"
                            placeholder="请输入飞手电话"
                            :disabled="dialogType === 'view'"
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="飞行时长(小时)" prop="flightHours">
                        <el-input
                            class="gd-input"
                            v-model.number="formData.flightHours"
                            placeholder="请输入飞行时长"
                            type="number"
                            :disabled="dialogType === 'view'"
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="技术特长" prop="technicalStrength">
                        <el-select
                            class="gd-select"
                            popper-class="gd-select-popper"
                            v-model="formData.technicalStrength"
                            placeholder="请选择技术特长"
                            :disabled="dialogType === 'view'"
                        >
                            <el-option
                                v-for="item in dictObj.technicalStrength"
                                :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="skilledUavType">
                        <el-select
                            class="gd-select"
                            popper-class="gd-select-popper"
                            v-model="formData.skilledUavType"
                            placeholder="请选择擅长机型"
                            clearable
                            @change="handleSearch"
                        >
                            <el-option
                                v-for="item in dictObj.skilledUavType"
                                :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="skilledTaskType">
                        <el-select
                            class="gd-select"
                            popper-class="gd-select-popper"
                            v-model="formData.skilledTaskType"
                            placeholder="请选择擅长任务类型"
                            :disabled="dialogType === 'view'"
                        >
                            <el-option
                                v-for="item in dictObj.skilledTaskType"
                                :key="item.dictKey"
                                :label="item.dictValue"
                                :value="item.dictKey"
                            />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="24">
                    <el-form-item label="项目经验" prop="projectExperience">
                        <el-input
                            class="gd-input"
                            v-model="formData.projectExperience"
                            placeholder="请输入项目经验"
                            type="textarea"
                            rows="3"
                            :disabled="dialogType === 'view'"
                        />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <template #footer>
            <el-button @click="handleClose">取消</el-button>
            <el-button
                v-if="dialogType !== 'view'"
                type="primary"
                @click="handleSubmit"
            >
                确定
            </el-button>
        </template>
    </el-dialog>
</template>
<script setup>
import { ref, reactive, computed, watch, inject } from 'vue'
import { submitFlyingHand } from './flyingHandApi'
import { ElMessage } from 'element-plus'
const props = defineProps({
    modelValue: {
        type: Boolean,
        default: false
    },
    dialogType: {
        type: String,
        default: 'add', // add, edit, view
        validator: (value) => ['add', 'edit', 'view'].includes(value)
    },
    flyingHandData: {
        type: Object,
        default: () => ({})
    }
})
const dictObj = inject('dictObj') // 擅长任务类型
const flyingHandList = inject('flyingHandList') // 飞手列表
const emit = defineEmits(['update:modelValue', 'confirm'])
const dialogVisible = computed({
    get: () => props.modelValue,
    set: (value) => emit('update:modelValue', value)
})
const dialogTitle = computed(() => {
    switch (props.dialogType) {
        case 'add':
            return '添加飞手信息'
        case 'edit':
            return '编辑飞手信息'
        case 'view':
            return '查看飞手信息'
        default:
            return '飞手信息'
    }
})
const formRef = ref(null)
const formData = reactive({
    flyerName: '',
    flyerPhone: '',
    flightHours: 0,
    projectExperience: '',
    technicalStrength: '',
    skilledUavType: ''
})
const rules = {
    flyerName: [
        { required: true, message: '请输入飞手姓名', trigger: 'blur' }
    ],
    flyerPhone: [
        { required: false, message: '请输入飞手电话', trigger: 'blur' },
        { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
    ],
    flightHours: [
        { required: true, message: '请输入飞行时长', trigger: 'blur' },
        { type: 'number', min: 0, message: '飞行时长必须大于等于0', trigger: 'blur' }
    ],
    technicalSkills: [
        { required: true, message: '请选择技术特长', trigger: 'change' }
    ],
    skilledTaskType: [
        { required: true, message: '请选择擅长任务类型', trigger: 'change' }
    ],
    projectExperience: [
        { required: true, message: '请输入项目经验', trigger: 'blur' }
    ]
}
// 监听飞手数据变化,更新表单
watch(
    () => props.flyingHandData,
    (newData) => {
        if (newData) {
            Object.assign(formData, newData)
        }
    },
    { deep: true, immediate: true }
)
// 处理关闭
function handleClose() {
    dialogVisible.value = false
    resetForm()
}
// 重置表单
function resetForm() {
    formRef.value?.resetFields()
    // 重置为默认值
    Object.assign(formData, {
        name: '',
        phone: '',
        flightHours: 0,
        projectExperience: '',
        technicalSkills: '',
        technicalExpertise: '',
        goodAtAircraftType: '',
        goodAtTaskType: ''
    })
}
// 处理提交
async function handleSubmit() {
    const isValid = await formRef.value?.validate()
    if (!isValid) {
        return
    }
    try {
        if (props.dialogType === 'add') {
            await submitFlyingHand(formData)
            ElMessage.success('添加成功')
        } else if (props.dialogType === 'edit') {
            await submitFlyingHand(formData)
            ElMessage.success('编辑成功')
        }
        emit('confirm')
        dialogVisible.value = false
        resetForm()
    } catch (error) {
        ElMessage.error('操作失败')
    }
}
</script>
<style scoped lang="scss">
.gd-form {
    .el-form-item {
        margin-bottom: 20px;
    }
}
</style>
applications/task-work-order/src/views/orderView/flyingHand/flyingHandApi.js
New file
@@ -0,0 +1,37 @@
import request from '@/axios'
// 飞手分页查询
export function getFlyingHandPage(params) {
    return request({
        url: '/drone-gd/flyer/gdFlyer/page',
        method: 'get',
        params
    })
}
// 飞手提交
export function submitFlyingHand(data) {
    return request({
        url: '/drone-gd/flyer/gdFlyer/submit',
        method: 'post',
        data
    })
}
// 飞手详情查询
export function getFlyingHandDetail(params) {
    return request({
        url: '/drone-gd/flyer/gdFlyer/detail',
        method: 'get',
        params
    })
}
// 飞手删除
export function removeFlyingHand(data) {
    return request({
        url: '/drone-gd/flyer/gdFlyer/remove',
        method: 'post',
        data
    })
}
applications/task-work-order/src/views/orderView/flyingHand/index.vue
@@ -1,7 +1,223 @@
<!-- 飞手信息管理 -->
<template>
    <basic-container>飞手</basic-container>
    <basic-container>
        <el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form">
            <el-form-item label="飞手姓名" prop="flyerName">
                <el-input
                    class="gd-input gray"
                    v-model="searchParams.flyerName"
                    placeholder="请输入飞手姓名"
                    clearable
                    @clear="handleSearch"
                />
            </el-form-item>
            <el-form-item label="擅长机型" prop="skilledUavType">
                <el-select
                    class="gd-select gray"
                    popper-class="gd-select-popper"
                    v-model="searchParams.skilledUavType"
                    placeholder="请选择"
                    clearable
                    @change="handleSearch"
                >
                    <el-option
                        v-for="item in dictObj.skilledUavType"
                        :key="item.dictKey"
                        :label="item.dictValue"
                        :value="item.dictKey"
                    />
                </el-select>
            </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="addFlyingHand">新增</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 type="index" width="64" label="序号" />
                    <el-table-column prop="flyerName" show-overflow-tooltip label="飞手姓名" />
                    <el-table-column prop="flyerPhone" show-overflow-tooltip label="飞手电话" />
                    <el-table-column prop="flightHours" show-overflow-tooltip label="飞行时长(小时)" />
                    <el-table-column prop="projectExperience" show-overflow-tooltip label="项目经验" />
                    <el-table-column prop="technicalStrength" show-overflow-tooltip label="技术特长">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.technicalStrength, dictObj.technicalStrength) }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="skilledUavType" show-overflow-tooltip label="擅长机型" />
                    <el-table-column prop="skilledTaskType" show-overflow-tooltip label="擅长任务类型">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.skilledTaskType, dictObj.skilledTaskType) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns" width="180">
                        <template v-slot="{ row }">
                            <el-link @click="viewFlyingHand(row)">查看</el-link>
                            <el-link type="primary" @click="editFlyingHand(row)">编辑</el-link>
                            <el-link type="danger" @click="deleteFlyingHand(row.id)">删除</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>
        <!-- 飞手信息弹窗 -->
        <FlyingHandDialog
            ref="flyingHandDialogRef"
            v-model="dialogVisible"
            :dialog-type="dialogType"
            :flying-hand-data="flyingHandData"
            @confirm="handleConfirm"
        />
    </basic-container>
</template>
<script setup>
import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
import { onMounted, ref, nextTick } from 'vue'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getDictLabel } from '@ztzf/utils'
import { getFlyingHandPage, removeFlyingHand } from './flyingHandApi'
import { deviceFlyerApi } from '@/api/zkxt'
import FlyingHandDialog from './FlyingHandDialog.vue'
import { prod } from 'mathjs'
<script setup></script>
// 初始化查询参数
const initSearchParams = () => ({
    flyerName: '', // 飞手姓名
    skilledUavType: '', // 擅长任务类型
    current: 1, // 当前页
    size: 10, // 每页大小
})
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const queryParamsRef = ref(null) // 查询表单实例
const flyingHandDialogRef = ref(null)
const dialogVisible = ref(false)
const dialogType = ref('add') // add, edit, view
const flyingHandData = ref({})
<style scoped lang="scss"></style>
const dictObj = ref({
    skilledTaskType: [], // 擅长任务类型
    skilledUavType: [], // 擅长机型
    technicalStrength: [] // 技术特长
})
provide('dictObj', dictObj)
// 获取列表
async function getList() {
    loading.value = true
    try {
        const res = await getFlyingHandPage(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 addFlyingHand() {
    dialogType.value = 'add'
    flyingHandData.value = {}
    dialogVisible.value = true
}
// 查看飞手
function viewFlyingHand(row) {
    dialogType.value = 'view'
    flyingHandData.value = { ...row }
    dialogVisible.value = true
}
// 编辑飞手
function editFlyingHand(row) {
    dialogType.value = 'edit'
    flyingHandData.value = { ...row }
    dialogVisible.value = true
}
// 删除飞手
function deleteFlyingHand(id) {
    ElMessageBox.confirm('确定要删除该飞手信息吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
    }).then(async () => {
        try {
            await removeFlyingHand(id)
            ElMessage.success('删除成功')
            getList()
        } catch (error) {
            ElMessage.error('删除失败')
        }
    }).catch(() => {
        // 取消删除
    })
}
// 处理弹窗确认
function handleConfirm() {
    dialogVisible.value = false
    getList()
}
// 获取字典
function getDictList() {
    getDictionaryByCode('skilledTaskType,skilledUavType,technicalStrength').then(res => {
        dictObj.value = res.data.data
    })
}
// 获取飞手列表
const flyingHandList = ref([])
function getFlyingHandList() {
    deviceFlyerApi({ current: 1, size: 10 }).then(res => {
        flyingHandList.value = res?.data?.data ?? []
    })
}
provide('flyingHandList', flyingHandList)
onMounted(() => {
    getList()
    getDictList()
    getFlyingHandList()
})
</script>
<style scoped lang="scss"></style>
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/FormDiaLog.vue
File was renamed from applications/task-work-order/src/views/orderView/organizationalMessage/agenciesManagement/FormDiaLog.vue
@@ -158,6 +158,10 @@
  if (data.status !== undefined) {
    data.status = String(data.status)
  }
  // 将 areaCode 转换为字符串类型,与 regionOptions 中的 value 类型保持一致
  if (data.areaCode !== undefined) {
    data.areaCode = String(data.areaCode)
  }
  formData.value = data
}
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/agenciesApi.js
New file
@@ -0,0 +1,66 @@
import request from '@/axios'
// 列表
export const agenciesPageApi = params => {
  return request({
    url: `/blade-system/dept/page`,
    method: 'get',
    params: { ...params },
  })
}
// 新增或编辑
export const agenciesSubmitApi = data => {
  return request({
    url: `/blade-system/dept/submit`,
    method: 'post',
    data,
  })
}
// 删除
export const agenciesRemoveApi = params => {
  return request({
    url: `/blade-system/dept/remove`,
    method: 'post',
    params,
  })
}
// 详情
export const agenciesDetailApi = params => {
  return request({
    url: `/blade-system/dept/detail`,
    method: 'get',
    params,
  })
}
// 导入机构
export const agenciesImportApi = (data,params) => {
  return request({
    url: `/blade-system/dept/import-region`,
    method: 'post',
    data,
    params
  })
}
// 导出机构
export const agenciesExportApi = params => {
  return request({
    url: `/blade-system/dept/export-region`,
    method: 'get',
    params,
    responseType: 'blob',
  })
}
// 懒加载列表
export const regionLazyTreeApi = params => {
  return request({
    url: `/blade-system/region/lazy-tree`,
    method: 'get',
    params,
  })
}
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/index.vue
applications/task-work-order/src/views/orderView/organizational/zoningManagement/index.vue