From efb3b5e84ed4493f82f30ed3c3dcdff0fd5a8083 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Fri, 16 Jan 2026 10:55:55 +0800
Subject: [PATCH] feat:任务工单后台地图

---
 applications/task-work-order/src/utils/cesium/useBoundary.js |  210 ++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 147 insertions(+), 63 deletions(-)

diff --git a/applications/task-work-order/src/utils/cesium/useBoundary.js b/applications/task-work-order/src/utils/cesium/useBoundary.js
index d04ffb9..053ddd6 100644
--- a/applications/task-work-order/src/utils/cesium/useBoundary.js
+++ b/applications/task-work-order/src/utils/cesium/useBoundary.js
@@ -6,19 +6,17 @@
 import { useStore } from 'vuex'
 import { getDeviceRegion, getDeviceRegionCount } from '@/api/home/aggregation'
 import userStore from '@/store/modules/user'
-import { areaCodeToArr, getContourByCode } from '@/utils/cesium/mapUtil'
-// import { getEllipse } from '@/hooks/useSingleDroneMap/useSingleDroneMap'
+import { areaCodeToArr, getContourByCode, getDockPolyLine, getLnglatAltitude } from '@/utils/cesium/mapUtil'
 import getBaseConfig from '@/buildConfig/config'
 import { getDroneStatusImage } from '@/utils/stateToImageMap/drone'
-import {
-	GroundCirclePrimitiveManager
-} from '@/utils/mapUtils'
+import { GroundCirclePrimitiveManager } from '@/utils/mapUtils'
+
+import lowBattery from '@/assets/images/aiNowFly/low-battery.svg'
+import mediumBattery from '@/assets/images/aiNowFly/medium-battery.svg'
+import fullBattery from '@/assets/images/aiNowFly/full-charge.svg'
 
 const { VITE_APP_BASE, VITE_APP_ENV, VITE_APP_REGION_URL } = import.meta.env
-// const { singleDockSystem } = getBaseConfig()
 const { singleDockSystem } = getBaseConfig()
-
-
 
 /**
  * 使用通用的边界
@@ -26,8 +24,15 @@
  * @param options
  */
 export const useBoundary = (viewer, options = {}) => {
-	const { multiple = 1.4, dockOptions = {}, boundaryChange, boundaryColor, dockRangeType, useDockHeight = false } = options
-	const { scrollShowDock = false, showDock = false } = dockOptions
+	const {
+		multiple = 1.4,
+		dockOptions = {},
+		boundaryChange,
+		boundaryColor,
+		dockRangeType,
+		useDockHeight = false,
+	} = options
+	const { scrollShowDock = false, showDock = false, showDockNameAndBattery = false } = dockOptions
 
 	const manager = new GroundCirclePrimitiveManager()
 	manager.init(viewer)
@@ -56,7 +61,7 @@
 	}
 
 	// 获取设备列表
-	async function getDeviceRegionFun () {
+	async function getDeviceRegionFun() {
 		const res = await getDeviceRegion({ areaCode: selectedAreaCode })
 		return res?.data?.data || []
 	}
@@ -100,60 +105,127 @@
 		return await res.json()
 	}
 
+	function getBase64Image(imgUrl) {
+		const img = new Image()
+		const canvas = document.createElement('canvas')
+		const ctx = canvas.getContext('2d')
+
+		return new Promise(resolve => {
+			img.onload = function () {
+				canvas.width = img.width
+				canvas.height = img.height
+				ctx.drawImage(img, 0, 0)
+				const base64 = canvas.toDataURL('image/png')
+				resolve(base64)
+			}
+			img.src = imgUrl
+		})
+	}
+
+	function batteryLevelSvg(batteryNum) {
+		if (batteryNum <= 30) {
+			return lowBattery
+		} else if (batteryNum > 30 && batteryNum <= 60) {
+			return mediumBattery
+		} else {
+			return fullBattery
+		}
+	}
+
+	async function nestSVG(nickname, batteryImgUrl, batteryNum) {
+		let txt = nickname.length < 4 ? 130 : nickname.length * 38
+		let tranx = nickname.length < 4 ? 86 : nickname.length * 26 + 3
+		const batteryBase64Image = await getBase64Image(batteryImgUrl)
+		const svg = `
+		  <svg width="${txt}" height="100" xmlns="http://www.w3.org/2000/svg">
+			<g transform="translate(${tranx}, 10)">
+				<!-- 电池图标 -->
+				<image href="${batteryBase64Image}" x="0" y="2" width="12" height="13"/>
+				<!-- 电量值 -->
+				<text x="12" y="10" font-size="14" fill="${
+					batteryNum <= 30 ? '#FF604B' : batteryNum >= 60 ? '#40FF5C' : '#00FFF2'
+				}"  text-anchor="start" dominant-baseline="middle" font-weight="bolder" font-family="Source Han Sans CN" text-rendering="geometricPrecision">
+					${batteryNum}%
+				</text>
+			</g>
+		  </svg>
+		`
+		return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
+	}
+
 	// 无人机散点渲染
 	const droneSplashed = () => {
-		let showDockList = showDockSnList === null
-			? _.cloneDeep(initDockList)
-			: initDockList.filter(item => showDockSnList.includes(item.device_sn))
+		manager.removeAll()
+		let showDockList =
+			showDockSnList === null
+				? _.cloneDeep(initDockList)
+				: initDockList.filter(item => showDockSnList.includes(item.device_sn))
 
-		showDockList.forEach((item, index) => {
+		showDockList.forEach(async (item, index) => {
+			if (item.status === 'OFFLINE') return
 			let polyline = {}
 
 			if (dockRangeType === 2) {
 				manager.addCircleOutline({
 					data: {
 						lng: item.longitude,
-						lat: item.latitude
+						lat: item.latitude,
 					},
-					frameColor: Cesium.Color.fromCssColorString('#7FFFD4')
+					frameColor: Cesium.Color.fromCssColorString('#7FFFD4'),
 				})
 			} else {
 				manager.addCircle({
 					data: {
 						lng: item.longitude,
-						lat: item.latitude
+						lat: item.latitude,
 					},
-					materialColor: Cesium.Color.CORNFLOWERBLUE.withAlpha(0.3)
+					materialColor: item.color ? item.color : Cesium.Color.CORNFLOWERBLUE.withAlpha(0.3),
 				})
 			}
 
 			if (useDockHeight) {
-				polyline = {
-					positions: new Cesium.CallbackProperty(() => {
-						const pointPosition = Cesium.Cartesian3.fromDegrees(+item.longitude, +item.latitude, +item.height)
-						if (!pointPosition) return []
-						// 获取点的位置
-						const cartographic = Cesium.Cartographic.fromCartesian(pointPosition)
-						// 获取地形高度
-						const terrainHeight = viewer.scene.globe.getHeight(cartographic) || 0
-						// 创建地面点位置
-						const groundPosition = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, terrainHeight)
-
-						return [pointPosition, groundPosition]
-					}, false),
-					width: 0.5,
-					material: Cesium.Color.WHITE,
-				}
+				polyline = getDockPolyLine(item, viewer)
 			}
-
+			const batteryImgUrl = batteryLevelSvg(item.capacity_percent)
+			const combinedImage = await nestSVG(item.nickname, batteryImgUrl, item.capacity_percent)
+			let pointObj = {}
+			try {
+				pointObj = await getLnglatAltitude(item.longitude, item.latitude, viewer)
+			} catch (e) {}
+			const position = Cesium.Cartesian3.fromDegrees(
+				+item.longitude,
+				+item.latitude,
+				useDockHeight ? +item.height : pointObj?.height || 0
+			)
+			if (showDockNameAndBattery) {
+				// 带电量
+				dockSource.entities.add({
+					position,
+					billboard: {
+						// new Cesium.ConstantProperty(pointData.status === 'OFFLINE' ? endingImg : unSelectedOnline),
+						image: combinedImage,
+					},
+				})
+			}
 			dockSource.entities.add({
-				position: Cesium.Cartesian3.fromDegrees(+item.longitude, +item.latitude, !useDockHeight ? 0 : +item.height),
+				label: {
+					text: item.nickname,
+					font: 'bold 16px Source Han Sans CN',
+					fillColor: Cesium.Color.WHITE,
+					outlineColor: Cesium.Color.BLACK,
+					outlineWidth: 2,
+					style: Cesium.LabelStyle.FILL_AND_OUTLINE,
+					verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+					pixelOffset: new Cesium.Cartesian2(0, -24),
+				},
+				position,
 				polyline,
 				billboard: {
 					image: getDroneStatusImage(item.status),
 					width: 60,
 					height: 60,
-					eyeOffset: new Cesium.Cartesian3(0, 0, -5)
+					disableDepthTestDistance: Number.POSITIVE_INFINITY,
+					eyeOffset: new Cesium.Cartesian3(0, 0, -5),
 				},
 			})
 		})
@@ -186,7 +258,7 @@
 							positions: positions,
 							width: 5, // 直接设置宽度
 							clampToGround: true,
-							material: Cesium.Color.fromCssColorString(boundaryColor)
+							material: Cesium.Color.fromCssColorString(boundaryColor),
 						},
 						polygon,
 					})
@@ -205,7 +277,7 @@
 						positions: positions,
 						width: 1, // 直接设置宽度
 						clampToGround: true,
-						material: Cesium.Color.fromCssColorString(boundaryColor)
+						material: Cesium.Color.fromCssColorString(boundaryColor),
 					},
 				})
 			})
@@ -217,7 +289,6 @@
 	// 打开边界
 	const openContour = async () => {
 		const areaCode = selectedAreaCode || userAreaCode
-
 		viewer.scene.postRender.removeEventListener(determineScaling)
 		if (!areaCode) return
 		const hierarchy = areaCodeToArr(areaCode.slice(0, 6))
@@ -258,25 +329,29 @@
 			const outlineGJson = await getContourByCode(areaCode.slice(0, 6))
 			scalingJudgment.forEach(item => item.show && (item.outline = outlineGJson))
 			renderOutline(scalingJudgment[(hierarchy.length - 3) * -1])
-		} catch (e) { }
-		viewer.scene.postRender.addEventListener(determineScaling)
+		} catch (e) {}
+		try {
+			viewer?.scene?.postRender?.addEventListener(determineScaling)
+		}catch (e) {
+
+		}
 	}
 
 	// 关闭边界
-	function closeContour () {
+	function closeContour() {
 		removeAllEntities()
 		viewer.scene.postRender.removeEventListener(determineScaling)
 	}
 
 	// 飞向边界
-	async function flyToBoundary () {
+	async function flyToBoundary() {
 		// 单机巢系统
 		if (singleDockSystem) {
 			if (!initDockList.length) {
 				initDockList = await getDeviceRegionFun()
 			}
 			viewer?.camera.flyTo({
-				destination: Cesium.Cartesian3.fromDegrees(initDockList[0].longitude, initDockList[0].latitude, 24000),
+				destination: Cesium.Cartesian3.fromDegrees(initDockList[0].longitude, initDockList[0].latitude, 22000),
 				duration: 0,
 			})
 			return
@@ -286,7 +361,8 @@
 		const dataSource = await Cesium.GeoJsonDataSource.load(gJson)
 		if (!dataSource) return
 		viewer.dataSources.add(dataSource)
-
+		// 获取多边形边界所有点
+		let positionList = []
 		dataSource.entities.values.forEach(function (entity) {
 			if (entity.polygon) {
 				// 获取多边形的边界球
@@ -295,18 +371,16 @@
 					let cartographic = Cesium.Cartographic.fromCartesian(item)
 					let lng = Cesium.Math.toDegrees(cartographic.longitude) // 经度
 					let lat = Cesium.Math.toDegrees(cartographic.latitude) // 纬度
-
 					return [_.round(lng, 6), _.round(lat, 6)]
 				})
-
-				const newBox = boxTransformScale(curPolygonPosition, multiple)
-
-				viewer.camera.flyTo({
-					destination: Cesium.Rectangle.fromDegrees(...newBox),
-					offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 0),
-					duration: 0.5,
-				})
+				positionList = positionList.concat(curPolygonPosition)
 			}
+		})
+		const newBox = boxTransformScale(positionList, multiple)
+		viewer.camera.flyTo({
+			destination: Cesium.Rectangle.fromDegrees(...newBox),
+			offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 0),
+			duration: 0.5,
 		})
 
 		dataSource.entities.values.forEach(entity => {
@@ -320,7 +394,7 @@
 	 * 设置要显示的机巢列表
 	 * @param dockSns - 要显示的机巢设备序列号数组。如果为 undefined,则显示所有已初始化的机巢;如果为空数组,则不显示任何机巢。
 	 */
-	function setShowDock (dockSns) {
+	function setShowDock(dockSns) {
 		if (dockSns === undefined) {
 			showDockSnList = null
 		} else if (dockSns.length === 0) {
@@ -328,21 +402,31 @@
 		} else {
 			showDockSnList = dockSns
 		}
-
 		removeDockCover()
-
-		if (active === '县') droneSplashed()
+		if (!showDock) return
+		if (scrollShowDock ? active === '县' : true) {
+			droneSplashed()
+		}
 	}
-
+	function setDockCoverColor(arr) {
+		initDockList.forEach(item => {
+			arr.forEach(dock => {
+				item.color = dock.device_sn === item.device_sn ? dock.color : null
+			})
+		})
+		if (scrollShowDock ? active === '县' : true) {
+			droneSplashed()
+		}
+	}
 	onBeforeUnmount(() => {
 		manager.destroy()
 	})
-
 
 	return {
 		openContour,
 		closeContour,
 		flyToBoundary,
-		setShowDock
+		setShowDock,
+		setDockCoverColor,
 	}
 }

--
Gitblit v1.9.3