0906
This commit is contained in:
		| @ -1,17 +1,63 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="p5" style="width: 100%; height: calc(100vh - 84px)" v-loading="loading"> |   <div class="p5" style="width: 100%; height: calc(100vh - 84px)" v-loading="loading"> | ||||||
|  |     <!-- 返回按钮区域 --> | ||||||
|  |     <div style="position: absolute; top: 20px; left: 20px; z-index: 100; display: flex; gap: 10px"> | ||||||
|  |       <el-button type="primary" icon="ArrowLeft" @click="handleBack">返回</el-button> | ||||||
|  |       <el-button type="success" icon="Play" @click="handleStart">开始</el-button> | ||||||
|  |       <el-button type="warning" icon="Pause" @click="handleStop">停止</el-button> | ||||||
|  |     </div> | ||||||
|     <div id="TrajectoryEarth" style="width: 100%; height: 100%"></div> |     <div id="TrajectoryEarth" style="width: 100%; height: 100%"></div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup name="equipmentGPS"> | <script setup name="equipmentGPS"> | ||||||
| import { ref, onMounted, onUnmounted } from 'vue'; | import { ref, onMounted, onUnmounted } from 'vue'; | ||||||
|  | import { ElButton } from 'element-plus'; | ||||||
| import { ElMessage } from 'element-plus'; | import { ElMessage } from 'element-plus'; | ||||||
| import { useRoute } from 'vue-router'; | import { useRoute, useRouter } from 'vue-router'; // 补充导入useRouter | ||||||
| import { getFootNote } from '@/api/equipment/index'; | import { getFootNote } from '@/api/equipment/index'; | ||||||
|  | import { ModelPathMover } from './index.js'; | ||||||
|  |  | ||||||
| const route = useRoute(); | const route = useRoute(); | ||||||
|  | const router = useRouter(); // 初始化router | ||||||
| const loading = ref(true); | const loading = ref(true); | ||||||
|  | let modelMover = null; | ||||||
|  |  | ||||||
|  | // 返回上一页 | ||||||
|  | const handleBack = () => { | ||||||
|  |   router.back(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // 开始轨迹运动 | ||||||
|  | const handleStart = () => { | ||||||
|  |   if (modelMover && typeof modelMover.start === 'function') { | ||||||
|  |     try { | ||||||
|  |       modelMover.start(); | ||||||
|  |       ElMessage.success('轨迹已开始播放'); | ||||||
|  |     } catch (error) { | ||||||
|  |       console.error('启动轨迹失败:', error); | ||||||
|  |       ElMessage.error('启动轨迹失败'); | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     ElMessage.warning('轨迹未初始化,请稍后再试'); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // 停止轨迹运动 | ||||||
|  | const handleStop = () => { | ||||||
|  |   if (modelMover && typeof modelMover.stop === 'function') { | ||||||
|  |     try { | ||||||
|  |       modelMover.stop(); | ||||||
|  |       ElMessage.success('轨迹已停止'); | ||||||
|  |     } catch (error) { | ||||||
|  |       console.error('停止轨迹失败:', error); | ||||||
|  |       ElMessage.error('停止轨迹失败'); | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     ElMessage.warning('轨迹未初始化,请稍后再试'); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
| let earthInstance = null; | let earthInstance = null; | ||||||
| let data = [ | let data = [ | ||||||
|   { |   { | ||||||
| @ -146,34 +192,39 @@ const renderRange = (data) => { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   try { |   try { | ||||||
|     const positions = data.map((point) => { |     const positions = data.map((item) => { | ||||||
|       return Cesium.Cartesian3.fromDegrees(point.locLongitude, point.locLatitude, point.locAltitude || 0); |       return { | ||||||
|  |         lon: item.locLongitude, | ||||||
|  |         lat: item.locLatitude, | ||||||
|  |         height: item.locAltitude || 0, | ||||||
|  |         name: item.name || '' | ||||||
|  |       }; | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     const entity = earthInstance.viewer.entities.add({ |     modelMover = new ModelPathMover(window.Earth3.viewer, { | ||||||
|       polyline: { |       // modelUrl: './air.glb', // 模型URL | ||||||
|       positions: positions, |       positions: positions, | ||||||
|         width: 5, |       speed: 50, // 移动速度 | ||||||
|         material: Cesium.Color.RED, |       loop: true, // 循环运动 | ||||||
|         clampToGround: true |       iconUrl: '/image/Foot.png', // 模型上方图标 | ||||||
|       } |       baseHeight: 10 // 外部传入的统一高度(米) | ||||||
|     }); |     }); | ||||||
|  |     window.modelMover = modelMover; | ||||||
|     // 调整视角以适应轨迹 |  | ||||||
|     earthInstance.viewer.flyTo(entity); |  | ||||||
|   } catch (err) { |   } catch (err) { | ||||||
|     console.error('渲染轨迹失败:', err); |     console.error('渲染轨迹失败:', err); | ||||||
|     ElMessage.error('渲染轨迹失败'); |     ElMessage.error('渲染轨迹失败'); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // |  | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
|   createEarth(); |   createEarth(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // |  | ||||||
| onUnmounted(() => { | onUnmounted(() => { | ||||||
|  |   if (modelMover) { | ||||||
|  |     modelMover.stop(); // 组件卸载时停止轨迹 | ||||||
|  |     modelMover = null; | ||||||
|  |   } | ||||||
|   if (earthInstance) { |   if (earthInstance) { | ||||||
|     earthInstance.destroy(); |     earthInstance.destroy(); | ||||||
|     earthInstance = null; |     earthInstance = null; | ||||||
|  | |||||||
							
								
								
									
										392
									
								
								src/views/equipment/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								src/views/equipment/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,392 @@ | |||||||
|  | export class ModelPathMover { | ||||||
|  |   /** | ||||||
|  |    * 构造函数 | ||||||
|  |    * @param {Cesium.Viewer} viewer - Cesium viewer实例 | ||||||
|  |    * @param {Object} options - 配置选项 | ||||||
|  |    * @param {Number} [options.baseHeight=0] - 外部传入的统一高度(米),将叠加到所有位置点上 | ||||||
|  |    */ | ||||||
|  |   constructor(viewer, options) { | ||||||
|  |     this.viewer = viewer; | ||||||
|  |     this.modelUrl = options.modelUrl; | ||||||
|  |     this.positions = options.positions || []; | ||||||
|  |     this.speed = options.speed || 10; // 米/秒 | ||||||
|  |     this.loop = options.loop !== undefined ? options.loop : true; | ||||||
|  |     this.pathStyle = options.pathStyle || { | ||||||
|  |       color: Cesium.Color.YELLOW, | ||||||
|  |       width: 3 | ||||||
|  |     }; | ||||||
|  |     this.markerStyle = options.markerStyle || { | ||||||
|  |       color: Cesium.Color.RED, | ||||||
|  |       pixelSize: 10, | ||||||
|  |       outlineColor: Cesium.Color.WHITE, | ||||||
|  |       outlineWidth: 2 | ||||||
|  |     }; | ||||||
|  |     this.iconUrl = options.iconUrl; | ||||||
|  |     this.iconSize = options.iconSize || 32; | ||||||
|  |     this.iconOffset = options.iconOffset || 0; | ||||||
|  |     this.headingOffset = options.headingOffset || 0; | ||||||
|  |     // 新增:接收外部传入的统一高度(默认0米) | ||||||
|  |     this.baseHeight = options.baseHeight || 0; | ||||||
|  |  | ||||||
|  |     // 内部状态(保持不变) | ||||||
|  |     this.modelEntity = null; | ||||||
|  |     this.pathEntity = null; | ||||||
|  |     this.markerEntities = []; | ||||||
|  |     this.iconEntity = null; | ||||||
|  |     this.currentIndex = 0; | ||||||
|  |     this.isMoving = false; | ||||||
|  |     this.lastTime = 0; | ||||||
|  |     this.animationFrameId = null; | ||||||
|  |     this.currentPosition = null; | ||||||
|  |     this.progress = 0; | ||||||
|  |     this.modelHeight = 0; | ||||||
|  |  | ||||||
|  |     // 初始化 | ||||||
|  |     this.init(); | ||||||
|  |  | ||||||
|  |     // 调试信息(增加baseHeight打印) | ||||||
|  |     console.log('ModelPathMover初始化完成', { | ||||||
|  |       positionCount: this.positions.length, | ||||||
|  |       speed: this.speed, | ||||||
|  |       loop: this.loop, | ||||||
|  |       baseHeight: this.baseHeight // 打印传入的统一高度 | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 初始化(保持不变) | ||||||
|  |    */ | ||||||
|  |   init() { | ||||||
|  |     if (this.positions.length < 2) { | ||||||
|  |       console.warn('至少需要2个位置点才能进行移动'); | ||||||
|  |     } | ||||||
|  |     this.createPath(); | ||||||
|  |     this.createMarkers(); | ||||||
|  |     this.createModel(); | ||||||
|  |     if (this.iconUrl) { | ||||||
|  |       this.createIcon(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 创建模型(修改:叠加baseHeight) | ||||||
|  |    */ | ||||||
|  |   createModel() { | ||||||
|  |     if (this.positions.length === 0) return; | ||||||
|  |  | ||||||
|  |     const firstPos = this.positions[0]; | ||||||
|  |     // 关键修改:在原始高度基础上叠加baseHeight | ||||||
|  |     const finalHeight = (firstPos.height || 0) + this.baseHeight; | ||||||
|  |     this.currentPosition = Cesium.Cartesian3.fromDegrees( | ||||||
|  |       firstPos.lon, | ||||||
|  |       firstPos.lat, | ||||||
|  |       finalHeight // 使用叠加后的高度 | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // 初始朝向计算(保持不变) | ||||||
|  |     let initialOrientation = Cesium.Transforms.headingPitchRollQuaternion(this.currentPosition, new Cesium.HeadingPitchRoll(0, 0, 0)); | ||||||
|  |  | ||||||
|  |     if (this.positions.length > 1) { | ||||||
|  |       const nextPos = this.positions[1]; | ||||||
|  |       // 计算下一个点时同样叠加baseHeight | ||||||
|  |       const nextFinalHeight = (nextPos.height || 0) + this.baseHeight; | ||||||
|  |       const nextCartesian = Cesium.Cartesian3.fromDegrees(nextPos.lon, nextPos.lat, nextFinalHeight); | ||||||
|  |       const heading = this.calculateHeading(this.currentPosition, nextCartesian); | ||||||
|  |       initialOrientation = Cesium.Transforms.headingPitchRollQuaternion(this.currentPosition, new Cesium.HeadingPitchRoll(heading, 0, 0)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.modelEntity = this.viewer.entities.add({ | ||||||
|  |       name: 'Moving Model', | ||||||
|  |       position: this.currentPosition, | ||||||
|  |       orientation: initialOrientation, | ||||||
|  |       model: { | ||||||
|  |         uri: this.modelUrl, | ||||||
|  |         minimumPixelSize: 64, | ||||||
|  |         readyPromise: (model) => { | ||||||
|  |           const boundingSphere = model.boundingSphere; | ||||||
|  |           if (boundingSphere) { | ||||||
|  |             this.modelHeight = boundingSphere.radius * 2; | ||||||
|  |             console.log('模型高度检测完成:', this.modelHeight, '米'); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 创建路径(修改:叠加baseHeight) | ||||||
|  |    */ | ||||||
|  |   createPath() { | ||||||
|  |     if (this.positions.length < 2) return; | ||||||
|  |  | ||||||
|  |     // 关键修改:遍历所有点时,给每个点的高度叠加baseHeight | ||||||
|  |     const pathPositions = this.positions.map((pos) => { | ||||||
|  |       const finalHeight = (pos.height || 0) + this.baseHeight; | ||||||
|  |       return Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, finalHeight); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     this.pathEntity = this.viewer.entities.add({ | ||||||
|  |       name: 'Model Path', | ||||||
|  |       polyline: { | ||||||
|  |         positions: pathPositions, | ||||||
|  |         width: this.pathStyle.width, | ||||||
|  |         material: this.pathStyle.color, | ||||||
|  |         clampToGround: false | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 创建位置标记(修改:叠加baseHeight) | ||||||
|  |    */ | ||||||
|  |   createMarkers() { | ||||||
|  |     this.markerEntities = this.positions.map((pos, index) => { | ||||||
|  |       // 关键修改:标记点高度同样叠加baseHeight | ||||||
|  |       const finalHeight = (pos.height || 0) + this.baseHeight; | ||||||
|  |       const position = Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, finalHeight); | ||||||
|  |  | ||||||
|  |       const marker = this.viewer.entities.add({ | ||||||
|  |         name: `Position Marker ${index}`, | ||||||
|  |         position: position, | ||||||
|  |         point: { | ||||||
|  |           color: this.markerStyle.color, | ||||||
|  |           pixelSize: this.markerStyle.pixelSize, | ||||||
|  |           outlineColor: this.markerStyle.outlineColor, | ||||||
|  |           outlineWidth: this.markerStyle.outlineWidth | ||||||
|  |         }, | ||||||
|  |         label: { | ||||||
|  |           text: pos.name || `Point ${index + 1}`, | ||||||
|  |           font: '14px sans-serif', | ||||||
|  |           pixelOffset: new Cesium.Cartesian2(0, -20), | ||||||
|  |           fillColor: Cesium.Color.WHITE | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       return marker; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 创建模型上方的图标(保持不变) | ||||||
|  |    */ | ||||||
|  |   createIcon() { | ||||||
|  |     this.iconEntity = this.viewer.entities.add({ | ||||||
|  |       name: 'Model Icon', | ||||||
|  |       position: new Cesium.CallbackProperty(() => { | ||||||
|  |         return this.adjustCartesianHeight(this.currentPosition, 0.3); | ||||||
|  |       }, false), | ||||||
|  |       billboard: { | ||||||
|  |         image: this.iconUrl, | ||||||
|  |         width: this.iconSize, | ||||||
|  |         height: this.iconSize, | ||||||
|  |         verticalOrigin: Cesium.VerticalOrigin.BOTTOM | ||||||
|  |       }, | ||||||
|  |       label: { | ||||||
|  |         text: 'Model Icon', | ||||||
|  |         font: '14px sans-serif', | ||||||
|  |         pixelOffset: new Cesium.Cartesian2(0, -40), | ||||||
|  |         fillColor: Cesium.Color.WHITE | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 调整笛卡尔坐标的高度(在原有高度基础上叠加指定值) | ||||||
|  |    * @param {Cesium.Cartesian3} cartesian - 原始笛卡尔坐标 | ||||||
|  |    * @param {Number} heightOffset - 要增加的高度值(米,可为负数) | ||||||
|  |    * @param {Cesium.Ellipsoid} [ellipsoid=Cesium.Ellipsoid.WGS84] - 参考椭球体 | ||||||
|  |    * @returns {Cesium.Cartesian3|null} 调整高度后的笛卡尔坐标(失败返回null) | ||||||
|  |    */ | ||||||
|  |   adjustCartesianHeight(cartesian, heightOffset, ellipsoid = Cesium.Ellipsoid.WGS84) { | ||||||
|  |     // 1. 校验输入的笛卡尔坐标是否有效 | ||||||
|  |     // if (!Cesium.Cartesian3.isValid(cartesian)) { | ||||||
|  |     //   console.error("无效的笛卡尔坐标,无法调整高度"); | ||||||
|  |     //   return null; | ||||||
|  |     // } | ||||||
|  |  | ||||||
|  |     // 2. 笛卡尔坐标 → Cartographic(地理坐标,含弧度经纬度和高度) | ||||||
|  |     const cartographic = Cesium.Cartographic.fromCartesian(cartesian, ellipsoid); | ||||||
|  |     if (!cartographic) { | ||||||
|  |       console.error('笛卡尔坐标转换为Cartographic失败'); | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 3. 调整高度(在原有高度上叠加offset,可为负数) | ||||||
|  |     cartographic.height += heightOffset; | ||||||
|  |     // 可选:确保高度不低于0(根据需求决定是否保留) | ||||||
|  |     // cartographic.height = Math.max(cartographic.height, 0); | ||||||
|  |  | ||||||
|  |     // 4. Cartographic → 笛卡尔坐标(使用弧度经纬度转换) | ||||||
|  |     const adjustedCartesian = Cesium.Cartesian3.fromRadians( | ||||||
|  |       cartographic.longitude, // 经度(弧度) | ||||||
|  |       cartographic.latitude, // 纬度(弧度) | ||||||
|  |       cartographic.height, // 调整后的高度(米) | ||||||
|  |       ellipsoid | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     return adjustedCartesian; | ||||||
|  |   } | ||||||
|  |   /** | ||||||
|  |    * 开始移动(保持不变) | ||||||
|  |    */ | ||||||
|  |   start() { | ||||||
|  |     if (this.isMoving) { | ||||||
|  |       console.log('模型已经在移动中'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (this.positions.length < 2) { | ||||||
|  |       console.error('无法移动:位置点数量不足(至少需要2个)'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this.isMoving = true; | ||||||
|  |     this.lastTime = performance.now(); | ||||||
|  |     this.progress = 0; | ||||||
|  |     console.log('开始移动,速度:', this.speed, '米/秒'); | ||||||
|  |     this.animate(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 动画循环函数(保持不变) | ||||||
|  |    */ | ||||||
|  |   animate() { | ||||||
|  |     if (!this.isMoving) return; | ||||||
|  |     const currentTime = performance.now(); | ||||||
|  |     const timeDeltaMs = currentTime - this.lastTime; | ||||||
|  |     this.lastTime = currentTime; | ||||||
|  |     const timeDelta = timeDeltaMs / 1000; | ||||||
|  |     this.updatePosition(timeDelta); | ||||||
|  |     this.animationFrameId = requestAnimationFrame(() => this.animate()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 停止移动(保持不变) | ||||||
|  |    */ | ||||||
|  |   stop() { | ||||||
|  |     if (!this.isMoving) return; | ||||||
|  |     this.isMoving = false; | ||||||
|  |     console.log('停止移动'); | ||||||
|  |     if (this.animationFrameId) { | ||||||
|  |       cancelAnimationFrame(this.animationFrameId); | ||||||
|  |       this.animationFrameId = null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 更新模型位置(修改:叠加baseHeight) | ||||||
|  |    */ | ||||||
|  |   updatePosition(timeDelta) { | ||||||
|  |     if (!this.isMoving) return; | ||||||
|  |     const distanceToMove = this.speed * timeDelta; | ||||||
|  |     const currentPos = this.positions[this.currentIndex]; | ||||||
|  |     const nextIndex = (this.currentIndex + 1) % this.positions.length; | ||||||
|  |     const nextPos = this.positions[nextIndex]; | ||||||
|  |  | ||||||
|  |     // 关键修改:计算当前点和下一点时,均叠加baseHeight | ||||||
|  |     const currentFinalHeight = (currentPos.height || 0) + this.baseHeight; | ||||||
|  |     const nextFinalHeight = (nextPos.height || 0) + this.baseHeight; | ||||||
|  |  | ||||||
|  |     const currentCartesian = Cesium.Cartesian3.fromDegrees(currentPos.lon, currentPos.lat, currentFinalHeight); | ||||||
|  |     const nextCartesian = Cesium.Cartesian3.fromDegrees(nextPos.lon, nextPos.lat, nextFinalHeight); | ||||||
|  |  | ||||||
|  |     // 后续逻辑保持不变 | ||||||
|  |     const totalDistance = Cesium.Cartesian3.distance(currentCartesian, nextCartesian); | ||||||
|  |     if (totalDistance < 0.001) { | ||||||
|  |       this.currentIndex = nextIndex; | ||||||
|  |       this.progress = 0; | ||||||
|  |       this.setModelPosition(nextCartesian, this.getNextPositionCartesian(nextIndex)); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this.progress += distanceToMove / totalDistance; | ||||||
|  |     if (this.progress >= 1.0) { | ||||||
|  |       const remainingDistance = (this.progress - 1.0) * totalDistance; | ||||||
|  |       this.currentIndex = nextIndex; | ||||||
|  |       if (!this.loop && this.currentIndex === this.positions.length - 1) { | ||||||
|  |         this.setModelPosition(nextCartesian, null); | ||||||
|  |         this.stop(); | ||||||
|  |         console.log('已到达终点,停止移动'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       const newTotalDistance = Cesium.Cartesian3.distance(nextCartesian, this.getNextPositionCartesian(this.currentIndex)); | ||||||
|  |       this.progress = newTotalDistance > 0.001 ? remainingDistance / newTotalDistance : 0; | ||||||
|  |       this.setModelPosition(nextCartesian, this.getNextPositionCartesian(this.currentIndex)); | ||||||
|  |     } else { | ||||||
|  |       const newPosition = Cesium.Cartesian3.lerp(currentCartesian, nextCartesian, this.progress, new Cesium.Cartesian3()); | ||||||
|  |       this.setModelPosition(newPosition, nextCartesian); | ||||||
|  |     } | ||||||
|  |     this.viewer.scene.requestRender(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 获取下一个位置的笛卡尔坐标(修改:叠加baseHeight) | ||||||
|  |    */ | ||||||
|  |   getNextPositionCartesian(currentIndex) { | ||||||
|  |     const nextIndex = (currentIndex + 1) % this.positions.length; | ||||||
|  |     const nextPos = this.positions[nextIndex]; | ||||||
|  |     // 关键修改:返回下一点时叠加baseHeight | ||||||
|  |     const finalHeight = (nextPos.height || 0) + this.baseHeight; | ||||||
|  |     return Cesium.Cartesian3.fromDegrees(nextPos.lon, nextPos.lat, finalHeight); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 设置模型位置和方向(保持不变) | ||||||
|  |    */ | ||||||
|  |   setModelPosition(position, targetPosition) { | ||||||
|  |     if (!this.modelEntity) return; | ||||||
|  |     this.currentPosition = position; | ||||||
|  |     this.modelEntity.position.setValue(position); | ||||||
|  |     if (targetPosition) { | ||||||
|  |       const heading = this.calculateHeading(position, targetPosition); | ||||||
|  |       this.modelEntity.orientation.setValue(Cesium.Transforms.headingPitchRollQuaternion(position, new Cesium.HeadingPitchRoll(heading, 0, 0))); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 计算两点之间的朝向(保持不变) | ||||||
|  |    */ | ||||||
|  |   calculateHeading(from, to) { | ||||||
|  |     const fromCartographic = Cesium.Cartographic.fromCartesian(from); | ||||||
|  |     const toCartographic = Cesium.Cartographic.fromCartesian(to); | ||||||
|  |     const startLat = fromCartographic.latitude; | ||||||
|  |     const startLon = fromCartographic.longitude; | ||||||
|  |     const endLat = toCartographic.latitude; | ||||||
|  |     const endLon = toCartographic.longitude; | ||||||
|  |     const dLon = endLon - startLon; | ||||||
|  |     const y = Math.sin(dLon) * Math.cos(endLat); | ||||||
|  |     const x = Math.cos(startLat) * Math.sin(endLat) - Math.sin(startLat) * Math.cos(endLat) * Math.cos(dLon); | ||||||
|  |     let heading = Math.atan2(y, x); | ||||||
|  |     heading = (heading + Cesium.Math.TWO_PI) % Cesium.Math.TWO_PI; | ||||||
|  |     heading = (heading + this.headingOffset) % Cesium.Math.TWO_PI; | ||||||
|  |     return heading; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 手动设置模型高度(保持不变) | ||||||
|  |    */ | ||||||
|  |   setModelHeight(height) { | ||||||
|  |     this.modelHeight = height; | ||||||
|  |     console.log('手动设置模型高度:', height, '米'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 设置航向角偏移(保持不变) | ||||||
|  |    */ | ||||||
|  |   setHeadingOffset(offsetDegrees) { | ||||||
|  |     this.headingOffset = Cesium.Math.toRadians(offsetDegrees); | ||||||
|  |     console.log('设置航向角偏移:', offsetDegrees, '度'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 销毁所有资源(保持不变) | ||||||
|  |    */ | ||||||
|  |   destroy() { | ||||||
|  |     this.stop(); | ||||||
|  |     if (this.modelEntity) this.viewer.entities.remove(this.modelEntity); | ||||||
|  |     if (this.pathEntity) this.viewer.entities.remove(this.pathEntity); | ||||||
|  |     this.markerEntities.forEach((marker) => this.viewer.entities.remove(marker)); | ||||||
|  |     if (this.iconEntity) this.viewer.entities.remove(this.iconEntity); | ||||||
|  |     this.modelEntity = null; | ||||||
|  |     this.pathEntity = null; | ||||||
|  |     this.markerEntities = []; | ||||||
|  |     this.iconEntity = null; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -34,13 +34,7 @@ | |||||||
|             > |             > | ||||||
|           </el-col> |           </el-col> | ||||||
|           <el-col :span="1.8"> |           <el-col :span="1.8"> | ||||||
|             <el-button |             <el-button type="primary" plain icon="User" :disabled="single" @click="handleBindUser()" v-hasPermi="['gps:equipment:bindManmachine']" | ||||||
|               type="primary" |  | ||||||
|               plain |  | ||||||
|               icon="User" |  | ||||||
|               :disabled="single" |  | ||||||
|               @click="handleBindUser()" |  | ||||||
|               v-hasPermi="['gps:equipment:unbindManmachine', 'gps:equipment:bindManmachine']" |  | ||||||
|               >绑定用户</el-button |               >绑定用户</el-button | ||||||
|             > |             > | ||||||
|           </el-col> |           </el-col> | ||||||
| @ -97,11 +91,11 @@ | |||||||
|                 type="primary" |                 type="primary" | ||||||
|                 icon="User" |                 icon="User" | ||||||
|                 @click="scope.row.type === 1 ? handleUnbindUser(scope.row) : handleBindUser(scope.row)" |                 @click="scope.row.type === 1 ? handleUnbindUser(scope.row) : handleBindUser(scope.row)" | ||||||
|                 v-hasPermi="['gps:equipment:unbindManmachine', 'gps:equipment:bindManmachine']" |                 v-hasPermi="scope.row.type === 1 ? ['gps:equipment:unbindManmachine'] : ['gps:equipment:bindManmachine']" | ||||||
|               > |               > | ||||||
|               </el-button> |               </el-button> | ||||||
|             </el-tooltip> |             </el-tooltip> | ||||||
|             <!-- 新增:跳转空页面按钮 --> |  | ||||||
|             <el-tooltip content="足迹" placement="top"> |             <el-tooltip content="足迹" placement="top"> | ||||||
|               <el-button |               <el-button | ||||||
|                 link |                 link | ||||||
| @ -113,7 +107,14 @@ | |||||||
|               ></el-button> |               ></el-button> | ||||||
|             </el-tooltip> |             </el-tooltip> | ||||||
|             <el-tooltip content="历史记录" placement="top"> |             <el-tooltip content="历史记录" placement="top"> | ||||||
|               <el-button link type="primary" icon="Clock" @click="handleOpenHistoryUser(scope.row.clientId, scope.row.userId)"> </el-button> |               <el-button | ||||||
|  |                 link | ||||||
|  |                 type="primary" | ||||||
|  |                 icon="Clock" | ||||||
|  |                 @click="handleOpenHistoryUser(scope.row.clientId, scope.row.userId)" | ||||||
|  |                 v-hasPermi="['gps:equipment:getUserList']" | ||||||
|  |               > | ||||||
|  |               </el-button> | ||||||
|             </el-tooltip> |             </el-tooltip> | ||||||
|           </template> |           </template> | ||||||
|         </el-table-column> |         </el-table-column> | ||||||
| @ -146,9 +147,6 @@ | |||||||
|             <el-option v-for="project in projectList" :key="project.projectId" :label="project.projectName" :value="project.projectId" /> |             <el-option v-for="project in projectList" :key="project.projectId" :label="project.projectName" :value="project.projectId" /> | ||||||
|           </el-select> |           </el-select> | ||||||
|         </el-form-item> |         </el-form-item> | ||||||
|         <el-form-item label="设备名称" prop="deviceName"> |  | ||||||
|           <el-input v-model="bindForm.deviceName" disabled placeholder="设备名称" /> |  | ||||||
|         </el-form-item> |  | ||||||
|         <el-form-item label="选择用户" prop="userId" required> |         <el-form-item label="选择用户" prop="userId" required> | ||||||
|           <el-select v-model="bindForm.userId" placeholder="请选择用户" clearable> |           <el-select v-model="bindForm.userId" placeholder="请选择用户" clearable> | ||||||
|             <el-option v-for="user in userList" :key="user.sysUserId" :label="user.userName" :value="user.sysUserId" /> |             <el-option v-for="user in userList" :key="user.sysUserId" :label="user.userName" :value="user.sysUserId" /> | ||||||
| @ -175,6 +173,7 @@ | |||||||
|         > |         > | ||||||
|           <el-table-column label="序号" type="index" width="60" align="center" /> |           <el-table-column label="序号" type="index" width="60" align="center" /> | ||||||
|           <el-table-column label="用户名" align="center" prop="userName" /> |           <el-table-column label="用户名" align="center" prop="userName" /> | ||||||
|  |           <el-table-column label="项目名称" align="center" prop="projectName" /> | ||||||
|           <el-table-column label="状态" align="center" width="120"> |           <el-table-column label="状态" align="center" width="120"> | ||||||
|             <template #default="scope"> |             <template #default="scope"> | ||||||
|               <el-tag :type="scope.row.type === 0 ? 'success' : 'info'" size="small"> |               <el-tag :type="scope.row.type === 0 ? 'success' : 'info'" size="small"> | ||||||
| @ -182,6 +181,19 @@ | |||||||
|               </el-tag> |               </el-tag> | ||||||
|             </template> |             </template> | ||||||
|           </el-table-column> |           </el-table-column> | ||||||
|  |           <el-table-column label="操作" align="center" width="80"> | ||||||
|  |             <template #default="scope"> | ||||||
|  |               <el-tooltip content="足迹" placement="top"> | ||||||
|  |                 <el-button | ||||||
|  |                   link | ||||||
|  |                   type="primary" | ||||||
|  |                   icon="Location" | ||||||
|  |                   v-hasPermi="['gps:equipmentSon:getList']" | ||||||
|  |                   @click="handleGoToEmptyPage(scope.row.userId, scope.row.projectId, currentHistoryClientId)" | ||||||
|  |                 ></el-button> | ||||||
|  |               </el-tooltip> | ||||||
|  |             </template> | ||||||
|  |           </el-table-column> | ||||||
|         </el-table> |         </el-table> | ||||||
|  |  | ||||||
|         <div v-if="!historyUserLoading && historyUserList.length === 0" style="text-align: center; padding: 50px 0"> |         <div v-if="!historyUserLoading && historyUserList.length === 0" style="text-align: center; padding: 50px 0"> | ||||||
| @ -479,6 +491,8 @@ const handleViewAll = () => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const handleGoToEmptyPage = (userId: any, projectId: any, clientId: any) => { | const handleGoToEmptyPage = (userId: any, projectId: any, clientId: any) => { | ||||||
|  |   console.log('userId:', userId, 'projectId:', projectId, 'clientId:', clientId); | ||||||
|  |  | ||||||
|   router.push({ |   router.push({ | ||||||
|     path: './equipmentGPS', |     path: './equipmentGPS', | ||||||
|     query: { |     query: { | ||||||
| @ -578,41 +592,53 @@ const handleDelete = async (row?: ExtendedEquipmentVO) => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| /** 绑定用户按钮操作 */ | /** 绑定用户按钮操作 */ | ||||||
| const handleBindUser = async (row?: EquipmentVO) => { | const handleBindUser = async (row: ExtendedEquipmentVO) => { | ||||||
|  |   try { | ||||||
|  |     if (!row) { | ||||||
|  |       proxy?.$modal.msgWarning('未获取到设备信息'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (row.id === undefined || row.id === null || row.id === '') { | ||||||
|  |       proxy?.$modal.msgWarning('设备ID不能为空'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const deviceId = Number(row.id); | ||||||
|  |     if (isNaN(deviceId) || deviceId <= 0) { | ||||||
|  |       proxy?.$modal.msgWarning(`设备ID格式错误,必须是正整数,当前值: ${row.id}`); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!row.clientId || typeof row.clientId !== 'string' || row.clientId.trim() === '') { | ||||||
|  |       proxy?.$modal.msgWarning(`设备标识clientId格式错误,必须是非空字符串,当前值: ${row.clientId}`); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 初始化表单 | ||||||
|     Object.assign(bindForm, { |     Object.assign(bindForm, { | ||||||
|     id: undefined, |       id: deviceId, | ||||||
|       projectId: undefined, |       projectId: undefined, | ||||||
|       userId: undefined, |       userId: undefined, | ||||||
|     clientId: row?.clientId, |       clientId: row.clientId, | ||||||
|     deviceName: undefined |       deviceName: row.deviceName || '未知设备' | ||||||
|     }); |     }); | ||||||
|     bindUserFormRef.value?.resetFields(); |     bindUserFormRef.value?.resetFields(); | ||||||
|     userList.value = []; |     userList.value = []; | ||||||
|  |  | ||||||
|   const _id = row?.id || ids.value[0]; |     // 加载项目和用户数据 | ||||||
|   try { |  | ||||||
|     const res = await getEquipment(_id); |  | ||||||
|     const equipmentData = res.data; |  | ||||||
|     bindForm.id = equipmentData.id; |  | ||||||
|     bindForm.deviceName = equipmentData.deviceName; |  | ||||||
|     bindForm.clientId = row?.clientId || equipmentData.clientId; |  | ||||||
|  |  | ||||||
|     if (projectList.value.length === 0) { |     if (projectList.value.length === 0) { | ||||||
|       await getProjects(); |       await getProjects(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (equipmentData.projectId) { |     if (currentProject.value?.id) { | ||||||
|       bindForm.projectId = equipmentData.projectId; |  | ||||||
|       await getUsersByProjectId(equipmentData.projectId); |  | ||||||
|     } else if (currentProject.value?.id) { |  | ||||||
|       bindForm.projectId = currentProject.value.id; |       bindForm.projectId = currentProject.value.id; | ||||||
|       await getUsersByProjectId(currentProject.value.id); |       await getUsersByProjectId(currentProject.value.id); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bindDialogVisible.value = true; |     bindDialogVisible.value = true; | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|     console.error('获取绑定用户信息失败:', error); |     console.error('打开绑定用户对话框失败:', error); | ||||||
|     proxy?.$modal.msgError('获取数据失败,请重试'); |     proxy?.$modal.msgError('操作失败,请重试'); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @ -628,17 +654,9 @@ const submitBindUser = () => { | |||||||
|  |  | ||||||
|       bindButtonLoading.value = true; |       bindButtonLoading.value = true; | ||||||
|       try { |       try { | ||||||
|         const bindData = { |         // 直接使用bindForm数据调用bindUser接口 | ||||||
|           id: bindForm.id, |         console.log('提交绑定用户参数:', bindForm); | ||||||
|           projectId: bindForm.projectId, |         await bindUser(bindForm); | ||||||
|           userId: bindForm.userId, |  | ||||||
|           clientId: bindForm.clientId, |  | ||||||
|           deviceName: bindForm.deviceName |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         console.log('提交绑定用户参数:', bindData); |  | ||||||
|  |  | ||||||
|         await bindUser(bindData); |  | ||||||
|         proxy?.$modal.msgSuccess('用户绑定成功'); |         proxy?.$modal.msgSuccess('用户绑定成功'); | ||||||
|         bindDialogVisible.value = false; |         bindDialogVisible.value = false; | ||||||
|         await getList(); |         await getList(); | ||||||
| @ -718,6 +736,8 @@ const handleOpenHistoryUser = async (clientId: string | number | undefined, curr | |||||||
|   historyUserDialogVisible.value = true; |   historyUserDialogVisible.value = true; | ||||||
|   historyUserLoading.value = true; |   historyUserLoading.value = true; | ||||||
|   historyUserList.value = []; |   historyUserList.value = []; | ||||||
|  |   // 保存当前clientId用于足迹操作 | ||||||
|  |   currentHistoryClientId.value = clientId; | ||||||
|  |  | ||||||
|   try { |   try { | ||||||
|     const res = await gethistroyUser({ |     const res = await gethistroyUser({ | ||||||
| @ -742,6 +762,27 @@ const handleOpenHistoryUser = async (clientId: string | number | undefined, curr | |||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /** 历史弹窗中的足迹操作 */ | ||||||
|  | const handleFootprintOperation = () => { | ||||||
|  |   if (!historyUserList.value || historyUserList.value.length === 0) { | ||||||
|  |     proxy?.$modal.msgWarning('暂无用户数据,无法查看足迹'); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // 获取第一个用户的数据(通常是当前绑定用户) | ||||||
|  |   const firstUser = historyUserList.value[0]; | ||||||
|  |   if (!firstUser.sysUserId) { | ||||||
|  |     proxy?.$modal.msgWarning('用户ID不存在,无法查看足迹'); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // 跳转到足迹页面 | ||||||
|  |   handleGoToEmptyPage(firstUser.sysUserId, firstUser.projectId || currentProject.value?.id, currentHistoryClientId.value); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // 当前历史弹窗的clientId | ||||||
|  | const currentHistoryClientId = ref(''); | ||||||
|  |  | ||||||
| /** 页面挂载时初始化 */ | /** 页面挂载时初始化 */ | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
|   getList(); |   getList(); | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ | |||||||
|             <el-form-item label="地块名称" prop="landName"> |             <el-form-item label="地块名称" prop="landName"> | ||||||
|               <el-input v-model="queryParams.landName" placeholder="请输入地块名称" clearable @keyup.enter="handleQuery" /> |               <el-input v-model="queryParams.landName" placeholder="请输入地块名称" clearable @keyup.enter="handleQuery" /> | ||||||
|             </el-form-item> |             </el-form-item> | ||||||
|             <el-form-item label="所属村委会" prop="villageCommittee"> |             <!-- <el-form-item label="所属村委会" prop="villageCommittee"> | ||||||
|               <el-input v-model="queryParams.villageCommittee" placeholder="请输入所属村委会" clearable @keyup.enter="handleQuery" /> |               <el-input v-model="queryParams.villageCommittee" placeholder="请输入所属村委会" clearable @keyup.enter="handleQuery" /> | ||||||
|             </el-form-item> |             </el-form-item> | ||||||
|             <el-form-item label="设计面积(亩)" prop="designArea"> |             <el-form-item label="设计面积(亩)" prop="designArea"> | ||||||
| @ -21,7 +21,7 @@ | |||||||
|             </el-form-item> |             </el-form-item> | ||||||
|             <el-form-item label="农户数(户)" prop="farmerCount"> |             <el-form-item label="农户数(户)" prop="farmerCount"> | ||||||
|               <el-input v-model="queryParams.farmerCount" type="number" placeholder="请输入农户数" clearable @keyup.enter="handleQuery" /> |               <el-input v-model="queryParams.farmerCount" type="number" placeholder="请输入农户数" clearable @keyup.enter="handleQuery" /> | ||||||
|             </el-form-item> |             </el-form-item> --> | ||||||
|             <el-form-item> |             <el-form-item> | ||||||
|               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> |               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> | ||||||
|               <el-button icon="Refresh" @click="resetQuery">重置</el-button> |               <el-button icon="Refresh" @click="resetQuery">重置</el-button> | ||||||
|  | |||||||
| @ -667,16 +667,20 @@ const data = reactive<PageData<LandTransferLedgerForm, LandTransferLedgerQuery>> | |||||||
|   }, |   }, | ||||||
|   rules: { |   rules: { | ||||||
|     projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }], |     projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }], | ||||||
|  |     landBlockId: [{ required: true, message: '请选择对应地块', trigger: 'change' }], | ||||||
|     landType: [{ required: true, message: '土地类型不能为空', trigger: 'change' }], |     landType: [{ required: true, message: '土地类型不能为空', trigger: 'change' }], | ||||||
|  |     transferStatus: [{ required: true, message: '请选择流转台账状态', trigger: 'change' }], | ||||||
|  |     designArea: [{ required: true, message: '请输入设计面积', trigger: 'blur' }], | ||||||
|  |     responsiblePerson: [{ required: true, message: '请输责任人', trigger: 'blur' }], | ||||||
|  |     expectedFinishDate: [{ required: true, message: '请选择预计完成时间', trigger: 'change' }], | ||||||
|     transferRatio: [ |     transferRatio: [ | ||||||
|       // 动态校验:仅已流转状态下必填 |  | ||||||
|       { |       { | ||||||
|         required: true, |         required: true, | ||||||
|         message: '流转比例不能为空', |         message: '流转比例不能为空', | ||||||
|         trigger: ['blur', 'change'], |         trigger: ['blur', 'change'], | ||||||
|         validator: (rule, value, callback) => { |         validator: (rule, value, callback) => { | ||||||
|           if (data.form.transferStatus !== '1') { |           if (data.form.transferStatus !== '1') { | ||||||
|             callback(); // 非已流转状态跳过校验 |             callback(); | ||||||
|             return; |             return; | ||||||
|           } |           } | ||||||
|           if (value === undefined || value === null || value === '') { |           if (value === undefined || value === null || value === '') { | ||||||
| @ -686,7 +690,6 @@ const data = reactive<PageData<LandTransferLedgerForm, LandTransferLedgerQuery>> | |||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       // 比例范围校验(0-100) |  | ||||||
|       { |       { | ||||||
|         validator: (rule, value, callback) => { |         validator: (rule, value, callback) => { | ||||||
|           if (value < 0 || value > 100) { |           if (value < 0 || value > 100) { | ||||||
| @ -746,6 +749,7 @@ const sonSummaryInfo = computed(() => { | |||||||
| const lastSelectedParent = ref<LandTransferLedgerVO | null>(null); | const lastSelectedParent = ref<LandTransferLedgerVO | null>(null); | ||||||
| const sonRules = { | const sonRules = { | ||||||
|   projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }], |   projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }], | ||||||
|  |   parentId: [{ required: true, message: '父级ID不能为空', trigger: 'blur' }], | ||||||
|   landType: [{ required: true, message: '土地类型不能为空', trigger: 'change' }], |   landType: [{ required: true, message: '土地类型不能为空', trigger: 'change' }], | ||||||
|   transferRatio: [ |   transferRatio: [ | ||||||
|     { |     { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user