applications/drone-command/src/components/map-container/device-map-container.vue
@@ -1,4 +1,4 @@ <template> <template> <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" @@ -93,6 +93,7 @@ import { cockpitAggregationApi } from '@/api/dataCockpit' import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png' import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png' import droneIcon from '@/assets/images/dataCockpit/map/drone.png' import aggregationIcon from '@/assets/images/dataCockpit/map/aggregation.png' import commandPostIcon from '@/assets/images/dataCockpit/map/command-post.png' import popupClose from '@/assets/images/dataCockpit/map/popup-close.png' @@ -121,19 +122,29 @@ const mapRef = ref(null) let viewer = null const deviceEntityIds = new Set() const deviceEntityMap = new Map() let defenseZoneSource = null let partitionSource = null let cockpitPrimitiveLayer = null const devicePickMap = new Map() let deviceBillboardCollection = null let deviceRingOutlinePrimitives = [] let deviceRingFillPrimitives = [] let defenseZonePrimitive = null let defenseZoneOutlinePrimitive = null let partitionPrimitive = null let partitionOutlinePrimitive = null let aggregationSource = null let commandPostSource = null let commandPostBillboardCollection = null let droneTrackBillboardCollection = null let droneTrackPolylineCollection = null let droneTrackTickHandler = null let droneTrackStartTime = null let droneTrackRuntime = [] const detailVisible = ref(true) const clusterVisible = ref(false) const countyCenterMap = new Map() const showLayerPanel = ref(false) const layerWrapRef = ref(null) const selectedDevice = ref(null) let selectedDeviceEntity = null let selectedDeviceBillboard = null let deviceClickHandler = null let popupRenderHandler = null const layerTreeProps = { @@ -180,23 +191,101 @@ return { longitude, latitude } } const ensureCockpitPrimitiveLayer = () => { if (!viewer) return if (!cockpitPrimitiveLayer) { cockpitPrimitiveLayer = new Cesium.PrimitiveCollection({ destroyPrimitives: false }) viewer.scene.primitives.add(cockpitPrimitiveLayer) } } const addCockpitPrimitive = primitive => { if (!primitive) return if (cockpitPrimitiveLayer) { cockpitPrimitiveLayer.add(primitive) return } viewer?.scene?.primitives?.add(primitive) } const removeCockpitPrimitive = (primitive, destroy = true) => { if (!primitive) return if (cockpitPrimitiveLayer) { cockpitPrimitiveLayer.remove(primitive, destroy) return } viewer?.scene?.primitives?.remove(primitive) } const reorderCockpitPrimitives = () => { if (!viewer) return ensureCockpitPrimitiveLayer() cockpitPrimitiveLayer.removeAll(false) if (defenseZonePrimitive) cockpitPrimitiveLayer.add(defenseZonePrimitive) if (defenseZoneOutlinePrimitive) cockpitPrimitiveLayer.add(defenseZoneOutlinePrimitive) if (partitionPrimitive) cockpitPrimitiveLayer.add(partitionPrimitive) if (partitionOutlinePrimitive) cockpitPrimitiveLayer.add(partitionOutlinePrimitive) if (deviceRingFillPrimitives.length) { deviceRingFillPrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive)) } if (deviceRingOutlinePrimitives.length) { deviceRingOutlinePrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive)) } if (droneTrackPolylineCollection) cockpitPrimitiveLayer.add(droneTrackPolylineCollection) if (droneTrackBillboardCollection) cockpitPrimitiveLayer.add(droneTrackBillboardCollection) if (deviceBillboardCollection) cockpitPrimitiveLayer.add(deviceBillboardCollection) if (commandPostBillboardCollection) cockpitPrimitiveLayer.add(commandPostBillboardCollection) } const clearDeviceEntities = () => { if (!viewer) return deviceEntityIds.forEach(id => { viewer.entities.removeById(id) }) deviceEntityIds.clear() deviceEntityMap.clear() if (deviceBillboardCollection) { deviceBillboardCollection.removeAll() removeCockpitPrimitive(deviceBillboardCollection) deviceBillboardCollection = null } if (deviceRingFillPrimitives.length) { deviceRingFillPrimitives.forEach(primitive => removeCockpitPrimitive(primitive)) deviceRingFillPrimitives = [] } if (deviceRingOutlinePrimitives.length) { deviceRingOutlinePrimitives.forEach(primitive => removeCockpitPrimitive(primitive)) deviceRingOutlinePrimitives = [] } devicePickMap.clear() } const ensureDeviceCollections = () => { if (!viewer) return ensureCockpitPrimitiveLayer() if (!deviceBillboardCollection) { deviceBillboardCollection = new Cesium.BillboardCollection() addCockpitPrimitive(deviceBillboardCollection) } } const clearDefenseZoneEntities = () => { if (!defenseZoneSource) return defenseZoneSource.entities.removeAll() if (!viewer) return if (defenseZonePrimitive) { removeCockpitPrimitive(defenseZonePrimitive) defenseZonePrimitive = null } if (defenseZoneOutlinePrimitive) { removeCockpitPrimitive(defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive = null } } const clearPartitionEntities = () => { if (!partitionSource) return partitionSource.entities.removeAll() if (!viewer) return if (partitionPrimitive) { removeCockpitPrimitive(partitionPrimitive) partitionPrimitive = null } if (partitionOutlinePrimitive) { removeCockpitPrimitive(partitionOutlinePrimitive) partitionOutlinePrimitive = null } } const clearAggregationEntities = () => { @@ -205,20 +294,42 @@ } const clearCommandPostEntities = () => { if (!commandPostSource) return commandPostSource.entities.removeAll() if (!viewer) return if (commandPostBillboardCollection) { commandPostBillboardCollection.removeAll() removeCockpitPrimitive(commandPostBillboardCollection) commandPostBillboardCollection = null } } const ensureCommandPostCollection = () => { if (!viewer) return ensureCockpitPrimitiveLayer() if (!commandPostBillboardCollection) { commandPostBillboardCollection = new Cesium.BillboardCollection() addCockpitPrimitive(commandPostBillboardCollection) } } const setDetailVisibility = visible => { detailVisible.value = visible if (defenseZoneSource) defenseZoneSource.show = visible if (partitionSource) partitionSource.show = visible if (commandPostSource) commandPostSource.show = visible if (defenseZonePrimitive) defenseZonePrimitive.show = visible if (defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive.show = visible if (partitionPrimitive) partitionPrimitive.show = visible if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = visible if (commandPostBillboardCollection) commandPostBillboardCollection.show = visible if (deviceBillboardCollection) deviceBillboardCollection.show = visible if (deviceRingFillPrimitives.length) { deviceRingFillPrimitives.forEach(primitive => { primitive.show = visible }) } if (deviceRingOutlinePrimitives.length) { deviceRingOutlinePrimitives.forEach(primitive => { primitive.show = visible }) } if (!visible) closePopup() deviceEntityIds.forEach(id => { const entity = viewer?.entities?.getById(id) if (entity) entity.show = visible }) } const setClusterVisibility = visible => { @@ -237,12 +348,139 @@ }) } 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], ] return offsets.map(item => ({ longitude: anchor.longitude + item[0], latitude: anchor.latitude + item[1], height: item[2], })) } const clearDroneTrackEntities = () => { if (!viewer) return if (droneTrackBillboardCollection) { droneTrackBillboardCollection.removeAll() removeCockpitPrimitive(droneTrackBillboardCollection) droneTrackBillboardCollection = null } if (droneTrackPolylineCollection) { droneTrackPolylineCollection.removeAll() removeCockpitPrimitive(droneTrackPolylineCollection) droneTrackPolylineCollection = null } droneTrackRuntime = [] stopDroneTrackAnimation() } const ensureDroneTrackCollections = () => { if (!viewer) return ensureCockpitPrimitiveLayer() if (!droneTrackBillboardCollection) { droneTrackBillboardCollection = new Cesium.BillboardCollection() addCockpitPrimitive(droneTrackBillboardCollection) } if (!droneTrackPolylineCollection) { droneTrackPolylineCollection = new Cesium.PolylineCollection() addCockpitPrimitive(droneTrackPolylineCollection) } } const startDroneTrackAnimation = () => { if (!viewer) return stopDroneTrackAnimation() droneTrackStartTime = Cesium.JulianDate.now() droneTrackTickHandler = clock => { if (!droneTrackRuntime.length) return const current = clock.currentTime const elapsed = Cesium.JulianDate.secondsDifference(current, droneTrackStartTime) droneTrackRuntime.forEach(track => { const duration = track.duration if (duration <= 0) return const t = ((elapsed % duration) + duration) % duration const seg = Math.min(track.positions.length - 2, Math.floor(t / track.segmentDuration)) const ratio = (t - seg * track.segmentDuration) / track.segmentDuration const pos = Cesium.Cartesian3.lerp( track.positions[seg], track.positions[seg + 1], ratio, new Cesium.Cartesian3() ) track.billboard.position = pos }) } viewer.clock.onTick.addEventListener(droneTrackTickHandler) viewer.clock.shouldAnimate = true } const stopDroneTrackAnimation = () => { if (!viewer || !droneTrackTickHandler) return viewer.clock.onTick.removeEventListener(droneTrackTickHandler) droneTrackTickHandler = null } const renderSimulatedDroneTrack = () => { if (!viewer) return ensureCountyCenterMap() ensureDroneTrackCollections() droneTrackBillboardCollection.removeAll() droneTrackPolylineCollection.removeAll() droneTrackRuntime = [] const centers = Array.from(countyCenterMap.values()) const baseCenters = centers.length ? centers.slice(0, 4) : [{ longitude: 116.397, latitude: 39.908 }] const segmentDuration = 6 baseCenters.forEach((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) ) droneTrackPolylineCollection.add({ positions, width: 2, material: Cesium.Material.fromType('Color', { color: Cesium.Color.fromCssColorString('#2AEDBF'), }), }) const billboard = droneTrackBillboardCollection.add({ position: positions[0], image: droneIcon, width: 36, height: 36, verticalOrigin: Cesium.VerticalOrigin.CENTER, }) droneTrackRuntime.push({ positions, billboard, segmentDuration, duration: (points.length - 1) * segmentDuration, }) }) viewer.clock.multiplier = 1 startDroneTrackAnimation() reorderCockpitPrimitives() } const RING_STYLES = [ { inner: 0, outer: 2000, gradient: ['#FF361C', '#360B00'] } ] const MATERIAL_TYPE = 'RadialGradientMaterial' const TEXTURED_VERTEX_FORMAT = Cesium.MaterialAppearance?.MaterialSupport?.TEXTURED?.vertexFormat || Cesium.VertexFormat.POSITION_AND_ST let materialRegistered = false const registerRadialGradientMaterial = () => { @@ -287,27 +525,39 @@ return positions } const addDeviceRings = (center, baseId, visible) => { const addDeviceRings = (center, ringFillInstancesByStyle, ringOutlineInstancesByStyle) => { if (!ringFillInstancesByStyle) return const centerPosition = Cesium.Cartesian3.fromDegrees(center.longitude, center.latitude, 0) RING_STYLES.forEach((ring, index) => { const outerPositions = buildCirclePositions(center, ring.outer) const holes = ring.inner ? [new Cesium.PolygonHierarchy(buildCirclePositions(center, ring.inner))] : [] const entityId = `${baseId}-ring-${index}` deviceEntityIds.add(entityId) registerRadialGradientMaterial() const [startColor, endColor] = ring.gradient const color1 = Cesium.Color.fromCssColorString(startColor).withAlpha(0.34) const color2 = Cesium.Color.fromCssColorString(endColor).withAlpha(0.34) const material = new RadialGradientMaterialProperty(color1, color2) viewer.entities.add({ id: entityId, show: visible, polygon: { hierarchy: new Cesium.PolygonHierarchy(outerPositions, holes), material, }, }) if (!ringFillInstancesByStyle[index]) return ringFillInstancesByStyle[index].push( new Cesium.GeometryInstance({ geometry: new Cesium.EllipseGeometry({ center: centerPosition, semiMajorAxis: ring.outer, semiMinorAxis: ring.outer, vertexFormat: TEXTURED_VERTEX_FORMAT, }), }) ) }) if (!ringOutlineInstancesByStyle) return RING_STYLES.forEach((ring, index) => { if (!ringOutlineInstancesByStyle[index]) return const positions = buildCirclePositions(center, ring.outer) ringOutlineInstancesByStyle[index].push( new Cesium.GeometryInstance({ geometry: new Cesium.GroundPolylineGeometry({ positions, width: 2, }), attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.8) ), }, }) ) }) } @@ -348,29 +598,69 @@ const renderDeviceEntities = devices => { if (!viewer) return clearDeviceEntities() ensureCockpitPrimitiveLayer() ensureDeviceCollections() deviceBillboardCollection.removeAll() if (deviceRingFillPrimitives.length) { deviceRingFillPrimitives.forEach(primitive => removeCockpitPrimitive(primitive)) deviceRingFillPrimitives = [] } if (deviceRingOutlinePrimitives.length) { deviceRingOutlinePrimitives.forEach(primitive => removeCockpitPrimitive(primitive)) deviceRingOutlinePrimitives = [] } devicePickMap.clear() deviceBillboardCollection.show = detailVisible.value const ringFillInstancesByStyle = RING_STYLES.map(() => []) const ringOutlineInstancesByStyle = RING_STYLES.map(() => []) devices.forEach((item, index) => { const position = getDevicePosition(item) if (!position) return const entityId = `online-device-${item.id ?? index}-${index}` deviceEntityIds.add(entityId) addDeviceRings(position, entityId, detailVisible.value) const entity = viewer.entities.add({ id: entityId, show: detailVisible.value, addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle) const billboard = deviceBillboardCollection.add({ position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0), billboard: { image: equipmentIcon, width: 40, height: 56, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, }, image: equipmentIcon, width: 40, height: 56, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, }) deviceEntityMap.set(entityId, { data: item, entity }) billboard.id = entityId devicePickMap.set(entityId, { data: item, billboard }) }) if (selectedDeviceEntity && !deviceEntityMap.has(selectedDeviceEntity.id)) { if (selectedDeviceBillboard && !devicePickMap.has(selectedDeviceBillboard.id)) { closePopup() } registerRadialGradientMaterial() ringFillInstancesByStyle.forEach((instances, index) => { if (!instances.length) return const ring = RING_STYLES[index] const material = Cesium.Material.fromType(MATERIAL_TYPE, { color1: Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.64), color2: Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(0.64), }) const primitive = new Cesium.GroundPrimitive({ geometryInstances: instances, appearance: new Cesium.MaterialAppearance({ material, translucent: true, }), }) primitive.show = detailVisible.value addCockpitPrimitive(primitive) deviceRingFillPrimitives.push(primitive) }) ringOutlineInstancesByStyle.forEach(instances => { if (!instances.length) return const primitive = new Cesium.GroundPolylinePrimitive({ geometryInstances: instances, appearance: new Cesium.PolylineColorAppearance(), }) primitive.show = detailVisible.value addCockpitPrimitive(primitive) deviceRingOutlinePrimitives.push(primitive) }) reorderCockpitPrimitives() } const getDefenseZonePositions = geom => { @@ -385,70 +675,87 @@ return list.map(item => Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude)) } const renderZonePolygons = ({ zones, source, idPrefix, lineColor, fillGradient }) => { if (!viewer || !source) return const buildZonePrimitives = (zones, lineColor, fillColor1, fillColor2) => { if (!viewer) return { primitive: null, outlinePrimitive: null } const polygonInstances = [] const lineInstances = [] registerRadialGradientMaterial() const borderColor = Cesium.Color.fromCssColorString(lineColor) const fillColorStart = Cesium.Color.fromCssColorString(fillGradient[0]).withAlpha(0.34) const fillColorEnd = Cesium.Color.fromCssColorString(fillGradient[1]).withAlpha(0.34) const fillMaterial = new RadialGradientMaterialProperty(fillColorStart, fillColorEnd) zones.forEach((zone, index) => { ; (zones || []).forEach((zone, index) => { if (!zone?.geom) return const positions = getDefenseZonePositions(zone.geom) if (!positions.length) return const entityId = `${idPrefix}-${zone.id ?? index}` const linePositions = positions.length > 1 ? [...positions, positions[0]] : positions source.entities.add({ id: entityId, polygon: { hierarchy: new Cesium.PolygonHierarchy(positions), material: fillMaterial, outline: true, outlineColor: borderColor, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, }, polyline: { positions: linePositions, clampToGround: true, width: 2, material: borderColor, }, const polygon = new Cesium.PolygonGeometry({ polygonHierarchy: new Cesium.PolygonHierarchy(positions), vertexFormat: TEXTURED_VERTEX_FORMAT, }) polygonInstances.push( new Cesium.GeometryInstance({ geometry: polygon, }) ) const linePositions = positions.length > 1 ? [...positions, positions[0]] : positions lineInstances.push( new Cesium.GeometryInstance({ geometry: new Cesium.GroundPolylineGeometry({ positions: linePositions, width: 2, }), attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.fromCssColorString(lineColor) ), }, }) ) }) let primitive = null if (polygonInstances.length) { const baseColor1 = Cesium.Color.fromCssColorString(fillColor1) const baseColor2 = Cesium.Color.fromCssColorString(fillColor2) const material = Cesium.Material.fromType(MATERIAL_TYPE, { color1: baseColor1.withAlpha(0.64), color2: baseColor2.withAlpha(0.64), }) primitive = new Cesium.GroundPrimitive({ geometryInstances: polygonInstances, appearance: new Cesium.MaterialAppearance({ material, translucent: true, }), }) addCockpitPrimitive(primitive) } let outlinePrimitive = null if (lineInstances.length) { outlinePrimitive = new Cesium.GroundPolylinePrimitive({ geometryInstances: lineInstances, appearance: new Cesium.PolylineColorAppearance(), }) addCockpitPrimitive(outlinePrimitive) } return { primitive, outlinePrimitive } } const renderDefenseZones = zones => { if (!viewer) return if (!defenseZoneSource) { defenseZoneSource = new Cesium.CustomDataSource('defenseZoneSource') viewer.dataSources.add(defenseZoneSource) } clearDefenseZoneEntities() defenseZoneSource.show = detailVisible.value renderZonePolygons({ zones, source: defenseZoneSource, idPrefix: 'defense-zone', lineColor: '#19D266', fillGradient: ['#2AEDBF', '#012B11'], }) const result = buildZonePrimitives(zones, '#19D266', '#2AEDBF', '#012B11') defenseZonePrimitive = result.primitive defenseZoneOutlinePrimitive = result.outlinePrimitive if (defenseZonePrimitive) defenseZonePrimitive.show = detailVisible.value if (defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive.show = detailVisible.value reorderCockpitPrimitives() } const renderPartitions = zones => { if (!viewer) return if (!partitionSource) { partitionSource = new Cesium.CustomDataSource('partitionSource') viewer.dataSources.add(partitionSource) } clearPartitionEntities() partitionSource.show = detailVisible.value renderZonePolygons({ zones, source: partitionSource, idPrefix: 'partition-zone', lineColor: '#FFCD2A', fillGradient: ['#FFC609', '#583300'], }) const result = buildZonePrimitives(zones, '#FFCD2A', '#FFC609', '#583300') partitionPrimitive = result.primitive partitionOutlinePrimitive = result.outlinePrimitive if (partitionPrimitive) partitionPrimitive.show = detailVisible.value if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = detailVisible.value reorderCockpitPrimitives() } const loadDefenseZones = async () => { @@ -517,19 +824,18 @@ const getPickedDevice = picks => { for (const pick of picks) { const entityId = typeof pick?.id === 'string' ? pick.id : pick?.id?.id if (entityId && deviceEntityMap.has(entityId)) { return deviceEntityMap.get(entityId) const pickId = pick?.id const resolvedId = typeof pickId === 'string' ? pickId : pickId?.id if (resolvedId && devicePickMap.has(resolvedId)) { return devicePickMap.get(resolvedId) } } return null } const updatePopupPosition = () => { if (!viewer || !selectedDeviceEntity) return const cartesian = selectedDeviceEntity.position?.getValue?.(Cesium.JulianDate.now()) || selectedDeviceEntity.position?._value if (!viewer || !selectedDeviceBillboard) return const cartesian = selectedDeviceBillboard.position if (!cartesian) return const screenPosition = viewer.scene.cartesianToCanvasCoordinates(cartesian) if (!screenPosition) return @@ -558,7 +864,7 @@ return } selectedDevice.value = pickedDevice.data selectedDeviceEntity = pickedDevice.entity selectedDeviceBillboard = pickedDevice.billboard startPopupRender() } @@ -576,7 +882,7 @@ function closePopup () { selectedDevice.value = null selectedDeviceEntity = null selectedDeviceBillboard = null stopPopupRender() } @@ -654,27 +960,21 @@ const renderCommandPosts = list => { if (!viewer) return if (!commandPostSource) { commandPostSource = new Cesium.CustomDataSource('commandPostSource') viewer.dataSources.add(commandPostSource) } clearCommandPostEntities() commandPostSource.show = detailVisible.value ; (list || []).forEach((item, index) => { const position = getDevicePosition(item) if (!position) return commandPostSource.entities.add({ id: `command-post-${item.id ?? index}`, position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0), billboard: { image: commandPostIcon, width: 40, height: 56, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, }, }) ensureCommandPostCollection() commandPostBillboardCollection.removeAll() commandPostBillboardCollection.show = detailVisible.value ; (list || []).forEach((item, index) => { const position = getDevicePosition(item) if (!position) return commandPostBillboardCollection.add({ position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0), image: commandPostIcon, width: 40, height: 56, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, }) }) reorderCockpitPrimitives() } const loadAggregation = async () => { @@ -724,6 +1024,7 @@ const handleMapReady = ({ viewer: mapViewer }) => { viewer = mapViewer ensureCockpitPrimitiveLayer() const height = viewer?.camera?.positionCartographic?.height const stage = height != null && height >= 100000 ? 'cluster' : 'detail' updateStageDisplay(stage) @@ -732,6 +1033,7 @@ loadPartitions() loadAggregation() loadCommandPosts() renderSimulatedDroneTrack() initDeviceClickHandler() } @@ -759,8 +1061,13 @@ clearPartitionEntities() clearAggregationEntities() clearCommandPostEntities() clearDroneTrackEntities() closePopup() destroyDeviceClickHandler() if (cockpitPrimitiveLayer && viewer?.scene?.primitives) { viewer.scene.primitives.remove(cockpitPrimitiveLayer) } cockpitPrimitiveLayer = null viewer = null }) </script> @@ -1092,3 +1399,4 @@ border-color: #a1a3d4; } </style> applications/drone-command/src/views/dataCockpit/components/HistoryWarning.vue
@@ -9,7 +9,7 @@ <div class="search-box"> <!-- 关键字 --> <el-input v-model="query.keyword" class="command-input" placeholder="请输入数据名称" clearable @keyup.enter="onSearch"> @keyup.enter="onSearch" @clear="onSearch"> </el-input> <!-- 日期范围 --> @@ -38,6 +38,7 @@ </template> <script setup> import { dateRangeFormat } from '@ztzf/utils' import { ref, computed, onMounted } from 'vue' import HistoryTemplate from './templateComponents/HistoryTemplate.vue' import { alarmLogApi } from '@/api/dataCockpit' @@ -62,10 +63,11 @@ } const fetchHistoryWarning = async () => { const startAt = Date.now() loading.value = true try { const [startTime, endTime] = query.value.dateRange || [] const [startTime, endTime] = dateRangeFormat(query.value.dateRange) || [] const keyword = (query.value.keyword || '').trim() const params = { current: 1, applications/drone-command/src/views/permissionManage/operationLog/index.vue
@@ -40,7 +40,7 @@ range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD" @change="handleSearch" /> </el-form-item> @@ -107,10 +107,9 @@ ] async function getList() { const range = dateRangeFormat(dateRange.value) loading.value = true try { const params = { ...searchParams.value, startTime: range[0], endTime: range[1] } const params = { ...searchParams.value, startTime: dateRange.value[0], endTime: dateRange.value[1] } const res = await getOperationLogPage(params) list.value = res?.data?.data?.records ?? [] total.value = res?.data?.data?.total ?? 0 applications/drone-command/src/views/recordManage/historyTracks/index.vue
@@ -162,7 +162,7 @@ ...searchParams.value, start_time: range?.[0], end_time: range?.[1], id: ids.join(','), ids: ids.join(','), }) .then(res => { blobDownload(res) applications/task-work-order/env/.env
@@ -1,6 +1,9 @@ #接口地址 VITE_APP_API=/api #接口地址-xt VITE_APP_API_XT=/apiXT #页面基础路径 VITE_APP_BASE=/task-work-order applications/task-work-order/env/.env.development
@@ -4,10 +4,10 @@ # @LastEditors : yuan # @LastEditTime : 2026-01-16 11:02:24 # @FilePath : \applications\task-work-order\env\.env.development # @Description : # Copyright 2026 OBKoro1, All Rights Reserved. # @Description : # Copyright 2026 OBKoro1, All Rights Reserved. # 2026-01-07 14:58:30 ### ### NODE_ENV = 'development' #开发环境配置 @@ -17,6 +17,8 @@ # VITE_APP_URL = https://wrj.shuixiongit.com/api VITE_APP_URL= http://192.168.1.204 VITE_APP_URL_XT=http://218.202.104.82:8200 #新大屏地址 VITE_APP_DASHBOARD_URL = 'https://wrj.shuixiongit.com/command-center-dashboard/' applications/task-work-order/src/App.vue
@@ -2,9 +2,19 @@ <router-view /> </template> <script setup> import { loginWithMiniProgramApi } from '@/api/zkxt' function getToken() { loginWithMiniProgramApi({username: 'admin'}).then(res => { console.log(res) }) } getToken() setInterval(() => { getToken() }, 600000) </script> <style> applications/task-work-order/src/api/zkxt/index.js
New file @@ -0,0 +1,9 @@ import request from '@/axiosXT' export const loginWithMiniProgramApi = (data) => { return request({ url: `/auth/loginWithMiniProgram`, method: 'post', data, }) } applications/task-work-order/src/axios.js
@@ -6,240 +6,224 @@ * isToken是否需要token */ import axios from 'axios'; import store from '@/store/'; import router from '@/router/'; import { serialize } from '@/utils/util'; import { getToken, removeToken, removeRefreshToken } from '@/utils/auth'; import { isURL, validatenull } from '@/utils/validate'; import { ElMessage } from 'element-plus'; import website from '@/config/website'; import { Base64 } from 'js-base64'; import { baseUrl } from '@/config/env'; import crypto from '@/utils/crypto'; const adminUrl = import.meta.env.VITE_APP_DASHBOARD_URL; import axios from 'axios' import store from '@/store/' import router from '@/router/' import { serialize } from '@/utils/util' import { getToken, removeToken, removeRefreshToken } from '@/utils/auth' import { isURL, validatenull } from '@/utils/validate' import { ElMessage } from 'element-plus' import website from '@/config/website' import { Base64 } from 'js-base64' import { baseUrl } from '@/config/env' import crypto from '@/utils/crypto' const adminUrl = import.meta.env.VITE_APP_DASHBOARD_URL // 全局未授权错误提示状态,只提示一次 let isErrorShown = false; let isErrorShown = false let _retry = false; // 超时时间设置为10分钟,部分接口上传比较慢,如固件上传 axios.defaults.timeout = 600000; //返回其他状态码 axios.defaults.validateStatus = function (status) { return status >= 200 && status <= 500; // 默认的 }; //跨域请求,允许保存cookie axios.defaults.withCredentials = true; let _retry = false // 创建作用域的 axios 实例 const service = axios.create({ // 超时时间设置为10分钟,部分接口上传比较慢,如固件上传 timeout: 600000, //返回其他状态码 validateStatus: function (status) { return status >= 200 && status <= 500 // 默认的 }, //跨域请求,允许保存cookie withCredentials: true, }) //http request拦截 axios.interceptors.request.use( config => { // 初始化错误提示状态 isErrorShown = false; //地址为已经配置状态则不添加前缀 if (!isURL(config.url) && !config.url.startsWith(baseUrl)) { config.url = baseUrl + config.url; } //安全请求header config.headers['areaCode'] = config?.params?.areaCode || store?.state?.user?.userInfo?.detail?.areaCode; config.headers['Blade-Requested-With'] = 'BladeHttpRequest'; //headers判断是否需要 const authorization = config.authorization === false; if (!authorization) { config.headers['Authorization'] = `Basic ${Base64.encode( `${website.clientId}:${website.clientSecret}` )}`; } // 后端的要求 if (router.currentRoute.value.path === '/job/jobstatistics'){ const userAreaCode = store?.state?.user?.userInfo?.detail?.areaCode const paramsKey = config.method === 'get' ? 'params' : 'data'; const codeKey = config.method === 'get' ? 'areaCode' : 'area_code'; const paramCode = config?.params?.areaCode || config?.data?.area_code try { if (paramCode){ if (paramCode === userAreaCode) { config[paramsKey][codeKey] = config.method === 'get' ? '' : undefined; } else { if (!Array.isArray(config[paramsKey])){ config[paramsKey] = {...config[paramsKey],[codeKey]: paramCode} } } } }catch (e) {} } service.interceptors.request.use( config => { // 初始化错误提示状态 isErrorShown = false //地址为已经配置状态则不添加前缀 if (!isURL(config.url) && !config.url.startsWith(baseUrl)) { config.url = baseUrl + config.url } //安全请求header config.headers['areaCode'] = config?.params?.areaCode || store?.state?.user?.userInfo?.detail?.areaCode config.headers['Blade-Requested-With'] = 'BladeHttpRequest' //headers判断是否需要 const authorization = config.authorization === false if (!authorization) { config.headers['Authorization'] = `Basic ${Base64.encode(`${website.clientId}:${website.clientSecret}`)}` } //headers判断请求是否携带token const meta = config.meta || {}; const isToken = meta.isToken === false; //headers传递token是否加密 const cryptoToken = config.cryptoToken === true; //判断传递数据是否加密 const cryptoData = config.cryptoData === true; const token = getToken(); if (token && !isToken) { config.headers[website.tokenHeader] = cryptoToken ? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey) : 'bearer ' + token; } // 开启报文加密 // if (cryptoData) { // if (config.params) { // const data = crypto.encryptAES(JSON.stringify(config.params), crypto.aesKey); // config.params = { data }; // } // if (config.data) { // config.text = true; // config.data = crypto.encryptAES(JSON.stringify(config.data), crypto.aesKey); // } // } //headers中配置text请求 if (config.text === true) { config.headers['Content-Type'] = 'text/plain'; } //headers中配置serialize为true开启序列化 if (config.method === 'post' && meta.isSerialize === true) { config.data = serialize(config.data); } return config; }, error => { return Promise.reject(error); } ); //headers判断请求是否携带token const meta = config.meta || {} const isToken = meta.isToken === false //headers传递token是否加密 const cryptoToken = config.cryptoToken === true //判断传递数据是否加密 const cryptoData = config.cryptoData === true const token = getToken() if (token && !isToken) { config.headers[website.tokenHeader] = cryptoToken ? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey) : 'bearer ' + token } // 开启报文加密 // if (cryptoData) { // if (config.params) { // const data = crypto.encryptAES(JSON.stringify(config.params), crypto.aesKey); // config.params = { data }; // } // if (config.data) { // config.text = true; // config.data = crypto.encryptAES(JSON.stringify(config.data), crypto.aesKey); // } // } //headers中配置text请求 if (config.text === true) { config.headers['Content-Type'] = 'text/plain' } //headers中配置serialize为true开启序列化 if (config.method === 'post' && meta.isSerialize === true) { config.data = serialize(config.data) } return config }, error => { return Promise.reject(error) } ) //http response拦截 axios.interceptors.response.use( res => { const status = res.data.error_code || res.data.code || res.status; const statusWhiteList = website.statusWhiteList || []; const message = res.data.msg || res.data.error_description || res.data.message || '系统错误'; const config = res.config; const cryptoData = config.cryptoData === true; if (status === 511) { ElMessage({ message: '请联系运维人员上传密钥证书!', type: 'error', }); // 获取当前路由名称 const currentRouteName = router.currentRoute.value.name; // 如果当前路由不是登录页,则跳转到登录页 if (currentRouteName !== '登录页') { store.dispatch('FedLogOut').then(() => router.push({ path: '/login', }) ); } return Promise.reject(new Error(message)); } //如果在白名单里则自行catch逻辑处理 if (statusWhiteList.includes(status)) return Promise.reject(res); // 如果是401并且没有重试过,尝试刷新token if (status === 401 && !_retry) { // 标记此请求已尝试刷新token _retry = true; // 调用RefreshToken action来刷新token return store .dispatch('RefreshToken') .then(() => { const meta = config.meta || {}; const isToken = meta.isToken === false; const cryptoToken = config.cryptoToken === true; // 获取刷新后的token const token = getToken(); if (token && !isToken) { config.headers[website.tokenHeader] = cryptoToken ? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey) : 'bearer ' + token; } // 重新发送原来的请求 return axios(config); }) .catch(() => { // 首次报错时提示 if (!isErrorShown) { isErrorShown = true; ElMessage({ message: message, type: 'error', }); } // 清除token信息 removeToken(); removeRefreshToken(); const env = import.meta.env.VITE_APP_ENV; // 重定向到登录页 // store.dispatch('FedLogOut').then(() => router.push({ // path: '/login' // })); env === 'development' ? store.dispatch('FedLogOut').then(() => router.push({ path: '/login', }) ) : store.dispatch('FedLogOut').then(() => window.location.replace(`${adminUrl}#/login`)); return Promise.reject(new Error(message)); }); } // 如果是401并且已经重试过,直接跳转到登录页面 if (status === 401 && _retry) { // 首次报错时提示 if (!isErrorShown) { isErrorShown = true; ElMessage({ message: '用户令牌过期,请重新登录', type: 'error', }); } // 清除token信息 removeToken(); removeRefreshToken(); // 重定向到登录页 // store.dispatch('FedLogOut').then(() => router.push({ // path: '/login' // })); const env = import.meta.env.VITE_APP_ENV; env === 'development' ? store.dispatch('FedLogOut').then(() => router.push({ path: '/login', }) ) : store.dispatch('FedLogOut').then(() => window.location.replace(`${adminUrl}#/login`)); return Promise.reject(new Error(message)); } // 如果请求为oauth2错误码则首次报错时提示 if (status > 2000 && !validatenull(res.data.error_description)) { // 首次报错时提示 if (!isErrorShown) { isErrorShown = true; ElMessage({ message: message, type: 'error', }); } return Promise.reject(new Error(message)); } // 如果请求为非200默认统一处理 if (status !== 200 && status !== 0) { if (!isErrorShown) { ElMessage({ message: message, type: 'error', }); } return Promise.reject(new Error(message)); } // 解析加密报文 if (cryptoData) { res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey)); } return res; }, error => { return Promise.reject(new Error(error)); } ); window._request = axios export default axios; service.interceptors.response.use( res => { const status = res.data.error_code || res.data.code || res.status const statusWhiteList = website.statusWhiteList || [] const message = res.data.msg || res.data.error_description || res.data.message || '系统错误' const config = res.config const cryptoData = config.cryptoData === true if (status === 511) { ElMessage({ message: '请联系运维人员上传密钥证书!', type: 'error', }) // 获取当前路由名称 const currentRouteName = router.currentRoute.value.name // 如果当前路由不是登录页,则跳转到登录页 if (currentRouteName !== '登录页') { store.dispatch('FedLogOut').then(() => router.push({ path: '/login', }) ) } return Promise.reject(new Error(message)) } //如果在白名单里则自行catch逻辑处理 if (statusWhiteList.includes(status)) return Promise.reject(res) // 如果是401并且没有重试过,尝试刷新token if (status === 401 && !_retry) { // 标记此请求已尝试刷新token _retry = true // 调用RefreshToken action来刷新token return store .dispatch('RefreshToken') .then(() => { const meta = config.meta || {} const isToken = meta.isToken === false const cryptoToken = config.cryptoToken === true // 获取刷新后的token const token = getToken() if (token && !isToken) { config.headers[website.tokenHeader] = cryptoToken ? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey) : 'bearer ' + token } // 重新发送原来的请求 return service(config) }) .catch(() => { // 首次报错时提示 if (!isErrorShown) { isErrorShown = true ElMessage({ message: message, type: 'error', }) } // 清除token信息 removeToken() removeRefreshToken() const env = import.meta.env.VITE_APP_ENV // 重定向到登录页 // store.dispatch('FedLogOut').then(() => router.push({ // path: '/login' // })); env === 'development' ? store.dispatch('FedLogOut').then(() => router.push({ path: '/login', }) ) : store.dispatch('FedLogOut').then(() => window.location.replace(`${adminUrl}#/login`)) return Promise.reject(new Error(message)) }) } // 如果是401并且已经重试过,直接跳转到登录页面 if (status === 401 && _retry) { // 首次报错时提示 if (!isErrorShown) { isErrorShown = true ElMessage({ message: '用户令牌过期,请重新登录', type: 'error', }) } // 清除token信息 removeToken() removeRefreshToken() // 重定向到登录页 // store.dispatch('FedLogOut').then(() => router.push({ // path: '/login' // })); const env = import.meta.env.VITE_APP_ENV env === 'development' ? store.dispatch('FedLogOut').then(() => router.push({ path: '/login', }) ) : store.dispatch('FedLogOut').then(() => window.location.replace(`${adminUrl}#/login`)) return Promise.reject(new Error(message)) } // 如果请求为oauth2错误码则首次报错时提示 if (status > 2000 && !validatenull(res.data.error_description)) { // 首次报错时提示 if (!isErrorShown) { isErrorShown = true ElMessage({ message: message, type: 'error', }) } return Promise.reject(new Error(message)) } // 如果请求为非200默认统一处理 if (status !== 200 && status !== 0) { if (!isErrorShown) { ElMessage({ message: message, type: 'error', }) } return Promise.reject(new Error(message)) } // 解析加密报文 if (cryptoData) { res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey)) } return res }, error => { return Promise.reject(new Error(error)) } ) window._request = service export default service applications/task-work-order/src/axiosXT.js
New file @@ -0,0 +1,73 @@ import axios from 'axios' import store from '@/store/' import { serialize } from '@/utils/util' import { getToken, removeToken, removeRefreshToken } from '@/utils/auth' import { isURL, validatenull } from '@/utils/validate' import { ElMessage } from 'element-plus' import website from '@/config/website' import { Base64 } from 'js-base64' import { baseUrl } from '@/config/env' import crypto from '@/utils/crypto' // 全局未授权错误提示状态,只提示一次 const { VITE_APP_API_XT } = import.meta.env // 创建作用域的 axios 实例 const serviceXT = axios.create({ // 超时时间设置为10分钟,部分接口上传比较慢,如固件上传 timeout: 600000, baseURL: VITE_APP_API_XT, //返回其他状态码 validateStatus: function (status) { return status >= 200 && status <= 500 // 默认的 }, //跨域请求,允许保存cookie withCredentials: true, }) //http request拦截 serviceXT.interceptors.request.use( config => { //headers判断是否需要 const authorization = config.authorization === false if (!authorization) { config.headers['Authorization'] = `Basic ${Base64.encode(`${website.clientId}:${website.clientSecret}`)}` } const meta = config.meta || {} //headers中配置text请求 if (config.text === true) { config.headers['Content-Type'] = 'text/plain' } //headers中配置serialize为true开启序列化 if (config.method === 'post' && meta.isSerialize === true) { config.data = serialize(config.data) } return config }, error => { return Promise.reject(error) } ) //http response拦截 serviceXT.interceptors.response.use( res => { const status = res.data.error_code || res.data.code || res.status const statusWhiteList = website.statusWhiteList || [] const message = res?.data?.msg || res?.data?.message || '系统错误' //如果在白名单里则自行catch逻辑处理 if (statusWhiteList.includes(status)) return Promise.reject(res) // 如果请求为非200默认统一处理 if (status !== 200 && status !== 0) { ElMessage({ message: message, type: 'error', }) return Promise.reject(new Error(message)) } return res }, error => { return Promise.reject(new Error(error)) } ) export default serviceXT applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/FormDiaLog.vue
@@ -256,19 +256,26 @@ // 获取工单列表 async function getWorkOrderList() { const res = await gdWorkOrderPageApi({ size: 999 }) const res = await gdWorkOrderPageApi({ size: 999, }) workOrderList.value = res?.data?.data?.records ?? [] } // 获取飞手列表 async function getFlyerList() { const res = await gdFlyerPageApi({ size: 999 }) const res = await gdFlyerPageApi({ size: 999, // todo 需要传入前面的巡查类型 // patrolTaskType: '1', }) flyerList.value = res?.data?.data?.records ?? [] } // 获取设备列表 async function getDeviceList() { const res = await gdManageDeviceListApi() const res = await gdManageDeviceListApi({ // TODO 需要传入ids设备 // ids: }) deviceList.value = res?.data?.data ?? [] } applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue
@@ -1,5 +1,9 @@ <template> <basic-container> <el-tabs class="gd-tabs" v-model="activeName" @tab-click="tabsClick"> <el-tab-pane label="全部 " name="all"></el-tab-pane> <el-tab-pane label="我的任务工单" name="my"></el-tab-pane> </el-tabs> <el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form"> <el-form-item label="任务名称" prop="patrolTaskName"> <el-input @@ -68,9 +72,7 @@ </el-form> <div class="gd-table-toolbar"> <!-- <el-button color="#F2F3F5" @click="requester = !requester">我是{{ requester ? '需求方' : '服务方' }}</el-button>--> <el-button :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">新增</el-button> <!-- <el-button :icon="Delete" color="#4C34FF" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>--> <el-button v-if="requester" :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">新增</el-button> </div> <div class="gd-table-container" v-loading="loading"> @@ -159,7 +161,7 @@ deviceLoadDemand: [], // 设备负载需求 taskStatus: [], // 巡查任务状态 }) const activeName = ref('all') provide('dictObj', dictObj) provide('requester', requester) @@ -168,7 +170,12 @@ const range = dateRangeFormat(dateRange.value) loading.value = true try { const res = await gdPatrolTaskPageApi({ ...searchParams.value, startTime: range[0], endTime: range[1] }) const res = await gdPatrolTaskPageApi({ ...searchParams.value, startTime: range[0], endTime: range[1], createUser: activeName.value === 'my' ? store.state.user.userInfo.user_id : '', }) list.value = res?.data?.data?.records ?? [] total.value = res?.data?.data?.total ?? 0 } finally { @@ -176,6 +183,12 @@ } } function tabsClick(tab, event) { activeName.value = tab.props.name resetForm() handleSearch() } // 查询 function handleSearch() { searchParams.value.current = 1 applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue
@@ -179,48 +179,35 @@ </div> <template #footer> <!-- <el-button color="#F2F3F5" @click="visible = false">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>--> <el-button class="save-btn" color="#4C34FF" v-if="(!dialogReadonly && !formData.id) || suddenlyEdit" :loading="submitting" :disabled="submitting" @click="handleSubmit" > 发布 </el-button> <template v-if="requester"> <el-button v-if="['11', '31'].includes(gdStatus) || suddenlyEdit || (!dialogReadonly && !formData.id)" :loading="submitting" :disabled="submitting" @click="handleSubmit" > {{ gdStatus === '11' ? '发布' : '提交修改' }} 发布 </el-button> <el-button v-if="['11', '21', '23', '24', '25', '31', '60'].includes(gdStatus)" @click="viewDescription"> {{ gdStatusObj[gdStatus].reason }} </el-button> <el-button @click="addDescription" v-if="['20', '24', '25', '50'].includes(gdStatus)"> {{ gdStatus === '50' ? '结算' : '申请取消' }} </el-button> <el-button @click="requestModification" v-if="['20', '25'].includes(gdStatus)">申请修改</el-button> </template> <template v-else> <template v-if="requester"> <el-button class="save-btn" color="#4C34FF" v-if="editStatus.includes(gdStatus)" :loading="submitting" :disabled="submitting" @click="handleSubmit" > 发布 </el-button> <el-button color="#F2F3F5" v-if="gdStatus === '11'" @click="addDescription(2, 'view')">拒单原因</el-button> <el-button color="#F2F3F5" @click="statusChange(3)" v-if="gdStatus === '20'">申请取消</el-button> <el-button color="#4C34FF" @click="requestModification" v-if="gdStatus === '20'" class="save-btn"> 申请修改 </el-button> </template> <template v-else> <el-button color="#F2F3F5" v-if="gdStatus === '10'" @click="addDescription(2)">拒绝接单</el-button> <el-button color="#4C34FF" v-if="gdStatus === '10'" @click="statusChange(1)" class="save-btn">接单</el-button> <el-button color="#F2F3F5" v-if="gdStatus === '22'" @click="statusChange(7)">同意修改</el-button> <el-button color="#4C34FF" v-if="gdStatus === '22'" @click="statusChange(8)" class="save-btn"> 不同意修改 </el-button> <el-button color="#F2F3F5" v-if="gdStatus === '21'" @click="statusChange(5)">同意取消</el-button> <el-button color="#4C34FF" v-if="gdStatus === '21'" @click="statusChange(6)" class="save-btn"> 不同意取消 </el-button> <el-button color="#F2F3F5" v-if="gdStatus === '30'" @click="addDescription(9)">协商修改</el-button> </template> <el-button v-if="['21', '23'].includes(gdStatus)" @click="viewDescription"> {{ gdStatusObj[gdStatus].reason }} </el-button> <el-button v-if="gdStatus === '10'" @click="addDescription">拒绝接单</el-button> <el-button v-if="gdStatus === '10'" @click="statusChange(1)">接单</el-button> <el-button v-if="gdStatus === '22'" @click="statusChange(7)">同意修改</el-button> <el-button v-if="gdStatus === '22'" @click="statusChange(8)">不同意修改</el-button> <el-button v-if="gdStatus === '21'" @click="statusChange(5)">同意取消</el-button> <el-button v-if="gdStatus === '21'" @click="statusChange(6)">不同意取消</el-button> <el-button v-if="gdStatus === '30'" @click="addDescription">协商修改</el-button> </template> </template> @@ -286,11 +273,27 @@ let drawPolygonExample let pointList = ref([]) const requester = inject('requester') const editStatus = inject('editStatus') const suddenlyEdit = ref(false) const processList = ref([]) let viewPlane const hasPatrolTaskList = computed(() => ['30', '40', '50', '60'].includes(String(formData.value.workOrderStatus))) const gdStatusObj = { '0': { reason: '' }, '10': { reason: '拒绝原因', operationType: '2' }, '11': { reason: '拒绝原因' }, '20': { reason: '取消原因', operationType: '3' }, '21': { reason: '取消原因' }, '22': { reason: '驳回原因' }, '23': { reason: '取消原因' }, '24': { reason: '驳回原因', operationType: '3' }, '25': { reason: '驳回原因', operationType: '3' }, '30': { reason: '修改原因', operationType: '9' }, '31': { reason: '修改原因' }, '40': { reason: '' }, '50': { reason: '情况说明', operationType: '10' }, '60': { reason: '情况说明' }, } // 工单状态:0草稿、10发布中_接单中、11发布中_拒绝接单、20响应中_待拆分、21响应中_申请取消、22响应中_申请修改、23响应中_已取消、 // 24响应中_拒绝取消、25响应中_拒绝修改 30执行中_待全部完成、31执行中_协商修改、40完成待验_待全部验收、50验收通过_待结算、60结算完成_已结算 @@ -302,9 +305,13 @@ executeStartTime: fieldRules(true), deviceLoadDemand: fieldRules(true), } // 操作类型:1接单,2拒接接单,3申请取消,4申请修改, 5同意取消 6不同意取消 7.同意修改 8.不同意修改 9.协商修改 function statusChange(operationType) { gdWorkOrderHandleStatusApi({ operationType: operationType, workOrderId: formData.value.id }).then(res => { gdWorkOrderHandleStatusApi({ operationType: operationType, workOrderId: formData.value.id, }).then(res => { visible.value = false emit('success') }) @@ -326,24 +333,24 @@ } // 填写说明 function addDescription(type, mode = 'add') { let formLabel = '' switch (type) { case 2: formLabel = '拒绝原因' break case 9: formLabel = '修改原因' break } function addDescription() { rejectVisible.value = true nextTick(() => { refuseOrderDialogRef.value.open({ mode, mode: 'add', row: formData.value, type, formLabel, type: gdStatusObj[gdStatus.value].operationType, formLabel: gdStatusObj[gdStatus.value].reason, }) }) } function viewDescription() { rejectVisible.value = true refuseOrderDialogRef.value.open({ mode: 'view', row: formData.value, formLabel: gdStatusObj[gdStatus.value].reason, }) } @@ -365,6 +372,7 @@ function loadDemandChange() { formData.value.recommendDeviceIds = '' selectedDevices.value = [] getDeviceList() syncSelection() } @@ -380,8 +388,10 @@ const isValid = await formRef.value?.validate().catch(() => false) if (!isValid) return if (!pointList.value.length) { ElMessage.warning('请绘制范围') return return ElMessage.warning('请绘制范围') } if (!formData.value.recommendDeviceIds) { return ElMessage.warning('请选择设备') } submitting.value = true try { @@ -436,6 +446,8 @@ const val = cartesian3Convert(item, viewer) return { ...val, lng: val.longitude, lat: val.latitude } }) formData.value.recommendDeviceIds = '' selectedDevices.value = [] await getDeviceList() syncSelection() } @@ -502,10 +514,11 @@ } import droneIcon from '@/assets/images/orderView/orderManage/drone.png' function renderingDevice(list) { // 渲染红点 // 渲染红点 list.forEach(item => { const position = Cesium.Cartesian3.fromDegrees(item.longitude || 115.00, item.latitude || 27.11) const position = Cesium.Cartesian3.fromDegrees(item.longitude || 115.0, item.latitude || 27.11) viewer.entities.add({ position: position, billboard: { @@ -554,16 +567,19 @@ .content { display: flex; gap: 0 20px; .leftBox { width: 0; flex: 1; display: flex; flex-direction: column; .gd-cesium { width: 100%; height: 423px; } } .rightBox { .title { font-weight: 500; @@ -571,6 +587,7 @@ color: #272e37; } } .processBox { width: 312px; } applications/task-work-order/src/views/orderView/orderManage/orderManage/RefuseOrderDialog.vue
@@ -100,11 +100,19 @@ } let formLabelStr = ref('原因') // 打开弹框 // 操作类型:1接单,2拒接接单,3申请取消,4申请修改, 5同意取消 6不同意取消 7.同意修改 8.不同意修改 9.协商修改 async function open({ mode = 'add', row, formLabel, type } = {}) { dialogMode.value = mode formLabelStr.value = formLabel formData.value = { workOrderId: row.id, operationType: type, rejectReason: row.rejectReason } formData.value = { workOrderId: row.id, operationType: type, } if (dialogMode.value !== 'add'){ formData.value.rejectReason = row.rejectReason } } defineExpose({ open }) applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue
@@ -107,8 +107,8 @@ {{ dayjs(row.executeEndTime).format('YYYY-MM-DD') }} </template> </el-table-column> <el-table-column prop="serviceParty" show-overflow-tooltip label="服务方名称" /> <el-table-column prop="createUserName" show-overflow-tooltip label="需求方名称" /> <el-table-column prop="serviceParty" show-overflow-tooltip label="服务方名称" /> <el-table-column prop="createTime" show-overflow-tooltip label="工单创建时间" /> <el-table-column label="操作" class-name="operation-btns"> <template v-slot="{ row }"> @@ -229,8 +229,6 @@ dialogRef.value?.open({ mode, row }) }) } const editStatus = ['0','11','24','25'] provide('editStatus', editStatus) // 工单状态:0草稿、10发布中_接单中、11发布中_拒绝接单、20响应中_待拆分、 // 21响应中_申请取消、22响应中_申请修改、23响应中_已取消、24响应中_拒绝取消、 // 25响应中_拒绝修改 30执行中_待全部完成、31执行中_协商修改、40完成待验_待全部验收、 @@ -238,7 +236,7 @@ function openFormChange(row) { dialogVisible.value = true nextTick(() => { const mode = editStatus.includes(row.workOrderStatus) && requester.value ? 'edit' : 'view' const mode = ['11','25'].includes(row.workOrderStatus) && requester.value ? 'edit' : 'view' dialogRef.value?.open({ mode, row }) }) } applications/task-work-order/vite.config.mjs
@@ -6,7 +6,7 @@ // https://vitejs.dev/config/ export default ({ mode, command }) => { const env = loadEnv(mode, fileURLToPath(new URL("./env", import.meta.url))) const { VITE_APP_ENV, VITE_APP_BASE, VITE_APP_URL } = env const { VITE_APP_ENV, VITE_APP_BASE, VITE_APP_URL,VITE_APP_API_XT,VITE_APP_URL_XT } = env // 判断是打生产环境包 const isProd = VITE_APP_ENV === 'production' @@ -41,11 +41,17 @@ port: 5178, // host: '192.168.1.178', proxy: { '/apiXT': { target: VITE_APP_URL_XT, changeOrigin: true, rewrite: path => path.replace(/^\/apiXT/, ''), }, '/api': { target: VITE_APP_URL, changeOrigin: true, rewrite: path => path.replace(/^\/api/, ''), } }, }, }, assetsInclude: ['**/*.gltf','**/*.docx','**/*.pdf'],