Merge remote-tracking branch 'origin/master'
10 files modified
3 files renamed
3 files added
| | |
| | | * @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. |
| | |
| | | export const cockpitAggregationApi = (params) => { |
| | | return request({ |
| | | url: '/drone-fw/device/fwDevice/statisticalDeviceOut', |
| | | method: 'get' |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | |
| | | <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"> |
| | |
| | | createRadialGradientMaterial, |
| | | getTexturedVertexFormat, |
| | | } from './device-map-materials' |
| | | |
| | | const CLUSTER_HEIGHT = 100000 |
| | | const DETAIL_HEIGHT = 10000 |
| | | |
| | | const props = defineProps({ |
| | | onlineDevices: { |
| | |
| | | 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 = () => { |
| | |
| | | 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) |
| | |
| | | }) |
| | | } |
| | | |
| | | 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], |
| | | })) |
| | | } |
| | | |
| | | |
| | |
| | | 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) => { |
| | |
| | | }) |
| | | const droneId = `drone-track-${trackIndex}` |
| | | const billboard = droneTrackBillboardCollection.add({ |
| | | position: positions[0], |
| | | position: positions[positions.length - 1], |
| | | image: droneIcon, |
| | | width: 36, |
| | | height: 36, |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | width: 66.73, |
| | | height: 43, |
| | | verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
| | | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, |
| | | // disableDepthTestDistance: Number.POSITIVE_INFINITY, |
| | | }, |
| | | label: { |
| | | text: `${count}`, |
| | |
| | | |
| | | pixelOffset: new Cesium.Cartesian2(0, -35), |
| | | disableDepthTestDistance: Number.POSITIVE_INFINITY, |
| | | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, |
| | | }, |
| | | }) |
| | | }) |
| | |
| | | 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() |
| | |
| | | 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' |
| | |
| | | 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; |
| | | } |
| | | `, |
| | |
| | | }) |
| | | } |
| | | |
| | | 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 => { |
| | |
| | | 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, |
| | | }), |
| | | }) |
| | |
| | | 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({ |
| | |
| | | }), |
| | | attributes: { |
| | | color: Cesium.ColorGeometryInstanceAttribute.fromColor( |
| | | Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.8) |
| | | Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(outlineAlpha) |
| | | ), |
| | | }, |
| | | }) |
| | |
| | | <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="设备类型"> |
| | |
| | | </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 }"> |
| | |
| | | } |
| | | |
| | | .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 { |
| | |
| | | </div> |
| | | <div class="col"> |
| | | <span class="label">有效范围</span> |
| | | {{ data.effectiveRangeKm }}m |
| | | {{ Number(data.effectiveRangeKm).toFixed(0) }}m |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | v-model="formData.effectiveRangeKm" |
| | | :min="0" |
| | | :precision="0" |
| | | :step="1" |
| | | :controls="false" |
| | | placeholder="请输入" |
| | | /> |
| | |
| | | |
| | | 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) |
| | | } |
| | | |
| | | // 关闭弹框 |
| | |
| | | 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) |
| | | |
| | |
| | | current: 1, |
| | | size: 200, |
| | | effectiveRangeKmIsNotNull: 2, |
| | | isTrack: 1 |
| | | }) |
| | | const records = res?.data?.data?.records ?? [] |
| | | deviceOptions.value = records.map(item => ({ |
| | |
| | | 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> |
| | |
| | | |
| | | 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) { |
| | |
| | | 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 { |
| | |
| | | function getDictList() { |
| | | getDictionaryByCode('deviceType,deviceStatus,deviceAtt').then(res => { |
| | | dictObj.value = res.data.data |
| | | dictObj.value.deviceStatus = dictObj.value.deviceStatus.filter(item => item.dictKey != 3) |
| | | }) |
| | | } |
| | | |
| New file |
| | |
| | | <!-- 飞手信息弹窗 --> |
| | | <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> |
| New file |
| | |
| | | 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 |
| | | }) |
| | | } |
| | |
| | | <!-- 飞手信息管理 --> |
| | | <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> |
| File was renamed from applications/task-work-order/src/views/orderView/organizationalMessage/agenciesManagement/FormDiaLog.vue |
| | |
| | | if (data.status !== undefined) { |
| | | data.status = String(data.status) |
| | | } |
| | | // 将 areaCode 转换为字符串类型,与 regionOptions 中的 value 类型保持一致 |
| | | if (data.areaCode !== undefined) { |
| | | data.areaCode = String(data.areaCode) |
| | | } |
| | | formData.value = data |
| | | } |
| | | |
| New file |
| | |
| | | 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, |
| | | }) |
| | | } |